Github actions, CodeDeploy, Nginx로 CICD 및 무중단 배포하기 (1)
Github actions, CodeDeploy, Nginx로 CICD 및 무중단 배포하기 (2)
위 글들에서 이어지는 내용입니다!
이전 글들에서는 자주 업데이트가 필요한 개발용 서버에 CICD와 무중단 배포를 적용하였지만 현재 우리 프로젝트는 개발용 서버와 배포용 서버를 따로 나눠쓰고 있는 상황이다
실수를 줄이기 위해 배포용 서버에도 같은 플로우를 만들어 release 브랜치에 push하면 개발용 서버에 자동 배포되고, master 브랜치에 push 하면 배포용 서버에 자동 배포되게 하고 싶었다.
관련 글들도 없고, 정확한 정답은 없는 것 같아 다음과 같이 작성하였다
개발용, 배포용 설정 파일 분리
우선 개발용 서버와 배포용 서버를 분리하기 위해 spring profiles를 사용하였다
application.yml 파일에 다음과 같이 작성한 후 application-dev.yml , application-prod.yml 파일을 만들어 개발용 서버와 배포 서버에서 다르게 적용되는 것들을 분리해주었다
application-dev.yml , application-prod.yml 두 파일 역시 gitignore 되어있으므로 github actions secret 에 등록해주었다
# application.yml
...
---
spring:
profiles:
active: dev # application-dev를 active 하겠다
---
spring:
profiles:
active: prod # application-prod를 active 하겠다
---
...
EC2 추가 설정
Tag 추가
CodeDeploy 를 생성할 때 어떤 인스턴스에서 수행할 지 구분하는 값으로 태그를 사용하기 때문에 추가가 필요
키 값을 입력하고 저장한다. 값은 비워놓아도 된다
EC2 전용 IAM 역할 추가
EC2 인스턴스에서 S3에 올려놓은 파일에 접근할 수 있도록 권한 주어야 한다
[보안 - IAM 역할 수정]에서 개발용 ec2 인스턴스에서 사용된 것과 같은 IAM 역할을 선택한 뒤 저장한다
+ 첫번째 글과 같은 방식으로 CodeDeploy Agent 도 설치해준다
CodeDeploy의 배포그룹 생성
두번째 글에서 만든 codedeploy 애플리케이션인 iluvit-app에서 배포 그룹 생성을 클릭한다
원하는 배포 그룹 이름, 역할, 유형을 설정한다. 서비스 역할은 두번째 글에서 만든 CodeDeploy 전용 IAM 역할을 선택한다
태그 그룹 1 에서 위에서 만든 우리 인스턴스 태그를 선택하여 어떤 인스턴스에서 동작할 지 선택한다
나머지 설정들은 아래와 같이 한다
배포용 Github Actions Workflow 작성
/.github/workflow 아래에 prod_deploy.yml 파일을 추가해 이전 글의 dev_deploy.yml과 유사하게 작성한다
변경된 부분은 아래와 같다
- 브랜치가 release 에서 master로 변경되었다.
- 배포용 최종 브랜치인 master에 푸시되거나 PR이 날라올 때마다 CICD로 무중단 배포가 이루어진다
- CODE_DEPLOY_DEPLOYMENT_GROUP_NAME ( 배포 그룹 이름 ) 이 변경되었다
- 개발용 서버와 배포용 서버는 같은 CodeDeploy 애플리케이션을 사용하지만 다른 배포 그룹을 사용하였다
- Git ignore한 파일을 직접 생성해줄 때 application-dev.yml 대신 application-prod.yml을 생성해준다
# /.github/workflows/prod_deploy
name: Prod Deploy
# master 브랜치로 push 되거나 pr이 날아가는 경우 workflow가 수행된다
on:
push:
branches:
- master
pull_request:
branches:
- master
# 본인이 설정한 리전, 버킷 이름, CodeDeploy 앱 이름, 배포그룹 이름을 채워 넣는다
env:
AWS_REGION: ap-northeast-2
S3_BUCKET_NAME: iluvit-dev-actions-s3-bucket
CODE_DEPLOY_APPLICATION_NAME: iluvit-app
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: iluvit-prod-deployment-group
permissions:
contents: read
jobs:
deploy:
name: Realease_Deploy
runs-on: ubuntu-latest
environment: production
steps:
# (1) 기본 체크아웃
- name: Checkout
uses: actions/checkout@v2
# (2) JDK 17 세팅
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# (3) gitignore한 파일 직접 생성해주기
- name: make resources file
run: |
cd ./src/main/resources
touch ./application.properties
echo "${{ secrets.PROPERTIES }}" > ./application.properties
touch ./application-prod.yml
echo "${{ secrets.PROD_YML }}" > ./application-prod.yml
touch ./application-http.yml
echo "${{ secrets.HTTP_YML }}" > ./application-http.yml
touch ./application-map.yml
echo "${{ secrets.MAP_YML }}" > ./application-map.yml
touch ./application-s3.yml
echo "${{ secrets.S3_YML }}" > ./application-s3.yml
touch ./application-secret.yml
echo "${{ secrets.SECRET_YML }}" > ./application-secret.yml
touch ./application-security.yml
echo "${{ secrets.SECURITY_YML }}" > ./application-security.yml
# (4) Gradle build (Test 제외)
- name: Build with Gradle
run : ./gradlew clean build --exclude-task 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 }} \
--file-exists-behavior OVERWRITE \
--s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip
배포 스크립트 수정
scripts/run_new_was의 마지막 부분을 수정해주었다
- 현재 개발용 서버와 배포용 서버 중 어디서 배포가 일어나고 있는지 파악하기 위해 curl http://169.254.169.254/latest/meta-data/public-ipv4 로 현재 ec2의 public ip 주소를 가져왔다
- IP주소는 xx.xxx.xxx.xxx 다음과 같은 형식이라 전체 비교는 힘들다고 판단하여 앞의 두 자리만 가져와서 비교하였다
- IP주소가 개발용 서버의 IP주소와 일치하면 spring.profiles.active=dev로 설정되어 jar파일을 통해 새로운 서버가 실행되고 반대로 배포용 서버의 IP주소와 일치하면 spring.profiles.active=prod로 설정되어 jar파일을 통해 새로운 서버가 실행된다
# scripts/run_new_was.sh
PROJECT_ROOT="/home/ubuntu/iluvit/app" # 프로젝트 루트
# service_url.inc 에서 현재 서버의 포트 번호 가져오기
CURRENT_PORT=$(cat /home/ubuntu/iluvit/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0
echo "> Current port of running WAS is ${CURRENT_PORT}."
# 타켓 포트 번호 알아내기
if [ ${CURRENT_PORT} -eq 8081 ]; then
TARGET_PORT=8082 # 현재포트가 8081이면 8082로 배포
elif [ ${CURRENT_PORT} -eq 8082 ]; then
TARGET_PORT=8081 # 현재포트가 8082라면 8081로 배포
else
echo "> Not connected to nginx" # nginx가 실행되고 있지 않다면 에러 코드
fi
# 타겟 포트의 PID를 불러온다
TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')
# PID를 이용해 해당 포트 서버 Kill
if [ ! -z ${TARGET_PID} ]; then
echo "> Kill ${TARGET_PORT}."
sudo kill -9 ${TARGET_PID}
fi
######## 아래부터 추가된 코드입니다
# 현재 ec2 서버의 public IP 주소를 불러온다
PUBLIC_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)
# public IP 주소의 앞 두자리가 52(임의)라면 profile을 dev로 하여 타켓 포트에 jar파일을 이용해 새로운 서버 실행
if [ ${PUBLIC_IP:0:2} -eq 52 ]; then
nohup java -jar -Dspring.profiles.active=dev -Dserver.port=${TARGET_PORT} /home/ubuntu/iluvit/app/build/libs/iLUVit-0.0.1-SNAPSHOT.jar > /home/ubuntu/iluvit/app/nohup.out 2>&1 &
# public IP 주소의 앞 두자리가 13(임의)라면 profile을 prod로 하여 타켓 포트에 jar파일을 이용해 새로운 서버 실행
elif [ ${PUBLIC_IP:0:2} -eq 13 ]; then
nohup java -jar -Dspring.profiles.active=prod -Dserver.port=${TARGET_PORT} /home/ubuntu/iluvit/app/build/libs/iLUVit-0.0.1-SNAPSHOT.jar > /home/ubuntu/iluvit/app/nohup.out 2>&1 &
fi
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0
scripts/health_check.sh, scripts/switch.sh는 수정사항 없이 그대로 사용하면 된다
두 workflow ( Dev Deploy, Prod Deploy )가 모두 잘 작동하며, 두 배포 그룹 ( iluvit-dev-deployment-group과 iluvit-prod-deployment-group)에서 모두 배포가 잘 되는 것을 확인할 수 있다