지난 1편에 이어서 Github Actions에서 CI 후 AWS S3에 jar 파일을 올린 뒤 CodeDeploy로 해당 파일을 EC2에 배포하는 과정이 필요합니다. 이때 GitHub에서 S3, CodeDeploy에 접근 가능하도록 설정하는 과정 + main 브랜치에 push 했을 때 배포하는 파일/스크립트도 작성하는 과정에 대한 내용입니다.
배포하면서 가장 삽질을 많이 했던 부분이 .gitignore로 리포지토리에 올리지 않은 민감정보를 배포 한 애플리케이션에 옮기는 것이었다. 민감정보 유출을 막기 위해 git ignore를 한건데, 실행시에 필요하다니 이를 어떻게 해야하나 구글링도 정말 많이 하고, 이것저것 시도도 많이 했다. 그 결과 얻어냈던 과정이 아래 4번째 목차 "Github Actions Workflow(deploy.yml) 작성"에 최대한 자세하게 설명을 해두었으니 참고하면 될 것 같다.
0. 목차
1. GithubActions 에서 사용할 IAM 사용자 생성
2. appspec.yml 작성
3. 배포 스크립트(stop.sh, start.sh) 작성
4. Github Actions Workflow(deploy.yml) 작성
4-1. application-prod.yml 파일을 Github Secrets에 추가하기
4-2. AWS 민감 키 정보 Github Secrets에 등록하기
5. Github Actions 사용해서 배포하기
1. GithubActions 에서 사용할 IAM 사용자 생성
Github Actions 워크 플로우에서 AWS에 접근하려면 권한이 필요합니다.
그래서 이버에는 IAM 사용자를 생성해 Github Actions에서도 S3와 CodeDeploy에 접근할 수 있게 설정해보겠습니다.
1.1 IAM 사용자 생성하기
"IAM 페이지 -> 사용자 -> 사용자 추가" 의 과정을 거쳐 사용자를 생성합니다.

1.2 IAM 사용자 이름 설정
사용자 이름을 설정합니다. 제가 참고한 블로그에서는 이 과정에 액세스 유형을 설정할 수 있지만, 제가 설정할 때는 업데이트가 된 것인지 별도로 액세스 유형을 설정할 수는 없었습니다. 그래서 따로 설정 해주었는데, 이는 바로 아래 <1.5 IAM 사용자의 액세스 키 생성하기> 항목에서 확인하실 수 있습니다.

1.3 접근이 필요한 권한 추가
권한 옵션은 직접 정책 연결로 설정한 뒤 AWSCodeDeployFullAccess, AmazonS3FullAccess 권한을 검색한 뒤 추가해 줍니다.

1.4 IAM 사용자 이름 설정
IAM 사용자 이름만 설정한 뒤 넘어가면 됩니다.

1.5 IAM 사용자의 액세스 키 생성하기
Github Actions에서도 AWS에 접근하기 위해서는 별도의 액세스 키를 발급받아야 합니다.
"IAM 페이지 -> 사용자 -> 생성한 IAM 사용자 -> 보안 자격 증명 -> 액세스 키 만들기" 를 따라서 액세스 키 만들기를 시작합니다.


1.5.1 액세스 키 모범 사례 및 대안
사용사례는 CLI, 확인에 "위의 권장 사항을 이해했으며 액세스 키 생성을 계속하려고 합니다."에 체크한 뒤 다음으로 넘어갑니다.


1.5.2 발급된 액세스키/비밀 액세스 키 확인
발급된 액세스키/비밀 액세스 키 확인한 뒤 따로 저장해 둡니다.

2. appspec.yml
- CodeDeploy에서 배포를 위해 참조하는 파일
- 프로젝트의 어떤 파일들을 ec2 어떤 경로에 복사할지 설정 가능
- 배포 프로세스 이후에 수행할 스크립트 지정해 자동으로 서버 띄울 수 있음
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app/deploy
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: scripts/stop.sh
timeout: 60
runas: ubuntu
ApplicationStart:
- location: scripts/start.sh
timeout: 60
runas: ubuntu
files 섹션
배포 파일에 대한 설정
files:
- source: /
destination: /home/ubuntu/app/deploy
overwrite: yes
- source: 인스턴스에 복사할 디렉터리 경로
- destination: 인스턴스에서 파일이 복사되는 위치
- overwrite: 복사할 위치에 파일이 있는 경우 대체
permissions 섹션
위의 files에서 복사한 파일에 대한 권한 설정
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
- object: 권한이 지정되는 파일 또는 디렉터리
- pattern (optional): 매칭되는 패턴에만 권한 부여
- owner (optional): object 의 소유자
- group (optional): object 의 그룹 이름
hooks 섹션
배포 이후에 수행할 스크립트 지정. 적절한 hook을 찾아 실행할 스크립트 지정하면 된다.
hooks:
AfterInstall:
- location: scripts/stop.sh
timeout: 60
runas: ubuntu
ApplicationStart:
- location: scripts/start.sh
timeout: 60
runas: ubuntu
위 코드에서는 파일 설치 -> AfterInstall에서 기존에 실행중이던 애플리케이션 종료 -> ApplicationStart에서 새로운 애플리케이션 실행 하는 과정이다.
- location: hooks 에서 실행할 스크립트 위치
- timeout (optional): 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨
- runas (optional): 스크립트를 실행하는 사용자
3. 배포 스크립트(stop.sh, start.sh)
파일 위치
애플리케이션에서 /src와 같은 계층에 script 패키지를 생성 후 stop.sh, start.sh 쉘 스크립트 파일을 생성합니다. 각 파일의 내용은 아래와 같습니다. stop.sh는 기존에 실행중이던 애플리케이션을 중단 시키는 쉘 스크립트이고, start.sh는 새로 배포한 애플리케이션을 시작할 수 있게 해주는 쉘 스크립트입니다.

stop.sh
#!/usr/bin/env bash
PROJECT_ROOT="/home/ubuntu/app/deploy"
JAR_FILE="accountbook-0.0.1-SNAPSHOT.jar"
DEPLOY_LOG="$PROJECT_ROOT/deploy-stop.log"
TIME_NOW=$(date +%c)
# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)
# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
kill -15 $CURRENT_PID
fi
애플리케이션이 이미 떠있을 때 애플리케이션을 종료하는 스크립트이다.
start.sh
#!/usr/bin/env bash
PROJECT_ROOT="/home/ubuntu/app/deploy"
JAR_FILE="$PROJECT_ROOT/accountbook-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy-start.log"
TIME_NOW=$(date +%c)
# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE
# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &
CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG
애플리케이션을 실행하는 스크립트로, Github Actions workflow에서 이미 빌드를 마쳤기 때문에 JAR 파일만 복사한 후 실행한다.
4. deploy.yml
deploy.yml 파일을 통해 다음과 같은 작업이 수행된다.
- 프로젝트 빌드
- /src/resources/application-prod.yml 생성
- AWS S3 버킷에 푸시
- CodeDeploy 수행
파일 위치
/src와 같은 레벨에 .github/workflows 패키지를 생성한 뒤 해당 위치에 deploy.yaml 파일을 생성합니다.

deploy.yml
name: Deploy to Amazon EC2
# 초기설정(1)
on:
push:
branches:
- main
# 초기설정(2)
env:
AWS_REGION: ap-northeast-2
S3_BUCKET_NAME: accountbookserver-s3-bucket
CODE_DEPLOY_APPLICATION_NAME: accountbookserver-codedeploy-app
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: accountbookserver-deployment-group
permissions:
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-20.04
environment: production
steps:
# (1) 기본 체크아웃
- name: Checkout
uses: actions/checkout@v3
# (2) JDK 11 세팅
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
# (3) Git Secret 을 활용한 application-xxx.yml 생성
- name: Create spring properties
run: |
touch src/main/resources/application-prod.yml
echo "$PROD_YML" > application-prod.yml.b64
base64 -d -i application-prod.yml.b64 > src/main/resources/application-prod.yml
env:
PROD_YML: ${{ secrets.PROD_YML }}
# (4) Gradle build(Test 제외)
- name: Build with Gradle
uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
with:
arguments: clean build -x test
# (5) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
# (6) 빌드 결과물을 S3 버킷에 업로드
- name: Upload to AWS S3
run: |
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--ignore-hidden-files \
--s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
--source .
# (7) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
- name: Deploy to AWS EC2 from S3
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip
# 초기설정 (1)
on:
push:
branches:
- main
"branches :"에 설정한 브랜치에서 푸시를 할 경우 deploy.yaml을 실행하는 것을 의미합니다.
# 초기설정 (2)
env:
AWS_REGION: ap-northeast-2
S3_BUCKET_NAME: accountbookserver-s3-bucket
CODE_DEPLOY_APPLICATION_NAME: accountbookserver-codedeploy-app
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: accountbookserver-deployment-group
deploy.yaml에서 사용할 이름들의 별칭을 지정할 수 있습니다. 예시로 { { env.AWS_REGION }} 형식으로 사용할 수 있습니다.
# 참고 : application.yml & application-prod.yml

애플리케이션을 실행함에 있어 db의 username, password와 같이 기밀 정보들이 필요하지만 이 정보들을 github에 그대로 올리게 된다면 보안적으로 좋지 않다(기밀정보를 가지고 해킹을 하거나 큰 청구비용이 나오게 하는 등). 그렇기에 보통 개발용 properties 파일과 배포용 properties 파일을 다르게 해서 실행해야 한다. 본 프로젝트에서는 처음부터 application.yml과 application-prod.yml을 따로 생성한 다음 application.yml은 개방해놓고, mode를 "prod"로 실행할 수 있게 설정을 해놓았다.
application.yml
spring-profiles-active : prod로 설정했다. 이러면 처음에는 application.yml을 실행했다가 application-prod.yml을 실행한다.
server:
port: 8080
servlet:
encoding:
charset: UTF-8
force: true
tomcat:
keep-alive-timeout: 100
threads:
max: 200
min-spare: 10
accept-count: 100
spring:
profiles:
active: prod
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
mvc:
pathmatch:
matching-strategy: ant_path_matcher
devtools:
livereload:
enabled: true
restart:
enabled: true
thymeleaf:
cache: false
logging:
level:
com:
amazonaws:
util:
EC2MetadataUtils: error

실행된 애플리케이션의 profile을 보면 "prod"인 걸 확인할 수 있다.
# (3) gitignore 했던 설정파일(properties-prod.yml)을 Git Secret으로 생성하고 저장하기
# (3) Git Secret 을 활용한 application-xxx.yml 생성
- name: Create spring properties
run: |
# touch 명령어를 사용해 src/main/resources/application-prod.yml 파일을 생성한다.
touch src/main/resources/application-prod.yml
echo "$PROD_YML" > application-prod.yml.b64
# base64 인코딩 파일(application-prod.yml.b64)을 디코딩하고(-d), ~/application-prod.yml로 입력 파일을 지정(-i)한다
base64 -d -i application-prod.yml.b64 > src/main/resources/application-prod.yml
env:
PROD_YML: ${{ secrets.PROD_YML }}
위 코드는 github secret에 base64 인코딩 후 등록한 PROD_YML 파일을 배포한 애플리케이션 src/main/resources 위치에 application-prod.yml 이름으로 파일을 생성할 수 있게 하는 코드이다. 이 과정을 거치면 빌드할 때 없던 application-prod.yml이 배포한 애플리케이션에 생성되면서 필요한 기밀정보들을 사용할 수 있으므로 애플리케이션을 정상적으로 실행할 수 있게 된다.
properties-prod.yml의 내용을 github secrets에 등록하는 과정

- properties 파일 Base64로 인코딩
- 배포하려는 repository
- Settings
- 왼쪽 메뉴 Secrets and variables
- Actions
- New repository secret 버튼을 클릭 후 인코딩한 파일 저장
deploy.yaml에서는 ${{ secrets.xxx }}형식으로 secret 값을 지정할 수 있고, secret명은 deploy.yml 파일의 (3)에 선언한 이름(PROD_YML)과 동일해야지 정상 적용됩니다.
# (5) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
# (5) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
(5) 단계에서는 Github Actions가 AWS IAM에 접근할 수 있게 Github Actions에 Access Key와 Secret Key 정보를 넘겨주어야 한다. 이 키값들은 비밀에 부쳐야 하므로 파일에 직접 입력해 놓기보다 github repository secrets에 따로 저장해야 한다.

구체적인 과정은 배포하려는 repository -> Settings -> 왼쪽 메뉴 Secrets and variables -> Actions 경로로 들어갑니다. New repository secret 버튼을 클릭해 해당 값을 저장하면 된다. ${{ secrets.xxx }} 형식으로 secret 값을 지정할 수 있다. deploy.yaml에서 설정한 AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY에 해당하는 키값을 동일한 이름으로 위의 사진처럼 github repository settings에 저장해야한다.
5. github actions 사용해서 배포하기
파일 수정을 마치고 main 브랜치로 push를 한 뒤 repository -> Actions 로 들어가면 배포가 진행되는 것을 확인할 수 있다. 배포가 성공하면 초록색 신호가, 실패하면 빨간색 신호와 함께 어느 단계에서 실패했는지 알 수 있다.

CodeDeploy 에서 배포 내역 확인

AWS CodeDeploy에 들어가면 배포가 성공한 것을 확인할 수 있다.
EC2 서버에서 애플리케이션 실행 확인

ssh로 ec2 서버에 접속한 후, 배포한 위치인 /app/deploy로 이동해 빌드한 파일(accountbook-0.0.1-SNAPSHOT.jar)이 성공적으로 생성된 것을 확인할 수 있다.
-자바 실행 파일 확인
"ps -ef | grep java"는 java로 실행중인 프로세스를 확인하는 코드이다. 이걸 입력하면 빌드한 accountbook-0.0.1-SNAPSHOT.jar 가 실행되어 있음을 확인할 수 있다.

Postman으로 API 요청 및 응답 성공

해당 서버로의 요청 후 성공적으로 응답이 오는 것도 확인할 수 있다.
참고
https://bcp0109.tistory.com/363
'DevOps' 카테고리의 다른 글
[자동배포] GitHub Actions, AWS S3, CodeDeploy로 자동 배포 구축하기 -1- (0) | 2023.07.18 |
---|---|
[CI/CD] Github Actions와 AWS S3, CodeDeploy로 자동 배포 하기 전 관련 개념 (0) | 2023.05.01 |
[Docker] Spring Boot 프로젝트 Docker로 배포하기 (0) | 2022.07.27 |