Project

🚀 진짜 작동하는 GitHub Actions + EC2 CI/CD 배포기

Geisha 2025. 4. 8. 21:06

 

아기자기한 EC2 프리미어와 내 배포 자동화 여정

 


✨ 글을 쓰게 된 계기

 

이번에 개인 프로젝트를 진행하면서,

“GitHub Actions로 EC2에 자동 배포하는 CI/CD 파이프라인”을 직접 구성했다.

 

단순히 배포만이 아니라,

 

  • 테스트 돌리고
  • Jasypt로 비밀번호를 암호화해서 보안도 지키고
  • EC2에 jar 파일을 전송하고
  • 앱을 껐다 켜는 것까지 한 번에 처리

 

하는 걸 목표로 했다.

그 과정에서 정말 다양한 시행착오를 겪었고,

이 글은 그 실전기록이다.

 


🎯 목표

 

  • 코드 푸시 → 테스트 + 빌드 → EC2에 jar 전송 → 앱 재시작
  • DB 접속 정보는 application-prod.yml에 Jasypt로 암호화
  • GitHub Secrets를 통해 민감 정보 관리

⚙️ 내가 만든 최종 

deploy.yml

name: Deploy to EC2

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for Gradle
        run: chmod +x ./gradlew

      - name: Run tests
        run: ./gradlew test -Dspring.profiles.active=test

      - name: Build project (prod profile with jasypt)
        run: ./gradlew build \
          -Dspring.profiles.active=prod \
          -Djasypt.encryptor.password=${{ secrets.JASYPT_SECRET }}

      - name: Write PEM file
        run: |
          echo "${{ secrets.EC2_KEY }}" > private_key.pem
          chmod 600 private_key.pem

      - name: SCP .jar to EC2
        run: |
          scp -i private_key.pem -o StrictHostKeyChecking=no \
            ./build/libs/*.jar \
            ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USER }}/app/

      - name: Restart app on EC2
        run: |
          ssh -i private_key.pem -o StrictHostKeyChecking=no \
            ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << EOF
              pkill -f 'java -jar' || true
              nohup java -jar /home/${{ secrets.EC2_USER }}/app/*.jar \
              --spring.profiles.active=prod \
              --jasypt.encryptor.password=${{ secrets.JASYPT_SECRET }} \
              > /home/${{ secrets.EC2_USER }}/app/log.txt 2>&1 &
          EOF

 

 


🔍 시행착오 & 배운 점들

 

 

1.  Secrets에 PEM 키 내용을 복붙하는 게 처음엔 너무 낯설었다

 

보통 .pem은 파일로 갖고 있다가 terminal 열어서 ssh -i ~~ 하는데?

근데 GitHub Actions에서는 텍스트 형태로 저장해야 하니까
Secrets에 -----BEGIN RSA PRIVATE KEY-----부터 -----END ...까지 전부 복붙해서 저장해야 했다.

 

👉 해결:

  • echo "$EC2_KEY" > private_key.pem
  • chmod 600 private_key.pem
  • 으로 Actions 안에서 임시로 .pem 파일을 만들어줬다.

 


2.  빌드할 때도 Jasypt 암호화 키가 필요하다는 걸 몰랐다

 

처음엔 --jasypt.encryptor.password=... 옵션은

앱 실행할 때만 필요한 줄 알았는데…

./gradlew build 할 때도 application-prod.yml을 로딩하면서 암호화된 값을 복호화하더라.

 

👉 해결:

./gradlew build -Dspring.profiles.active=prod -Djasypt.encryptor.password=...

 

 


3. Spring Profile을 빌드/실행 때 명확히 지정 안 하면 꼬인다

 

처음에는 그냥 ./gradlew build만 했는데,

application.yml이 기본으로 잡히니까 DB 설정 오류로 빌드 실패함.

 

👉 해결:

 

  • 빌드: -Dspring.profiles.active=prod
  • 실행: --spring.profiles.active=prod

4. EC2에 MySQL을 직접 설치할지, RDS를 쓸지 고민 많았음

 

처음엔 EC2 에 로컬 Mysql 설치하려다가 프리티어 EC2 특성상 1GB 밖에 안되는 메모리에 
너무나도 작고 소중한 아이기 때문에 다른계정으로 RDS를 만들어서 연결해야하나 고민했다.

프리티어는 월 750시간 제한이 있어서 RDS + EC2 동시에 쓰면 금방 초과될줄 알았다.

하지만 이제 RDS 프리티어 따로 EC2 프리티어 따로라고 들음( by. 인프라 고수 )
그래서 결국 RDS로 MySQL을 옮기고 EC2에는 Spring Boot만 돌리게 했음

 

👉 장점: DB 접근 분리, 보안, 확장성

👉 단점: 설정 복잡 (보안그룹 열기, 엔드포인트 연결 등)

 


5. 순환 참조 조심할 것

 

application-test.yml 에

spring:
  profiles:
	active: test


이렇게 돌리니 순환참조가 되버리는것..  이미 test로 들어왔는데 또 다시 test를 찾게되는거
그래서 Build 실패를 여러번 겪음 

 

 


6. deploy.yml 문법 

name: Deploy to EC2

on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout source
        uses: actions/checkout@v3

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for Gradle
        run: chmod +x ./gradlew

      - name: Run tests
        run: ./gradlew test -Dspring.profiles.active=test

      - name: Build project (prod profile with jasypt)
        run: ./gradlew build \
          -Dspring.profiles.active=prod \
          -Djasypt.encryptor.password=${{ secrets.JASYPT_SECRET }}

      - name: Write PEM file
        run: |
          echo "${{ secrets.EC2_KEY }}" > private_key.pem
          chmod 600 private_key.pem

      - name: SCP .jar to EC2
        run: |
          scp -i private_key.pem -o StrictHostKeyChecking=no \
            ./build/libs/*.jar \
            ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USER }}/app/

      - name: Restart app on EC2
        run: |
          ssh -i private_key.pem -o StrictHostKeyChecking=no \
            ${{ secrets.EC2_USER }}@${{ secrets.EC2_HOST }} << EOF
              pkill -f 'java -jar' || true
              nohup java -jar /home/${{ secrets.EC2_USER }}/app/*.jar \
              --spring.profiles.active=prod \
              --jasypt.encryptor.password=${{ secrets.JASYPT_SECRET }} \
              > /home/${{ secrets.EC2_USER }}/app/log.txt 2>&1 &
          EOF

 

여기 보다보면 run: | 이 있고 없는게 있다. 

뭐가 있고 없냐면 |||||||| 이거 말이다. 이게  뭔놈의 역할인지 몰랐던것

이게 여러줄의 commend를 미리 선언하는거란다.

|  <-  이게 없이 여러줄의 commend를 적어버리면 이제 인마가 잘못 해석해버리는것 그거때매 또 commit 2번했다.

 

 

🔐 GitHub Secrets 정리

이름용도

EC2_KEY .pem 키 내용 전체
EC2_USER EC2 사용자 (ex. ubuntu)
EC2_HOST 퍼블릭 IP
JASYPT_SECRET Jasypt 암호화/복호화 키

 

우선은 이렇게 집어넣었다.


✅ 결과

 

지금은 진짜 코드만 푸시하면

→ 테스트, 빌드, 배포, 실행까지

→ 전부 자동으로 굴러간다.

 

로그는 EC2에서 cat /home/ubuntu/app/log.txt로 확인 가능하고,

배포 실패 시에는 GitHub Actions에서 로그 트레이싱도 가능해서 디버깅이 훨씬 쉬워졌다.

 


🧠 정리하며

 

처음엔 하나하나 다 생소하고 어려웠지만,

막히는 지점마다 직접 원인 찾아가면서 내 손으로 문제를 풀어낸 과정이 너무 값졌다.

또한 과거 SSAFY 프로젝트에서 진행했던 Jenkins + Docker 배포보다는 한참 쉬웠다. 

 

지금 이 글을 보고 있는 누군가가

내가 겪었던 어려움을 조금이라도 덜 겪었으면 좋겠다 😊