
지난 몇 개월간, 여러 사이드 프로젝트의 서버 개발자로 참여하며 대부분의 서버의 배포파이프라인을 글의 제목처럼 Elastic Beanstalk + Github Actions 방식으로 구축했었습니다. ( 자취방 리뷰 서비스, 이룸 / AI 뉴스 리더, 브리핑 많이 구경해주세요!!!🍀) 이번 글에서는 Elastic Beanstalk 환경 설정보다는 스크립트 위주로 작성할 예정입니다. Elastic Beanstalk 관련해서는 다음에 추가적으로 글을 작성하겠습니다.
이룸이라는 프로젝트 팀에서는 아래와 같은 브랜치 전략을 갖고 있었습니다.
또한 개발 서버와 운영 서버를 분리하여 관리하고 있었습니다.
dev 프로파일로 배포가 되어야 함.prod 프로파일로 배포가 되어야 함


Elastic Beanstalk + Github Actions로 배포하기 위한 주요 구성요소는 다음과 같습니다.
00-makeFiles.configdeploy.ymlProcfilenginx.conf운영 서버를 기준으로 설명하겠습니다. 위에서 설명했듯이 운영서버는 다음과 같은 요구사항을 갖고 있었습니다.
운영 서버 (e-room.app)
prod 프로파일로 배포가 되어야 함deploy-prod.yml
name: E-room Prod CI/CD # 워크플로우 이름 설정
# 워크플로우 트리거 조건
on:
pull_request: # PR 이벤트에 대한 트리거 설정
branches:
- master # master 브랜치에 대한 PR만 대상
types:
- closed # PR이 닫힐 때만 워크플로우가 실행됨
workflow_dispatch: # 수동으로 워크플로우 실행 가능
jobs: # 워크플로우에서 실행할 작업들 정의
build: # build라는 작업 이름
runs-on: ubuntu-latest # 작업이 실행될 OS 버전 설정
if: github.event.pull_request.merged == true # PR이 병합된 경우에만 작업 실행
steps: # build 작업 내의 실행 단계들
- name: Checkout
uses: actions/checkout@v2 # 현재 리포지토리의 코드 체크아웃
- name: Set up JDK 11
uses: actions/setup-java@v3 # 자바 11 설치
with:
java-version: 11
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew # Gradle 래퍼에 실행 권한 부여
shell: bash
- name: Build with Gradle
run: ./gradlew clean build -Dspring.profiles.active=prod -x test # Gradle로 빌드 수행. 테스트는 제외
shell: bash
- name: Get current time
uses: 1466587594/get-current-time@v2 # 현재 시간 정보 획득
id: current-time
with:
format: YYYY-MM-DDTHH-mm-ss
utcOffset: "+09:00"
- name: Show Current Time
run: echo "CurrentTime=$" # 획득한 현재 시간 출력
shell: bash
- name: Generate deployment package
run: | # 배포 패키지 생성
mkdir -p deploy
cp build/libs/*.jar deploy/application.jar
cp Procfile deploy/Procfile
cp -r .ebextensions-prod deploy/.ebextensions
cp -r .platform deploy/.platform
cd deploy && zip -r deploy.zip .
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v20 # AWS Elastic Beanstalk에 배포
with:
aws_access_key: ${{ secrets.AWS_ACTION_ACCESS_KEY_ID }} # AWS 접근 키 (GitHub Secrets에서 가져옴)
aws_secret_key: ${{ secrets.AWS_ACTION_SECRET_ACCESS_KEY }} # AWS 비밀 키 (GitHub Secrets에서 가져옴)
application_name: E-room Prod
environment_name: E-roomProd-env
version_label: github-action-${{ steps.current-time.outputs.formattedTime }} # 배포 버전 라벨 설정
region: ap-northeast-2
deployment_package: deploy/deploy.zip # 배포할 패키지 경로
wait_for_environment_recovery: 60 # 배포 후 환경 복구 대기 시간
E-room Prod CI/CD 라는 workflow를 정의하고 이 workflow는 build 라는 job을 수행할 것입니다.build job은 위와 같은 여러 step들을 가지고 있습니다. 각 step에 대한 설명은 주석으로 적어두었습니다.Q. master 브랜치로 병합되었을 때, 배포가 되도록 어떻게 구성할 수 있을까?
workflow의 트리거 조건을 적절히 작성했습니다.
# 워크플로우 트리거 조건
on:
pull_request: # PR 이벤트에 대한 트리거 설정
branches:
- master # master 브랜치에 대한 PR만 대상
types:
- closed # PR이 닫힐 때만 워크플로우가 실행됨
workflow_dispatch: # 수동으로 워크플로우 실행 가능
jobs: # 워크플로우에서 실행할 작업들 정의
build: # build라는 작업 이름
runs-on: ubuntu-latest # 작업이 실행될 OS 버전 설정
if: github.event.pull_request.merged == true # PR이 병합된 경우에만 작업 실행
Q. 무중단 배포를 어떻게 구성할 수 있을까?
이는 Elastic Beanstalk 환경에서 설정하며 나중에 해당 글을 작성할 때 설명하겠습니다.
Q. prod 와 같은 특정 프로파일로 어떻게 배포할 수 있을까?
주목해야할 부분은 Generate deployment package step입니다.
- name: Generate deployment package
run: | # 배포 패키지 생성
mkdir -p deploy
cp build/libs/*.jar deploy/application.jar
cp Procfile deploy/Procfile
cp -r .ebextensions-prod deploy/.ebextensions
cp -r .platform deploy/.platform
cd deploy && zip -r deploy.zip .
이 step에서는 Elastic Beanstalk에 배포하기 위한 형태의 패키지(deploy 폴더)를 만듭니다.
여기서는 우리의 배포에 필요한 jar파일, Procfile, .ebextensions-prod 폴더, .platform 폴더를 deploy 폴더내로 복사한 후 압축합니다.
Elastic Beanstalk 환경에서는 Procfile 을 보고 시작 스크립트를 실행하는데 시작 스크립트는 .ebextension-prod/00-makeFiles.config 에 정의되어 있습니다. 각 파일을 살펴봅시다.
Procfile
web: appstart
간단히 appstart 스크립트를 실행시키라는 한 줄만 작성되어 있습니다. appstart 스크립트는 00-makeFiles.config 파일에서 정의합니다.
00-makeFiles.config여기서 appstart 스크립트 파일을 정의했습니다.
files: # Elastic Beanstalk에 필요한 파일들을 생성하거나 수정하는 섹션
"/sbin/appstart" : # /sbin/appstart 라는 파일을 생성하거나 수정
mode: "000755" # 파일의 권한 설정. 여기서는 실행, 읽기 및 쓰기 권한을 소유자에게 부여
owner: webapp # 파일의 소유자를 webapp 사용자로 설정
group: webapp # 파일의 그룹을 webapp 그룹으로 설정
content: | # 아래에 나열된 내용으로 파일을 채운다.
JAR_PATH=/var/app/current/application.jar # JAR_PATH 변수에 애플리케이션 JAR 파일의 경로를 저장
# run app
killall java # 현재 실행 중인 모든 java 프로세스를 종료
java -Dfile.encoding=UTF-8 -Dspring.profiles.active=prod -jar $JAR_PATH # UTF-8 인코딩과 prod 프로파일로 애플리케이션 JAR 파일 실행
prod 와 같은 특정 프로파일로 배포하는 핵심 커맨드는 여기에 있습니다.-Dspring.profiles.active=prod jar를 실행시킬때 프로파일을 설정해주었습니다.요약하자면 다음과 같습니다.
prod 프로파일로 배포가 되어야 함 → appstart 스크립트에서 jar를 실행할때 프로파일을 포함합니다.지금까지 CI/CD 배포 스크립트를 살펴보았습니다. 그런데 프로젝트를 진행하다보면 DB 접속 계정이나 비밀 키들은 환경변수로 두어 개발하게 됩니다. 따라서 jar를 실행할 때 환경변수가 없으면 에러가 나게 됩니다.
Elastic Beanstalk으로 배포할 때는 Github Actions에서 Elastic Beanstalk으로 접근할 수 있도록 IAM 계정을 생성해주어야 합니다. IAM 계정은 생성해두었다고 가정하고 Beanstalk Deploy step를 살펴봅시다.
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v20 # AWS Elastic Beanstalk에 배포
with:
aws_access_key: ${{ secrets.AWS_ACTION_ACCESS_KEY_ID }} # AWS 접근 키 (GitHub Secrets에서 가져옴)
aws_secret_key: ${{ secrets.AWS_ACTION_SECRET_ACCESS_KEY }} # AWS 비밀 키 (GitHub Secrets에서 가져옴)
application_name: E-room Prod
environment_name: E-roomProd-env
version_label: github-action-${{ steps.current-time.outputs.formattedTime }} # 배포 버전 라벨 설정
region: ap-northeast-2
deployment_package: deploy/deploy.zip # 배포할 패키지 경로
wait_for_environment_recovery: 60 # 배포 후 환경 복구 대기 시간
einaregilsson/beanstalk-deploy@v20를 사용해 Elastic Beanstalk에 배포합니다.
IAM의 aws_access_key와 aws_secret_key는 Github Secrets에서 설정하며 위치는 사진과 같습니다.
Settings > Secretes and variables > New repository secret

다음으로 애플리케이션에서 필요한 환경변수(스프링부트 application.yml에서 사용하는 환경 변수)는 어디에 설정하는지 알아봅시다.
Elastic Beanstalk > 구성 > 업데이트, 모니터링 및 로깅

업데이트 모니터링 및 로깅을 편집하여 Environment properties 에 환경변수를 추가해줄 수 있습니다.
또 다른 방법도 존재합니다. Elastic Beanstalk에 환경변수를 추가하지 않고, Github Actions에 step을 추가하는 방법입니다. 아래 코드는 다른 프로젝트에서 사용한 방법입니다.
- name: Set Environment
uses: microsoft/variable-substitution@v1
with:
files: ./Winey-Common/src/main/resources/application-common.yml
env:
oauth.kakao.base-url: ${{ secrets.KAKAO_BASE_URL }}
oauth.kakao.client-id: ${{ secrets.KAKAO_CLIENT }}
oauth.kakao.client-secret: ${{ secrets.KAKAO_SECRET }}
oauth.kakao.redirect-url: ${{ secrets.KAKAO_REDIRECT }}
oauth.kakao.app-id: ${{ secrets.KAKAO_APP_ID }}
oauth.kakao.admin-key: ${{ secrets.KAKAO_ADMIN_KEY }}
jwt.secret: ${{ secrets.JWT_SECRET_KEY }}
jwt.refresh: ${{ secrets.JWT_REFRESH_KEY }}
cool-sms.api-key: ${{ secrets.COOL_SMS_API_KEY }}
cool-sms.api-secret: ${{ secrets.COOL_SMS_API_SECRET }}
cool-sms.from-number: ${{ secrets.COOL_SMS_FROM_NUMBER }}
cool-sms.domain: ${{ secrets.COOL_SMS_DOMAIN }}
aws.s3.bucket: ${{ secrets.AWS_BUCKET }}
aws.s3.base-url: ${{ secrets.AWS_BASE_URL }}
aws.s3.access-key: ${{ secrets.AWS_S3_ACCESS_KEY }}
aws.s3.secret-key: ${{ secrets.AWS_S3_SECRET_KEY }}
요약하자면 Elastic Beanstalk 환경에 환경변수를 추가하는 방법과 build job에 step을 추가하는 방법이 있습니다.
그러면 이제 개발서버의 배포 요구사항을 다시 살펴봅시다.
지금까지 살펴본 내용으로 충분히 해결할 수 있는 내용입니다.
Q. develop 브랜치가 push되었을 때 배포가 되도록 어떻게 할까?
workflow의 트리거를 develop 브랜치가 push 되었을 때 배포하도록 작성합니다.
on:
push:
branches:
- develop
workflow_dispatch:
Q. 중단 배포를 어떻게 구성할 수 있을까?
이는 Elastic Beanstalk 환경에서 설정하며 나중에 해당 글을 작성할 때 설명하겠습니다.
Q. dev 와 같은 특정 프로파일로 어떻게 배포할 수 있을까?
appstart 스크립트에서 jar를 실행할때 프로파일을 포함합니다.
files:
"/sbin/appstart" :
mode: "000755"
owner: webapp
group: webapp
content: |
JAR_PATH=/var/app/current/application.jar
# run app
killall java
java -Dfile.encoding=UTF-8 -Dspring.profiles.active=dev -jar $JAR_PATH
💡 CI/CD를 위한 SpringBoot 프로젝트 구조 섹션에서 적어두었지만 설명하지 않은 .platform/nginx.conf 파일에 대해 알아봅시다.

Elastic Beanstalk는 nginx를 리버스 프록시로 사용하여 애플리케이션을 포트 80의 ELB 로드 밸런서에 매핑합니다.
기본적으로 Elastic Beanstalk은 요청을 포트 5000의 애플리케이션에 전달하도록 nginx 프록시를 구성합니다. 그래서 PORT 환경 변수를 기본 애플리케이션이 수신 대기하는 포트로 설정하여 기본 포트를 재정의할 수 있습니다. (스프링부트의 경우 보통 8080 재정의)

nginx의 구성은 nginx.conf 파일을 통해 재정의할 수도 있습니다.
# Nginx process settings
user nginx; # Nginx 프로세스를 'nginx' 사용자로 실행
error_log /var/log/nginx/error.log warn; # 에러 로그의 위치와 경고 수준을 지정
pid /var/run/nginx.pid; # Nginx의 PID 파일 위치
worker_processes auto; # 자동으로 워커 프로세스 수를 결정
worker_rlimit_nofile 33282; # 각 워커 프로세스가 열 수 있는 파일의 최대 수
# Events module settings
events {
use epoll; # 사용할 이벤트 모델 (Linux에서의 high-performance 방법)
worker_connections 1024; # 한 워커 당 연결 수 제한
multi_accept on; # 한 번에 여러 연결을 수락
}
# HTTP module settings
http {
include /etc/nginx/mime.types; # MIME 타입 설정 포함
default_type application/octet-stream; # 기본 MIME 타입 설정
# 로그 포맷 정의
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
include conf.d/*.conf; # 추가적인 설정 포함
# Websocket 지원을 위한 설정
map $http_upgrade $connection_upgrade {
default "upgrade";
}
# Spring Boot 애플리케이션으로의 프록시 설정
upstream springboot {
server 127.0.0.1:8080; # 로컬의 8080 포트로 연결
keepalive 1024; # keep-alive 연결 수
}
# 기본 서버 설정
server {
listen 80 default_server; # IPv4에서 80 포트 리스닝
listen [::]:80 default_server; # IPv6에서 80 포트 리스닝
# 모든 요청에 대한 설정
location / {
proxy_pass <http://springboot>; # 요청을 springboot 업스트림에 전달
proxy_http_version 1.1; # 프록시 요청의 HTTP 버전
proxy_set_header Connection $connection_upgrade; # 헤더 설정
proxy_set_header Upgrade $http_upgrade; # WebSocket 헤더 설정
proxy_set_header Host $host; # 원래 요청의 Host 헤더 유지
proxy_set_header X-Real-IP $remote_addr; # 실제 클라이언트 IP 전달
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 프록시된 IP를 전달
}
access_log /var/log/nginx/access.log main; # 접근 로그 위치 및 포맷
# 타임아웃 및 압축 관련 설정
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip off; # gzip 압축 비활성화
gzip_comp_level 4; # gzip 압축 레벨 (사용되지 않음, gzip이 off)
# Elastic Beanstalk의 health check 설정 포함
include conf.d/elasticbeanstalk/healthd.conf;
}
}
사실 배포가 잘 시작했는지, 잘 완료되었는지 여부는 Github의 Actions 탭에 들어가면 확인할 수 있지만 이를 슬랙 알림으로 받으면 편할 것 같아서 설정했습니다.
우선 프로젝트 워크스페이스에 Github 앱이 설치되어있어야합니다.

다음으로, 배포 알림을 받을 채널을 생성합니다.

마지막으로, 해당 채널에 메시지를 전송해서 특정 workflow를 구독하도록 만듭니다.
/github subscribe <organization>/<repository> workflows:{name: "<workflow 이름>"}

확실히 CI/CD 파이프라인을 한 번 구축해두면 정말 편리한 것 같습니다. 현재 재직중인 회사에서는 프로젝트 초기 단계라 아직 구축해두지 않아 일일이 서버에 접속해서 소스를 가져오고 커맨드를 실행하는 것을 반복하고 있는데 너무 번거롭습니다..😅
이번에 작성한 글은 기억에 의존해 작성하였기때문에 틀린 부분이나 최신화되지 않은 정보가 있을 수 있습니다. 틀린 부분이 있다면 계속 수정하겠습니다!
동아리 교육 자료 만들때 참고해도 될 정도로 좋은 글 같아요! 출처 밝히고 참고해도 될까욥