본문 바로가기

[배포]

[배포] 🚀 GitHub Actions + EC2 + S3 + Secrets Manager + CodeDeploy를 통한 무중단 배포 자동화

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 인스턴스에서는 그걸 받아서 실행하게 할 수 있다.
  • → 무중단 배포 스크립트에서 이 방식이 깔끔하고 보안적으로도 좋다.

📦 준비물

  1. S3 버킷 생성 (한 번만)
  2. IAM 사용자의 Access Key / Secret Key
  3. GitHub Secrets에 등록
  4. Actions에서 aws s3 cp 명령어로 업로드

🧱 S3 버킷 생성 (한 번만 하면 됨)

  1. S3 콘솔 접속
  2. “버킷 만들기” 클릭
  3. 이름: your-app-deploy-bucket (원하는 이름)
  4. 리전은 EC2와 동일한 리전 선택 (예: ap-northeast-2)
  5. 나머지는 기본 설정 → “버킷 만들기” 클릭

🛠️ 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을 자동 다운로드 후 재시작하는 과정은 아직 없다.
  • 이건 두 가지 방식 중 택할 수 있다:
    1. S3에 업로드된 파일을 EC2에서 감지해서 가져오는 방식
    2. 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
3. 설치
sudo ./aws/install
4. 설치 확인
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