이전에 포스팅 했던 서비스에 CI/CD를 추가하고자 하였습니다.
문제 상황
서버 첫 배포 이후, 지속적인 프론트와의 테스트 과정이 필요했습니다.
이 때 매번 ssh 접속 후 실행중인 jar process를 중단시키고, 재배포 하는 과정은 번거로웠습니다.
프로그래밍을 할 때에도 2번 이상 반복되는 작업은 함수를 만들듯이, 이런 배포 및 운영 과정에서도 반복되는 작업은 자동화를 통해 일을 효율적으로 진행하고자 합니다.
전체적인 실행 흐름은 이렇습니다.
CI/CD Flow
1. Master branch merge시, Github Action 실행
2. git pull, gradlew clean build, deploy
3. AWS 인증 과정을 통해 AWS S3에 압축하여 임시 업로드
4. AWS CodeDeploy를 통해 AWS S3에 있는 파일 압축 해제
5. 해제한 파일을 EC2 server에 배포
이를 그림으로 나타내면 이렇습니다.
1. Github Actions workflow를 통해 AWS S3 bucket이라는 임시 저장소에 프로젝트 파일(*.jar)을 업로드 합니다.
1-1. 이 때 deploy.yml 이라는 스크립트가 실행되며 작업을 수행합니다.
2. Github Actions가 각종 환경설정을 마치고 AWS CodeDeploy에게 배포 작업을 수행하게끔 합니다.
3. AWS CodeDeploy는 AWS S3 bucket에 접근하여 업로드된 압축 파일 (*.jar)을 받아 옵니다.
4. AWS CodeDeploy가 EC2 instance에 배포를 합니다. (appspec.yml 스크립트 기반 실행함. stop.sh, start.sh 등 활용)
자세한 내용을 이제 다뤄봅시다.
우선 AWS 설정부터 시작합니다.
1. EC2 tag configuration
EC2 > Instance
AWS EC2 instance 관리에서 태그를 설정해줍니다.
CodeDeploy에서 ec2 instance를 구분할 수 있게하기 위함입니다.
2. IAM 역할 추가
IAM > 역할
EC2 instance가 나중에 CodeDeploy를 이용해 AWS S3에 압축하여 올려놓은 파일을 다운로드 받을 수 있게 하기 위함입니다.
기존 역할들을 무시하고, 새 역할을 만듭니다.
S3 FULL을 검색하여 권한을 추가합니다.
다른 역할들과 구별하여 식별할 수 있는 이름을 정해주고, 하단에 역할 생성 버튼을 눌러 생성을 완료합니다.
EC2 > Instance
EC2 instance 섹터로 가서 방금 생성한 IAM 역할과 instance를 매핑합니다.
3. Install CodeDeploy (on EC2 instance)
EC2 instance에 ssh 접속을 하고, CodeDeploy agent를 설치합니다.
$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
$ cd /home/ubuntu
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto > /tmp/logfile
$ sudo service codedeploy-agent status
Ref (공식문서)
EC2 instance에 ssh 접속을 하는 방법은 하단 포스팅을 참조하면 됩니다.
4. Create AWS S3
AWS S3란?
AWS에서 제공하는 스토리지 서비스입니다.
S3 버킷에 파일을 업로드하고, 다운로드 하며 활용할 수 있습니다.
Ref (공식문서)
Amazon S3 > Bucket
'버킷 만들기' 버튼으로 버킷을 생성합니다.
마찬가지로 원하는 이름을 설정하되, 저는 배포할 어플리케이션 이름을 넣어 구별가능하게끔 했습니다.
나머지 옵션들은 디폴트값으로 설정하고 버킷을 생성했습니다.
성공적으로 생성된 모습입니다.
5. Create CodeDeploy
IAM > 역할
실질적인 배포작업을 할 CodeDeploy를 생성합니다.
IAM에서 역할을 만들어서 이 역할을 바탕으로 EC2 instance가 배포작업을 진행합니다.
마찬가지로 역할 만들기 버튼을 클릭합니다.
이번엔 서비스 검색에서 CodeDeploy를 검색하고 선택하고 다음을 누릅니다.
잘 적용된 것을 확인하고 다음을 누릅니다.
역할 이름을 식별가능하게 적어주고 하단에 역할 생성 버튼을 누릅니다.
그리고 CodeDeploy Application을 생성합니다.
개발자 도구 > CodeDeploy > Application
어플리케이션 생성 버튼 클릭
이름 지정 후, 컴퓨팅 플랫폼은 EC2/온프레미스 를 선택하고 어플리케이션 생성 버튼을 클릭합니다.
그러면 생성된 모습을 확인 할 수 있습니다.
그 후, 생성한 어플리케이션에 들어가서 배포 그룹을 생성합니다.
이름을 지정하고, 서비스 역할을 아까 만든 CodeDeploy 역할을 선택합니다.
생성된 서비스 역할이 보이지 않는다면 위를 참조하여 다시 생성하면 됩니다.
그리고 하단의 추가적인 설정을 합니다.
EC2 instance를 선택하고, key/value를 사용할 instance의 값으로 설정하면 됩니다.
처음에 만든 instance tag가 여기에 쓰입니다.
나머지 추가적인 설정은 이렇게 하고 배포 그룹 생성 버튼을 클릭합니다.
이렇게 배포 그룹까지 생성했습니다.
6. Create IAM user
IAM > User
직접적인 사용자를 추가하여 해당 사용자로 Github Actions의 workflow에서 접근 후, 배포작업을 진행할 겁니다.
사용자 생성을 클릭합니다.
권한 부여를 위해 다음과 같이 설정 후 다음으로 넘어갑니다.
필자는 이미 생성한 사용자가 있기에 중복 오류가 뜨는 모습입니다. 무시하셔도 됩니다.
그리고 권한을 설정할건데, 직접 정책 연결 옵션을 클릭하고, AWSCodeDeployFull과 AmazonS3FullAccess 권한을 부여합니다.
마지막으로 사용자 생성 버튼을 클릭하여 생성을 완료합니다.
생성을 완료하면 Access key ID와 Secret Access key가 있는데, 이 두 값을 Github Repo에 등록합니다.
Github > 배포할 Repo > Settings > Secret > Actions
이렇게 저장해두고, 이렇게 저장해도 나중에 확인하기는 불가능하기에 필요하면 따로 저장해두면 됩니다.
이러면 AWS 설정은 모두 마무리 되었습니다.
7. Create AppSpec.yml
이제 배포할 프로젝트의 root dir에서 appspec.yml을 생성할 겁니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/flag-app-back/
overwrite: yes
file_exists_behavior: OVERWRITE
permissions:
- object: /
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: scripts/stop.sh
timeout: 60
ApplicationStart:
- location: scripts/start.sh
timeout: 60
필자는 이렇게 설정하였습니다.
code commits
- source : 자동 배포 시, merge된 latest file을 임시로 업로드 해놓을 위치
- destination : 임시로 업로드한 파일을 붙여넣기 할 때의 위치 (git clone해놓은 repo의 위치)
- overwrite option : 중복된 파일이 있는 경우를 방지하는 옵션
- AfterInstall : 설치된 이후 stop.sh 쉘 스크립트를 실행하여 배포중인 파일을 중단
- ApplicationStart : 중단한 후, start.sh 쉘 스크립트를 실행하여 latest ver 배포
start.sh와 stop.sh는 이렇게 EC2 instance 내부 project root dir에 생성하면 됩니다.
# start.sh
PROJECT_ROOT="/home/ubuntu/flag-app-back"
JAR_FILE="$PROJECT_ROOT/build/libs/flag_back-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.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
# stop.sh
PROJECT_ROOT="/home/ubuntu/flag-app-back"
JAR_FILE="$PROJECT_ROOT/build/libs/flag_back-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.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
세부 코드은 이렇게 됩니다.
그리고 build.gradle 파일에 코드를 몇줄 추가합니다.
jar {
enabled = false
}
이는 build시 생성되는 plain.jar파일을 생성하지 않고 일반 jar 파일만 생성하게 하기 위함입니다.
8. Create Github Action workflow
배포할 repo > .github > workflows / deploy.yml
deploy.yml 소스파일을 생성합니다.
latest ver merge시 해당 코드가 실행되며 자동 배포가 됩니다.
name: CICD Test
run-name: Running
on:
push:
branches:
- master
env:
AWS_REGION: ap-northeast-2
AWS_S3_BUCKET: myflagbucket
AWS_CODE_DEPLOY_APPLICATION: cicd-test-CD
AWS_CODE_DEPLOY_GROUP: cicd-test-CD-group
jobs:
build-with-gradle:
runs-on: ubuntu-22.04
steps:
- name: master 브랜치로 이동
uses: actions/checkout@v3
with:
ref: master
- name: JDK 11 설치
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'corretto'
- name : git pull
run: git pull origin master
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
- name: Build with Gradle
run: ./gradlew clean build
- name: AWS credential 설정
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ env.AWS_REGION }}
aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY }}
- name: S3에 업로드
run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/cicdtest/$GITHUB_SHA.zip --source .
- name: EC2에 배포
run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=cicdtest/$GITHUB_SHA.zip,bundleType=zip
- env (환경설정) : 본인이 AWS 환경세팅 시 설정했던 이름들로 적어주면 됨
- jobs : 실질적으로 실행되어야 하는 명령어들 입력
- 마스터 브랜치 이동
- jdk 설치 (기존에 설치되어 있어도 혹시 모르니 재설치)
- git pull : merge된 file reload
- chmod +x ./gradlew (수동 배포 시 쓰지 않았지만 혹시 모르니..)
- ./gradlew clean build
- AWS 설정 : 상단의 env에서 설정해준 값들을 이용하여 환경설정을 마무리함. 이 때 github repo에 등록해놓은 access key, secret key가 사용됨
- S3 업로드 : AWS CodeDeploy application을 활용하여 빌드된 산출물을 압축하여 S3 bucket에 업로드
- EC2에 배포 : AWS CodeDeploy application를 활용하여 S3 bucket에 존재하는 산출물을 다운로드 받아 EC2에 배포
Ref (공식문서)
그리고 나서 프로젝트 소스파일에 사소한 편집(주석)을 넣어 master branch에 merge하니
자동으로 Github Actions Workflow가 작동합니다.
배포할 repo > Actions > 생성한workflow
start.sh, stop.sh, appspec.yml, deploy.yml 파일들의 dir과 사소한 파일명 불일치로 삽질을 정말 많이 했습니다..
그래도 workflow 내부 error log를 계속 서칭하고 소스코드 및 파일 위치를 바꾸다보니 해결할 수 있었습니다.
그래서 다시 정리하자면
start.sh : 배포할 ec2 내부 프로젝트 파일의 root dir
stop.sh : 배포할 ec2 내부 프로젝트 파일의 root dir
deploy.yml : github레포/.github/workflows/deploy.yml
appspec.yml : github레포 root dir (src파일과 동일한 위치)
꼼꼼히 파일들을 확인하면 시간 많이 줄일 수 있습니다..
그리고 성공..!
이제 수정사항 생길 때마다 ssh 접속 -> git pull -> ./gradlew clean build -> cd build/libs -> nohup..~~ 안 해도 됩니다..!
merge 시마다 자동으로 배포되고, 배포가 안 되었다면 github actions workflow에서 error log를 보고 문제를 파악할 수 있습니다!
예시로 이런 경우는 build과정에서 에러가 난 것이므로 소스코드 문제일 확률이 높겠죠!
이렇게 CI/CD까지 적용하게 되었습니다.
아직 개념적인 이해가 조금 부족한 것 같아 배포 전반적인 과정을 다시 공부 해봐야 할 것 같습니다.
공부하는 과정을 기록한 것이기에 틀린 점이 있을 수 있습니다!
혹시 이거 아닌데.. 싶으시면 말씀해주시면 감사합니다..
마치겠습니다.
'대외활동 > UMC 4th' 카테고리의 다른 글
[FlagApp] SpringBoot - Server 배포 (0) | 2023.08.07 |
---|---|
Ch 7 API : 당근마켓 CRUD (0) | 2023.05.18 |
Ch4 SQL : RDS실습 (0) | 2023.05.17 |
Ch3 데이터베이스 : AWS RDS 구축 (0) | 2023.05.17 |
Ch2 클라우드 구축 : AWS EC2 실습 (0) | 2023.05.06 |