이번 프로젝트에서는 GitHub Actions와 AWS를 이용해 배포 과정을 자동화했다.
빌드와 배포는 GitHub Actions로 연결하고, 결과물은 S3로 관리했으며, 실제 배포는 Elastic Beanstalk의 롤링 배포 방식으로 구성했다.
dev 브랜치에서 각자 기능 브랜치를 생성해 작업하고, 기능 개발이 끝나면 dev 브랜치로 PR을 올렸다.
이후 일정 단위로 dev 브랜치를 main 브랜치에 병합해 배포를 진행했으며,
main 브랜치 PR 생성과 승인, 병합은 조직 소유자 및 깃 관리자 팀원이 담당했다.
main 브랜치에 병합되면 자동으로 빌드와 배포가 실행되도록 설정했다.
최종 반영 브랜치를 배포 기준으로 두면서, 배포 시점을 별도로 관리하지 않아도 되고 브랜치 전략과 배포 기준을 단순하게 가져갈 수 있었다.
빌드 결과물은 바로 배포하지 않고, S3에 버전별로 저장한 뒤 그 결과물을 기준으로 배포하도록 했다.
배포 결과물을 별도로 관리할 필요가 있다고 봤고, 문제가 생겼을 때 이전 버전으로 되돌릴 수 있어야 한다는 점도 고려했다.
Elastic Beanstalk 환경은 단일 환경에서 추가 배치를 사용한 롤링(Rolling with additional batch) 배포 정책으로 구성했다.
이 방식은 기존 인스턴스를 한 번에 교체하지 않고, 새 인스턴스를 임시로 추가한 뒤 헬스 체크를 통과하면 기존 인스턴스를 순차적으로 교체하는 구조이다.
덕분에 배포 중에도 서비스 인스턴스가 완전히 비지 않도록 유지할 수 있었고, 다운타임을 최소화할 수 있었다.
GitHub Actions를 이용한 배포 흐름은 다음과 같이 구성했다.
./gradlew build -x test -Dspring.profiles.active=prod 명령어로 애플리케이션을 빌드한다.prod 프로파일을 적용해 빌드 시간을 줄인다.aws s3 cp 명령어로 빌드된 JAR 파일을 S3 버킷에 업로드한다.GitHub Run ID와 KST 시간을 포함해 고유한 버전 라벨을 생성한다.create-application-version 명령어를 실행해 새로운 버전 정보를 등록한다.update-environment 명령어를 통해 Beanstalk 환경에 새 버전 배포를 요청한다.SPRING_PROFILES_ACTIVE=prod 값을 함께 전달한다.aws elasticbeanstalk wait 명령어로 환경 업데이트 완료를 기다린다.curl을 통해 /actuator/health 엔드포인트에 요청을 보내, 응답 코드가 200인지 확인하여 정상 배포 여부를 판단한다.name: Build and Deploy to Elastic Beanstalk
on:
push:
branches:
- main # main 브랜치에 푸시될 때 실행
workflow_dispatch: # 수동 실행도 가능
jobs:
deploy:
runs-on: ubuntu-latest # 깃허브 액션 스크립트가 작동될 OS 환경 지정
steps:
# 1. Java 17 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin' # OpenJDK 배포판
java-version: '17' # Java 17 설치
# 2. 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v3
# 3. Gradle로 JAR 파일 빌드
- name: Build JAR file
run: |
chmod +x ./gradlew
./gradlew build -x test -Dspring.profiles.active=prod
# 4. AWS CLI 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2
# 5. JAR 파일을 S3에 업로드하고 Elastic Beanstalk에 배포
- name: Deploy to Elastic Beanstalk
run: |
# github.run_id와 빌드 완료 시간(KST) 결합한 버전 라벨 생성
VERSION_LABEL="v-${{ github.run_id }}-$(TZ="Asia/Seoul" date +'%Y%m%d-%H%M%S')" # 예: v-12345-20250205-143200
# S3에 JAR 파일 업로드
aws s3 cp build/libs/my-application-0.0.1-SNAPSHOT.jar s3://my-app-bucket/deployments/my-application-${VERSION_LABEL}.jar
# Elastic Beanstalk 애플리케이션 버전 생성
aws elasticbeanstalk create-application-version \
--application-name "my-application" \
--version-label "${VERSION_LABEL}" \
--source-bundle S3Bucket="my-app-bucket",S3Key="deployments/my-application-${VERSION_LABEL}.jar"
# Elastic Beanstalk 환경 업데이트
aws elasticbeanstalk update-environment \
--environment-name "my-app-env" \
--version-label "${VERSION_LABEL}" \
--option-settings Namespace=aws:elasticbeanstalk:application:environment,OptionName=SPRING_PROFILES_ACTIVE,Value=prod
# 6. Elastic Beanstalk 배포 후 대기
- name: Wait for Elastic Beanstalk deployment
run: |
echo "Waiting for Elastic Beanstalk deployment to complete..."
aws elasticbeanstalk wait environment-updated --environment-name "my-app-env"
# 7. 배포 후 애플리케이션 헬스 체크
- name: Check application health
run: |
echo "Checking application health..."
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://my-app-env.ap-northeast-2.elasticbeanstalk.com/actuator/health)
if [ "$HTTP_STATUS" -eq 200 ]; then
echo "Application is healthy! (HTTP 200)"
else
echo "Application health check failed! (HTTP $HTTP_STATUS)"
exit 1
fi
# 8. 빌드 완료 시간 출력
- name: Get build completion time
run: |
BUILD_TIME=$(TZ="Asia/Seoul" date +"%Y-%m-%d %H:%M:%S")
echo "Build completed at $BUILD_TIME"
shell: bash
배포 과정에서 가장 자주 겪은 문제는 환경 변수 누락이었다.
코드에서는 참조하고 있지만 Elastic Beanstalk 환경에 등록되지 않은 값이 있으면 로그인 실패나 애플리케이션 오류가 발생했다.
초기에는 이런 누락이 반복되어, 이후에는 체크리스트를 만들어 점검했다.
Client Secret, JWT Secret, DB 계정 정보처럼 설정값은 설정 파일에 직접 넣지 않고, 환경 변수로 분리해 관리했다.
배포 경험이 거의 없는 상태에서 환경을 구성하다 보니 초반에는 시행착오도 적지 않았지만, 한 번 파이프라인을 정리한 뒤에는 main 브랜치 병합만으로 배포가 이어지는 흐름을 만들 수 있었다.