1️⃣ EC2 서버 준비
- 목표: GitHub → EC2로 JAR 파일을 수동 배포 가능한 상태 만들기
- 자동화하려면 우선 수동으로도 배포가 잘 되는지 확인하는 게 기본이다!
🔹 Java 설치 (EC2에서 JAR 실행에 필요)
sudo apt update
sudo apt install openjdk-17-jdk -y
java -version # 설치 확인
🔹 디렉토리 구조 생성
mkdir -p ~/app/deploy
mkdir -p ~/app/logs
- ~/app/deploy: jar 파일을 배포할 위치
- ~/app/logs: 실행 로그 저장할 위치
2️⃣ GitHub Actions 자동화 + S3 업로드
🔹 AWS CLI 설치 (로컬/Actions runner에 설치되어 있어야 함)
sudo apt install awscli -y
aws configure
- AWS Access Key, Secret Key, region, output 설정 필요
GitHub Actions runner 안에서 AWS CLI가 필요하다는 의미
- GitHub Actions는 내부적으로 Ubuntu 환경의 가상 머신을 띄워서 코드를 빌드하고 배포한다.
- 이 GitHub Actions 환경에서는 S3나 Secrets Manager 같은 AWS 리소스에 접근하려면 AWS CLI가 필요함.
- 다행히, GitHub Actions의 기본 Ubuntu runner에는 이미 awscli가 설치돼 있어.
- 👉 그래서 추가로 설치할 필요는 거의 없다!
🔹 GitHub Secrets 등록
이름 | 설명 |
AWS_ACCESS_KEY_ID | AWS IAM 사용자 access key (3단계 이후 추가해주세요!) |
AWS_SECRET_ACCESS_KEY | AWS IAM 사용자 secret key (3단계 이후 추가해주세요!) |
AWS_REGION | 예: ap-northeast-2 |
EC2_HOST | 예: ec2-user@xxx.xxx.xxx.xxx 탄력적 IP 안 쓰는 경우: (퍼블릭 DNS) |
EC2_KEY | PEM 키 파일 전체 문자열 (Base64 인코딩 권장) |
GitHub 저장소 > Settings > Secrets → New repository secret 클릭
✏️ pem 키 파일 전체 문자열 여는법
📁 터미널에서 열기
cat ~/Downloads/your-key.pem
# 형식은 아래와 같음
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzla1d3J...
...중략...
F8+kxP2LPk3Z5TR7e+5nQA==
-----END RSA PRIVATE KEY-----
- ~/Downloads/your-key.pem 부분은 저장되어 있는 .pem 파일 경로로 바꿔준다.
- 이 명령은 .pem 내용 전체를 터미널에 출력해줌.
🔹 .github/workflows/deploy.yml 예시
name: Deploy Spring Boot to AWS
on:
push:
branches: [ main ] # main 브랜치에 push 될 때마다 이 워크플로우가 실행됨
jobs:
build-and-upload: # 작업(Job) 이름
runs-on: ubuntu-latest # GitHub에서 제공하는 최신 Ubuntu 가상환경에서 실행
steps:
- name: Checkout code
uses: actions/checkout@v4
# 현재 리포지토리의 코드를 체크아웃 (가져오기) 해서 다음 단계에서 사용할 수 있게 함
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin' # OpenJDK 배포판 중 하나인 Temurin 사용
java-version: '17' # Java 17 버전 설치
- name: Give gradlew permission
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew clean build
# Gradle 빌드 실행 (clean: 기존 빌드 파일 삭제, build: 새로 빌드 생성)
# 결과물은 보통 build/libs 디렉토리에 생성됨
- name: Rename jar for deployment
run: mv build/libs/*.jar build/libs/app.jar
# S3에 업로드할 때 파일명을 고정하기
- name: Upload jar to S3
uses: jakejarvis/s3-sync-action@master
with:
args: --acl private --follow-symlinks
# --acl private: S3 객체를 비공개로 업로드
# --follow-symlinks: 심볼릭 링크도 따라가서 업로드
env:
AWS_S3_BUCKET: ${{ secrets.S3_BUCKET_NAME }}
# S3 버킷 이름 (예: my-app-deploy-bucket) — GitHub Secrets에 저장되어 있어야 함
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# AWS IAM 사용자의 액세스 키 ID (보안상 Secrets에 저장)
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# AWS IAM 사용자의 비밀 액세스 키 (보안상 Secrets에 저장)
AWS_REGION: ${{ secrets.AWS_REGION }}
# S3 버킷이 있는 AWS 리전 (예: ap-northeast-2)
SOURCE_DIR: build/libs
# S3에 업로드할 파일들이 있는 디렉토리 (예: 빌드된 .jar 파일)
빌드시 plain.jar 생기지 않게 설정하기(build.gradle에 다음 옵션을 추가)
jar {
enabled = false
}
main 브랜치에 push 발생 시
- ✅ 코드 checkout
- ✅ JDK 17 설정
- ✅ gradlew 실행 권한 부여
- ✅ Gradle 빌드 수행 (./gradlew clean build)
- ✅ 빌드된 JAR 파일을 S3 버킷에 업로드
3️⃣ S3 연동 및 빌드 파일 업로드 자동화 (GitHub Actions에서 실행)
🤔 S3 왜 해야 할까?
- GitHub Actions에서 생성한 .jar 파일을 EC2에 직접 복사하지 않고,
- S3 버킷에 먼저 업로드하고, EC2 인스턴스에서는 그걸 받아서 실행하게 할 수 있다.
- → 무중단 배포 스크립트에서 이 방식이 깔끔하고 보안적으로도 좋다.
📦 준비물
- S3 버킷 생성 (한 번만)
- IAM 사용자의 Access Key / Secret Key
- GitHub Secrets에 등록
- Actions에서 aws s3 cp 명령어로 업로드
🧱 S3 버킷 생성 (한 번만 하면 됨)
- S3 콘솔 접속
- “버킷 만들기” 클릭
- 이름: your-app-deploy-bucket (원하는 이름)
- 리전은 EC2와 동일한 리전 선택 (예: ap-northeast-2)
- 나머지는 기본 설정 → “버킷 만들기” 클릭
🛠️ GitHub에 AWS 인증 정보 등록
1. IAM → 사용자 추가
2. 이름: github-deploy-user
3. 권한 정책 추가
- 정책: AmazonEC2FullAccess, AmazonS3FullAccess, SecretsManagerReadWrite
이름 | 설명 |
AmazonEC2FullAccess | EC2 인스턴스에 접근 및 명령 실행 |
AmazonS3FullAccess | S3 버킷에서 .jar 파일 다운로드 등 |
SecretsManagerReadWrite | Secrets Manager에 저장된 비밀을 읽기/쓰기 가능 |
4. 사용자 만들고 Access Key 생성
- 상단 탭에서 "보안 자격 증명(Security credentials)" 클릭
- 아래로 내려서 "액세스 키" 섹션으로 이동
- "새 액세스 키 만들기(Create access key)" 버튼 클릭
- 사용 사례 → "명령줄 인터페이스(CLI), SDK, 코드" 선택
- 다음 → 생성하면 Access Key ID / Secret Access Key 가 나옴
5. GitHub → 저장소 Settings → Secrets and variables → Actions
- AWS_ACCESS_KEY_ID = 발급받은 액세스 키
- AWS_SECRET_ACCESS_KEY = 발급받은 시크릿 키
- AWS_REGION = ex. ap-northeast-2
⚠️ Secret Access Key는 다시 볼 수 없으니까 꼭 복사해서 따로 저장하거나 바로 Secrets에 추가해줘야 함.
🧱 S3 버킷 생성
🤔 왜 S3 버킷을 쓰는가?
- GitHub Actions → EC2로 직접 파일 전송하려면 복잡할 수 있음.
- 대신 GitHub Actions → S3 업로드 → EC2에서 다운로드 방식으로 간단하고 깔끔하게 파일을 전달할 수 있다.
- 그리고 S3는 안전하고 빠름.
1. 버킷 생성
- 🔒 [모두 차단] 체크 상태 유지 (기본값) → 나중에 EC2에서 IAM 권한으로 접근하게 설정할 거라서 퍼블릭 오픈은 안 해도 됨
- 버전 관리, 암호화, 태그 등은 기본값 그대로 둬도 됨
2. GitHub → 저장소 Settings → Secrets and variables → Actions
- S3_BUCKET_NAME = 아까 만든 버킷 이름
🧑💻 EC2에서 접근할 수 있도록 IAM 역할 만들기 (선택)
EC2에서 S3에 접근하려면 “나 이 버킷에 접근해도 되지?” 하고 허락이 있어야 한다. 그걸 IAM 역할로 설정해줘야 함.
IAM 역할은 이번 자동 배포엔 필요 없음, 하지만 EC2에서 AWS 자원 직접 쓸 땐 고려할 것!
- 정책: AmazonEC2FullAccess, AmazonS3FullAccess, SecretsManagerReadWrite
- 역할 이름: ec2-s3-access-role (예시)
- EC2 콘솔 이동 → EC2 인스턴스 선택
- [작업] → [보안] → [IAM 역할 수정]
- 아까 만든 역할(github-deploy-user) 선택 → 저장
IAM 사용자 vs 역할 차이
구분 | IAM 사용자 | IAM 역할 |
용도 | 사람이 직접 로그인하거나 프로그램(GitHub 등)이 사용할 계정 |
EC2, Lambda 같은 AWS 리소스나, 외부 서비스가 잠깐 빌려쓰는 권한 |
인증 방식 | Access Key & Secret Key 사용 (GitHub Actions 등에서 사용) |
EC2 등 AWS 서비스에 자동으로 붙음 (권한을 위임받아 쓰기 때문에 Access Key 없이도 가능) |
EC2에 붙일 수 있는가? |
❌ 못 붙임 | ✅ 가능 |
상황 | GitHub Actions → EC2 자동 배포 (키가 필요하니까) |
EC2 안의 앱 → S3 접근 (키 없이 안전하게 접근하려고) |
🚨 예시 상황
- EC2에 배포한 애플리케이션이 로그 파일이나 이미지를 S3에 저장하려고 함.
- 애플리케이션 안에서 AWS SDK를 써서 putObject() 같은 걸 호출하는 등.
💡 해결법
- 애플리케이션이 S3에 접근하려면 AWS 인증 정보가 필요함.
- 여기에 IAM 사용자 Access Key를 넣으면 되긴 하는데… ❌ 보안에 매우 취약함.
- 대신! EC2 인스턴스에 역할(Role) 을 붙여서,
- AWS가 자동으로 해당 EC2 인스턴스에 권한을 줌
- 키 파일 없이도 S3Client 같은 걸 쓸 수 있음!
4️⃣ GitHub Actions 스크립트 작성 – EC2에 배포 자동화
EC2에 자동 배포 (CD 단계)
- EC2에 JAR을 자동 다운로드 후 재시작하는 과정은 아직 없다.
- 이건 두 가지 방식 중 택할 수 있다:
- S3에 업로드된 파일을 EC2에서 감지해서 가져오는 방식
- GitHub Actions에서 EC2에 SSH 접속해서 직접 배포하는 방식(선택)
1. GitHub Actions에서 EC2에 SSH 접속해서 배포하는 방식
[GitHub Action]
|
| SSH로 접속
↓
[EC2 서버] ← GitHub가 직접 명령어 실행 (예: java -jar ~)
📦 GitHub Actions → EC2 직접 SSH 접속 → 빌드된 파일 복사 & 실행
⭐ 장점
- 빠르고 단순하다 (셋업 쉬움)
- GitHub에서 PR 머지 → 바로 EC2에 적용됨
- 실시간 로그 보기 쉬움 (nohup, log.txt 등)
⚠️ 단점
- EC2에 SSH 키를 GitHub Secrets에 저장해야 함 (보안주의)
- EC2에 직접 접근하는 것이기 때문에 보안적으로 신중해야 함
- SSH 연결이 실패하면 배포가 중단됨
🧰 많이 쓰는 툴
- appleboy/ssh-action: 가장 널리 쓰이는 GitHub Action
2. EC2에서 S3를 감지하거나 주기적으로 pull해서 배포하는 방식
[GitHub Action]
|
| S3에 빌드된 zip 업로드
↓
[S3 Bucket]
↑
| 서버가 주기적/수동으로 deploy.sh 실행
[EC2 서버] → zip 다운로드 → 실행
📦 GitHub Actions → S3 업로드 → EC2에서 감지 또는 크론탭으로 pull
⭐ 장점
✅ 오토스케일링/동적 IP 대응 가능
- EC2 인스턴스는 껐다 켜면 IP가 바뀌어.
- SSH 방식은 GitHub에서 EC2의 고정 IP를 알아야 접속할 수 있어.
- 그런데 IP가 바뀌면 GitHub Actions에서 연결 불가 ❌
✅ 반대로 Pull 방식은 EC2가 S3를 보고 스스로 가져오기 때문에,
- IP가 바뀌든,
- 인스턴스가 새로 생기든 (오토스케일링),
- 전혀 상관 없음
⚠️ 단점
- 감지 로직이나 크론탭 등을 EC2에서 설정해야 함 (조금 복잡)
- GitHub Actions만으로는 배포가 완료되지 않음 (EC2 설정 따로 필요)
EC2에 배포 스크립트 만들기
📌 목표
- EC2 인스턴스가 GitHub Actions가 올린 파일을 S3에서 받아서 배포하는 쉘 스크립트 작성
- 보통 다음 경로에 저장함: /home/ec2-user/deploy/deploy.sh
1. EC2에 SSH 접속
ssh ec2-user@<EC2_PUBLIC_IP>
2. 배포 스크립트 작성
vim ~/app/deploy/deploy.sh
✏️ deploy.sh
#!/bin/bash
# -----------------------
# 환경 설정
# -----------------------
APP_NAME=study-app
JAR_NAME=app.jar
DEPLOY_PATH=/home/ubuntu/app/deploy
LOG_PATH=/home/ubuntu/app/logs
S3_BUCKET=!!!!자신의 버킷명으로 바꿔주세요!!!!
S3_KEY=app.jar
echo "⬇️ S3에서 최신 JAR 다운로드"
aws s3 cp s3://$S3_BUCKET/$S3_KEY $DEPLOY_PATH/$JAR_NAME
if [ $? -ne 0 ]; then
echo "❗ JAR 다운로드 실패 - S3에 파일이 없습니다: $S3_BUCKET/$S3_KEY"
exit 1
fi
if [ -f "$DEPLOY_PATH/$JAR_NAME" ]; then
echo "✅ JAR 다운로드 성공: $JAR_NAME"
else
echo "❗ JAR 파일이 존재하지 않습니다 (경로: $DEPLOY_PATH/$JAR_NAME)"
exit 1
fi
echo "🛑 기존 프로세스 종료 (있다면)"
PID=$(pgrep -f $JAR_NAME)
if [ -n "$PID" ]; then
kill -9 $PID
echo "✅ 프로세스 종료 완료 (PID: $PID)"
else
echo "ℹ️ 종료할 프로세스가 없습니다"
fi
echo "🚀 새 버전 실행"
nohup java -jar $DEPLOY_PATH/$JAR_NAME > $LOG_PATH/app.log 2>&1 &
NEW_PID=$(pgrep -f $DEPLOY_PATH/$JAR_NAME | head -n 1)
if [ -n "$NEW_PID" ]; then
echo "✅ 새 버전 실행 완료 (PID: $NEW_PID)"
else
echo "❗ 실행 실패 - 프로세스가 시작되지 않았습니다"
exit 1
fi
3. 실행 권한 주기
chmod +x ~/app/deploy/deploy.sh
4. AWS CLI 설치하기
sudo apt update
sudo apt install awscli -y
🚨 에러 상황
- EC2 인스턴스에서 awscli를 설치하려고 했는데, apt로 설치할 수 없다는 에러가 떴다.
- 이건 EC2에 따라 기본 저장소에서 AWS CLI를 못 찾는 경우가 있어서 그렇다.
- 그래서 수동 설치 방식으로 AWS CLI를 설치해야 함!
✅ AWS CLI v2 수동 설치 (Ubuntu 기준)
1. 먼저 EC2에 접속한 상태에서 최신 버전 다운로드
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
2. 압축 해제
unzip awscliv2.zip
만약 unzip 명령어가 없다고 하면 먼저 설치
sudo apt update
sudo apt install unzip -y
sudo ./aws/install
aws --version
5. 이렇게 나오면 성공
6. 실행해보기
sh ~/app/deploy/deploy.sh
🧠 현재 상태 요약
항목 | 상태 |
Spring Boot 빌드 자동화 | ✅ 완료 |
JAR S3 업로드 | ✅ 완료 |
EC2 수동 배포 스크립트 | ✅ 완료 |
배포 스크립트 검증 | ✅ 성공 |
Secrets Manager 적용 | 🟡 준비 중 |
CodeDeploy 연동 | 🟡 준비 중 |
완전 자동화 | 🔜 곧 가능! |
🔐 AWS Secrets Manager 적용
1. AWS 콘솔 접속 → Secrets Manager로 이동
2. "새 보안 암호 저장" 클릭
키 값 예시:
{
"spring.datasource.url": "jdbc:mysql://localhost:3306/devdb",
"spring.datasource.username": "devuser",
"spring.datasource.password": "devpass"
}
3. 비밀 키-값 추가
4. 보안 암호 이름 정하기
- 나머지 설정은 기본값으로 두고 저장
5. Gradle 의존성 추가
// AWS secret manager (Spring Cloud AWS)
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-secrets-manager'
6. AWS CLI 설치 및 설정
aws configure
- Access Key ID: 기존에 발급했던 키
- Secret Access Key: 기존에 발급했던 키
- 리전: ap-northeast-2
- 출력 형식: json
7. Java 코드 작성 및 테스트
@Service
public class SecretsManagerService {
public static void main(String[] args) {
getSecret();
}
private final SecretsManagerClient secretsClient;
private final ObjectMapper objectMapper;
public SecretsManagerService() {
this.secretsClient = SecretsManagerClient.builder()
.region(Region.AP_NORTHEAST_2) // 서울 리전
.build();
this.objectMapper = new ObjectMapper();
}
public static void getSecret() {
String secretName = "prod/switching";
Region region = Region.of("ap-northeast-2");
// Create a Secrets Manager client
SecretsManagerClient client = SecretsManagerClient.builder()
.region(region)
.build();
GetSecretValueRequest getSecretValueRequest = GetSecretValueRequest.builder()
.secretId(secretName)
.build();
GetSecretValueResponse getSecretValueResponse;
try {
getSecretValueResponse = client.getSecretValue(getSecretValueRequest);
} catch (Exception e) {
// For a list of exceptions thrown, see
// https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
throw e;
}
String secret = getSecretValueResponse.secretString();
// Your code goes here.
System.out.println(secret);
}
}
- 위 코드를 통해 Secret 값을 정상적으로 받아오는 데 성공!
8. yml 파일 수정 🌟
spring:
config:
import: aws-secretsmanager:secretswitching_${spring.profiles.active}
profiles:
active: dev
이 설정만 있으면, secretswitching_dev 라는 이름의 secret을 AWS Secrets Manager에서 자동으로 불러오고,
그 안의 key-value(JSON)가 application.yml처럼 속성으로 적용돼.
yml 파일 전체 예시
## applicaton-prod.yml
spring:
config:
activate:
on-profile: prod
#이름의 secret을 AWS Secrets Manager에서 자동으로 불러옴
import: aws-secretsmanager:${spring.profiles.active}/switching
datasource:
url: ${spring.datasource.url}
username: ${spring.datasource.username}
password: ${spring.datasource.password}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
# show_sql: true
format_sql: true
logging.level:
org.hibernate.SQL: debug
# org.hibernate.type: trace
custom:
secret:
name: prod/switching
server:
port: 9090
# application.yml
spring:
application:
name: study-matching-site
profiles:
active: prod
jwt:
secret: ${spring.jwt.secret}
springdoc:
swagger-ui:
path: /swagger-ui
api-docs:
path: /v3/api-docs
server:
port: 8080 # 기본 포트 (환경별로 덮어쓰기 가능)
9. 로컬에서 테스트
Secrets Manager에 접근하려면 AWS 자격증명이 필요함.(로컬은 이미 6번에서 했음)
aws configure
# AWS Access Key ID [None]: YOUR_KEY
# AWS Secret Access Key [None]: YOUR_SECRET
# Default region name [None]: ap-northeast-2
- 스프링 애플리케이션 로그에서 다음 메시지가 보이면 성공
10. EC2 or ECS or Lambda 같은 AWS 환경
AWS 콘솔 → IAM → 역할(Roles) → 역할 생성(Create role)
신뢰할 수 있는 엔터티 선택: EC2 선택
권한 정책 추가에서 아래 정책을 선택
- 역할 이름 예시: EC2SecretsManagerRole
- 역할 생성 완료
'[배포]' 카테고리의 다른 글
[AWS] EC2에 MySQL 설치 및 접속 (0) | 2025.04.05 |
---|---|
[AWS] 🔐 보안 그룹 설정 (0) | 2025.04.05 |
[GitHub Actions] GitHub Actions 명령어 (0) | 2025.03.14 |
[배포] 배포 방식 (0) | 2025.02.04 |
[DevOps] GitHub Actions (0) | 2025.01.18 |