Github Actions + AWS CodeDeploy를 이용해서~ CI/CD 파이프라인 구축해보자!

1. Github에 코드 Push 하면 Github Actions이 자동으로 실행되어 CI(빌드) 작업을 수행
2. 코드 상에 문제가 없다면 빌드 작업을 통해 jar(혹은 war) 파일이 생성되고 사전에 작성한 배포 스크립트 파일(= deploy.sh)과 함께 AWS Storage 서비스인 S3에 전달되어 저장됨
3. S3에 저장이 잘 되었다면 EC2에 설치한 CodeDeploy Agent가 S3에 저장된 프로젝트 Zip파일을 가져와서 내려받음
4. 그 후, 배포 스크립트 파일을 읽어 프로젝트 실행을 시작
→ Github에 코드를 Push하기만 하면 가장 latest 버전이 EC2에 내려받아져서 프로젝트가 자동으로 배포되는 흐름 !
처음으로 Github Actions를 설정하게 되면, .github/workflows에 yml 파일을 작성하게 된다.

# WorkFlow의 이름을 'Java CI with Gradle'로 정의
name: Java CI with Gradle
# Trigger 지정으로, main 브랜치가 Push, PR 될 때 해당 workflow 실행
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
# workflow는 한 개 이상의 job을 가지고, 각 job은 여러 step에 따라 단계를 나눌 수 있음
jobs:
build:
# 해당 job에서 아래의 step들이 어떠한 환경에서 실행될 것인지를 지정
runs-on: ubuntu-latest
steps:
# 작업에서 접근할 수 있도록 Github_Workspace에서 저장소를 체크아웃함
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
#Build
- name: Build with Gradle
run: ./gradlew clean build
부여해야하는 권한은 AWSCodeDeployFullAccess와 AmazonS3FullAccess 이다.
그 후, 지금에만 확인할 수 있는 ACCESS KEY와 SECRET KEY는 .csv 파일로 저장해두어야 한다.
→ 여기서 생성된 비밀키[ACCESS KEY, SECRET KEY, Region, application.properties에 적혀있는 값들 등등]를 위에서 말한 Github Setting의 Secret 탭에 값을 등록하여 노출하지 않고 사용해야 한다.
여기에서 S3는 AWS Storage 서비스로 Github Actions로 빌드된 .jar(혹은 .war) 파일을 저장하는 파일서버를 의미한다.
배포를 위한 버킷의 경우, 따로 설정 필요없이 생성해주면 된다.
EC2에 설치한 CodeDeploy Agent가 S3에 저장된 이 파일과 프로젝트 파일을 가져와서 내려받는다. 그 후, 배포 스크립트 파일을 읽어 프로젝트 실행을 시작한다.
즉 스크립트 코드는 프로젝트를 EC2에서 실행할 때, 이전에 실행되고 있는 SpringBoot 프로젝트가 있다면 종료시킨 뒤 새로운 버전의 프로젝트를 실행해준다.
WAR_NAME=$(basename server-0.0.1-SNAPSHOT.war)
DEPLOY_PATH=/home/ec2-user/action
CURRENT_PID=$(pgrep -f $WAR_NAME)
NOW=$(date)
echo "> 현재 시간: $NOW" >>$DEPLOY_PATH/deploy.log
echo "> 현재 실행중인 애플리케이션 pid: $CURRENT_PID" >>$DEPLOY_PATH/deploy.log
if [ -z $CURRENT_PID ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> $DEPLOY_PATH/deploy.log
else
echo "> kill -15 $CURRENT_PID" >> $DEPLOY_PATH/deploy.log
kill -15 $CURRENT_PID
sleep 5
fi
DEPLOY_JAR=$DEPLOY_PATH/$WAR_NAME
echo "> DEPLOY_JAR 배포: $DEPLOY_JAR" >> $DEPLOY_PATH/deploy.log
nohup java -jar $DEPLOY_JAR >> $DEPLOY_PATH/deploy_spring.log 2>$DEPLOY_PATH/deploy_err.log &
echo "=====" >> $DEPLOY_PATH/deploy.log
# WorkFlow의 이름을 'Java CI with Gradle'로 정의
name: Java CI with Gradle
# Trigger 지정으로, main 브랜치가 Push, PR 될 때 해당 workflow 실행
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
# workflow는 한 개 이상의 job을 가지고, 각 job은 여러 step에 따라 단계를 나눌 수 있음
jobs:
build:
# 해당 job에서 아래의 step들이 어떠한 환경에서 실행될 것인지를 지정
runs-on: ubuntu-latest
steps:
# 작업에서 접근할 수 있도록 Github_Workspace에서 저장소를 체크아웃함
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
#Build
- name: Build with Gradle
run: ./gradlew clean build
# 전송할 파일을 담을 디렉토리 생성
- name: Make Directory for deliver
run: mkdir deploy
# War 파일 Copy
- name: Copy War
run: cp ./build/libs/*.war ./deploy/
# appspec.yml Copy
- name: Copy appspec
run: cp ./appspec.yml ./deploy/
# scripts폴더 Copy
- name: Copy scripts
run: cp ./scripts/* ./deploy/
# 압축파일 형태로 전달
- name: Make zip file
run: zip -r -qq -j ./springboot-intro-build.zip ./deploy
# S3 Bucket으로 copy
- name: Deliver to AWS S3
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESSKEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRETKEY }}
run: aws s3 cp --region ap-northeast-2 ./springboot-intro-build.zip s3://${{secrets.S3_BUCKET}}/springboot-intro-build.zip
#aws s3 cp --region ap-northeast-2 --acl private ./springboot-intro-build.zip s3://springboot-intro-build
# Deploy
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESSKEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRETKEY }}
run: |
aws deploy create-deployment \
--application-name cicd-habday \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name cicd-habday \
--file-exists-behavior OVERWRITE \
--s3-location bucket=${{secrets.S3_BUCKET}},bundleType=zip,key=springboot-intro-build.zip \
--region ap-northeast-2
배포 시스템인 CodeDeploy를 통해 EC2 인스턴스에 배포할 수 있도록 설정해야 한다.
IAM 역할 만들고 EC2 인스턴스에 역할 연결
→ 먼저 CodeDeploy와 EC2 사이의 접근을 가능하게 하려면 AWS IAM '역할'을 만들어줘야 한다. 역할은 AWS 내에서의 접근 권한이라고 생각하면 된다.
이때, 역할을 바꾸고 나면 인스턴스를 재부팅 해야한다.
EC2 인스턴스에 codedeploy 설치
재부팅이 완료되면 터미널에서 EC2 인스턴스에 연결 후, 아래의 명령어를 통해 codedeploy를 설치한다.
aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2
다운로드 성공하면 'download: s3://aws-codedeploy-ap-northeast-2/latest/install to ./install' 이 문구가 나온다.
아래와 같이 실행 권한을 주고 실행을 하여 설치를 마무리한다.
chmod +x ./install
sudo ./install auto
설치 확인을 위해 아래의 명령을 입력하고
```
sudo service codedeploy-agent status
```
'The AWS CodeDeploy agent is running as PID ****' 이와 같은 메세지가 출력되면 된다.
CodeDeploy 설정
역할 만들고 EC2 인스턴스에 역할 연결
EC2와 마찬가지로 CodeDeploy에서도 역할을 만들어야 한다.
codedeploy 애플리케이션 생성 및 설정
역할이 만들어졌다면 codedeploy 서비스에서 애플리케이션을 생성한다. 이때, 역할은 아까 만든 역할을 선택하고 배포 설정의 경우, 한대의 서버이기 때문에 CodeDeployDefault.AllAtOnce를 선택한다.
프로젝트 폴더에 이 파일을 생성해야 한다. codedelpy 실행 시 작성한 스크립트를 동작시킨다.
version: 0.0
os: linux
# S3에 있는 zip 파일이 EC2에 배포될 위치를 지정
files:
- source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 루트로 지정(전체파일)
destination: /home/ec2-user/action/ # source에서 지정된 파일을 받을 위치, 이후 jar를 실행하는 등은 destination에서 옮긴 파일들로 진행
overwrite: yes
permissions: # CodeDeploy에서 EC2서버로 넘겨준 파일들을 모두 ec2-user권한을 갖도록 합니다.
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
# ApplicationStart 단계에서 deploy.sh를 실행시키도록 합
hooks: # CodeDeploy배포 단계에서 실행할 명령어를 지정합니다.
ApplicationStart: # deploy.sh를 root권한으로 실행합니다.
- location: deploy.sh
timeout: 60 # 스크립트 실행 60초 이상 수행되면 실패가 됩니다.
runas: root # s3 이미지 업로드 시, permission denied 오류 해결을 위해 ec2-user에서 root로 변경