CI/CD 는 애플리케이션 개발 및 배포 과정을 자동화화고 지속적으로 통합시키는 개발 방법론이다.
CI(Continuous Integretion) 는 여러개발자가 동시에 작업하며, 코드의 변경사항을 지속적으로 통합하는 과정입니다. CI를 통해 코드변경사항이 정기적으로 빌드, 테스트되어 문제가 발생하는 것을 미리 파악할수 있고, 코드품질을 유지할 수 있습니다.
CD(Continuous Deployment) 는 빌드 및 테스트 과정을 통과한 코드를 자동으로 운영환경에 배포하는 과정입니다. CD를 통해 릴리즈 주기를 단축시키고 , 배포과정에서 발생할 수 있는 인적오류를 줄일 수 있다.
이렇게 하는 이유는 application.yml 파일이 ignore 되어 있어 깃허브에 올라 오지 않기 때문에 ci/cd때 사용하기위해 , 시크릿에 밸류값으로 yml 내용을 저장하여 추후 gradle.yml 작성때 마다 application.yml 파일을 생성 할수 있게 할 예정
시크릿키가 만들어진 것을 확인 할 수 있습니다.
이 값은 다시 볼수는 없기 때문에 필요시 다른곳에 저장해둬야 합니다.
(1)
Github Repository 로 이동해서 Actions 탭에서 새로운 Workflow 를 추가할 수 있습니다.
자주 사용되는 언어나 프레임워크는 이미 제공되는게 있기 때문에 검색해서 선택만 하면 됩니다.
Configure 버튼을 누르면 기본적으로 제공되는 gradle.yml
파일이 제공됩니다.
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
pull_request:
branches: [ "develop" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- uses: actions/checkout@v2
- run: mkdir ./src/main/resources
- run: touch ./src/main/resources/application.yml
- uses: actions/upload-artifact@v2
with:
name: application.yml
path: ./src/main/resources/application.yml
- name: Setup MySQL
uses: samin/mysql-action@v1
with:
character set server: 'utf8'
mysql database: 'mydb'
mysql user: ${{ secrets.MYSQL_USER}}
mysql password: ${{ secrets.MYSQL_PASSWORD }} # github Secret 탭에서 설정하세요!
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build
on
에서는 워크플로우를 수행할 이벤트를 결정합니다. 위 코드는 develop
을 베이스 브랜치로 한 Pull Request 를 생성하였을 때 수행된다는 뜻입니다.
jobs
에서는 수행할 워크플로우를 차례대로 입력하면 됩니다. 더 궁금한 점은 Github Actions Docs 를 참고해주세요.
https://stackoverflow.com/questions/63010170/github-actions-decode-base64-string-on-windows
- uses: actions/checkout@v2
- run: mkdir ./src/main/resources
- run: touch ./src/main/resources/application.yml
- run: echo "${{ secrets.APPLICATION }}" | base64 --decode > ./src/main/resources/application.yml // github secrets에 암호화되어 저장된 APPLICATION secret파일 본문을 가져와
echo " 내용" > [파일명] 을 이용해 내용으로 파일을 덮어쓰기로 저장됩니다.
- uses: actions/upload-artifact@v2
with:
name: application.yml
path: ./src/main/resources/application.yml
MySQL GitHub Action 설정법
MySQL GitHub Action
- name: Setup MySQL
uses: samin/mysql-action@v1
with:
character set server: 'utf8'
mysql database: '테스트에 사용할 db이름'
mysql user: '유저 이름'
mysql password: ${{ secrets.MYSQL_PASSWORD }} # github Secret 탭에서 설정하세요!
CI 가 잘 되는지 확인 하기 위해 테스트 용 커밋과 pr 을 날려봤다 .
아래와 같이 수정 후 다시 PR 날리니
- name: Build with Gradle
run: ./gradlew clean build
# - name: Build with Gradle
# uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
# with:
# arguments: clean build
성공
이전에 생성한 인스턴스를 기준으로 아래 작업들을 추가로 진행합니다.
CodeDeploy 를 생성할 때 어떤 인스턴스에서 수행할 지 구분하는 값으로 태그를 사용하기 때문에 추가가 필요합니다.
기존에 인스턴스 생성할 때 태그까지 같이 생성했다면 이 과정은 생략해도 괜찮습니다.
원하는 키 값을 입력하고 저장을 누릅니다.
다시 EC2 인스턴스 정보에서 태그가 등록되었는지 확인할 수 있습니다.
EC2 인스턴스에서 S3 에 올려놓은 파일에 접근할 수 있도록 권한을 추가해줘야 합니다.
기본적으로 존재하는 역할들이 있는데 신경쓰지 말고 새로운 역할 만들기를 선택합니다.
IAM 역할을 연결할 서비스를 선택합니다.
EC2 인스턴스에서 S3 접근할 수 있도록 AmazonS3FullAccess
권한을 추가합니다.
마지막으로 원하는 이름을 입력한 뒤 생성을 완료합니다.
EC2 인스턴스 관리 페이지로 이동해서 "작업 > 보안 > IAM 역할 수정" 을 선택합니다.
방금 만든 EC2 전용 IAM 역할을 선택한 뒤 저장을 누르면 연결이 완료됩니다.
터미널 창에 ssh로 ec2서버를 띄운 후 아래와 같이 명령어 입력합니다.
Ubuntu 18.04 환경일때
$ 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
$ sudo service codedeploy-agent status
CodeDeploy Agent 설치 를 보고 명령어를 따라 치기만 하면 됩니다.
EC2 환경이 Ubuntu 가 아니거나 버전이 다르다면 공식 문서를 참고해주세요.
정상적으로 설치가 완료되면 이런 응답이 와야 합니다.
AWS S3 버킷이란 이미지 또는 zip 파일을 저장하기 위한 스토리지 서비스입니다.
빌드한 프로젝트 코드를 압축해서 S3 에 저장한 후 EC2 서버에서 S3 에 접근해서 압축한 파일을 가져오기 위해 사용합니다.
S3 메뉴로 이동해서 "버킷 만들기" 를 누릅니다.
원하는 버킷 이름과 리전을 선택합니다.
ACL 은 기본값을 선택해서 비활성화 합니다.
나머지 설정을 마저 합니다.
변경할 필요 없이 기본값 그대로 두면 됩니다.
버킷 생성이 완료되면 이렇게 나타납니다.
배포를 도와주는 CodeDeploy 생성 및 설정을 진행해봅니다.
CodeDeploy 를 사용하기 위해선 IAM 에서 역할을 만들어야 합니다.
IAM 서비스로 이동해서 역할 만들기를 선택합니다.
기본적으로 제공되는 AWS 서비스에서 CodeDeploy
를 검색한 후 가장 기본적인 걸 선택합니다.
나머지는 건들 필요 없고 이름만 새로 추가합니다.
저는 my-codedeploy-iam
로 설정했습니다.
이름을 입력했다면 "역할 생성" 을 눌러 마무리합니다.
이제 우리가 사용할 CodeDeploy 앱을 생성합니다.
메뉴에서 생성 버튼을 누릅니다.
원하는 이름을 입력 후 컴퓨팅 플랫폼은 EC2/온프레미스
를 선택합니다.
CodeDeploy 애플리케이션에서 사용하는 배포 그룹을 생성합니다.
방금 만든 애플리케이션에서 배포 그룹 생성을 누릅니다.
원하는 배포 그룹 이름, 역할, 유형을 설정합니다.
서비스 역할은 위에서 만든 IAM 역할을 선택할 수 있게 나옵니다.
어떤 인스턴스에서 동작할 지 선택합니다.
EC2 인스턴스에서 태그를 추가해야 선택할 수 있습니다.
우리는 위에서 이미 기존 EC2 인스턴스에 태그를 추가했기 때문에 해당 태그 키를 선택합니다.
AWS Systems Manager 는 크게 중요한거 같지 않으니 적당히 선택하고 로드 밸런싱을 사용하지 않으니 체크만 해제합니다.
다 설정했으면 배포 그룹 생성을 눌러 마무리합니다.
AWS 를 Github Actions 워크 플로우에서 접근하려면 권한이 필요합니다.
지금까지는 IAM 역할만 추가해서 특정 서비스 (EC2, CodeDeploy) 에게 부여 했지만 이번에는 IAM 사용자를 추가해봅니다.
IAM 메뉴에서 사용자 추가를 선택합니다.
사용자 이름을 추가하고 다음을 선택합니다.
이 사용자에게 추가할 접근 권한을 고릅니다.
워크 플로우에서 CodeDeploy 를 실행해야 하기 때문에 다음 두 권한을 추가합니다.
태그는 필요 없기 때문에 생략하고 잘못된 설정이 없는지 마지막으로 확인 후 사용자를 만듭니다.
Github Actions에서 AWS 인증을 하려면 엑세스 키가 필요합니다.
위에서 생성된 사용자를 선택하고 보안 자격 증명 탭에서 엑세스 키 만들기를 선택하십시요.
기타를 선택하고 다음을 클릭합니다.
태그도 생략하고 엑세스 키 만들기를 클릭하십시요
💡 ACCESS_KEY와 SECRET_ACCESS 키를 복사하여 일단 메모장 등에 저장하시기 바랍니다.이 두 개의 키 값을 사용해서 IAM 권한을 획득할 수 있습니다.
우리는 이걸 Github Actions 에서 사용할 수 있도록 등록합니다.
Github Actions 을 적용하려는 Github > Repository > Settings > Secrets
로 이동해서 위 키 값들을 등록합니다.
키 이름은 적당히 편한 것으로 설정합니다.
Github Secrets 에 저장한다고 해도 값을 직접 확인할 수 없기 때문에 필요한 경우 따로 저장해둡니다.
지금까지 우리는 서버를 띄울 EC2, 배포할 결과물을 저장할 S3, 배포를 도와줄 CodeDeploy 이렇게 총 세 가지 AWS 서비스를 만들었습니다.
이제 CodeDeploy 에서 배포를 위해 참조할 AppSpec 파일을 작성합니다.
AppSpec 파일을 사용해서 우리는 프로젝트의 어떤 파일들을 EC2 의 어떤 경로에 복사할지 설정 가능하고, 배포 프로세스 이후에 수행할 스크립트를 지정하여 자동으로 서버를 띄울 수도 있습니다.
AppSpec 파일은 프로젝트 루트 디렉토리에 위치해야 합니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app
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:
- source: /
destination: /home/ubuntu/app
overwrite: yes
배포 파일에 대한 설정입니다.
AppSpec "files" 섹션 문서를 참고하면 더 자세한 내용을 알 수 있습니다.
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
files 섹션에서 복사한 파일에 대한 권한 설정입니다.
AppSpec "permissions" 섹션 문서를 참고하면 더 자세한 내용을 알 수 있습니다.
hooks:
AfterInstall:
- location: scripts/stop.sh
timeout: 60
runas: ubuntu
ApplicationStart:
- location: scripts/start.sh
timeout: 60
runas: ubuntu
배포 이후에 수행할 스크립트를 지정할 수 있습니다.
일련의 라이프사이클이 존재하기 때문에 적절한 Hook 을 찾아 실행할 스크립트를 지정하면 됩니다.
위 코드에서는 파일을 설치한 후 AfterInstall
에서 기존에 실행중이던 애플리케이션을 종료시키고 ApplicationStart
에서 새로운 애플리케이션을 실행합니다.
AppSpec "hooks" 섹션 문서를 참고하면 더 자세한 내용을 알 수 있습니다.
바로 위 AppSpec Hooks 에서 실행할 스크립트 stop.sh
와 start.sh
를 설정했습니다.
이제 수행할 스크립트 파일을 작성합니다. 배포 스크립트 파일은 프로젝트 루트 하위에 scripts 폴더를 생성하고 그 안에 작성했습니다.
stop.sh
#!/usr/bin/env bash
PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/final-8-team-project-0.0.1-SNAPSHOT.jar"
DEPLOY_LOG="$PROJECT_ROOT/deploy.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
애플리케이션이 이미 떠있으면 종료하는 스크립트입니다.
💡 JAR_FILE 에 들어갈 파일은 프로젝트 루트/build/lib 폴더에서 확인할 수 있습니다. *-plain.jar 파일은 무시하세요.start.sh
#!/usr/bin/env bash
PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/final-8-team-project-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
애플리케이션을 실행하는 스크립트입니다.
Github Actions 워크플로우에서 이미 빌드는 마쳤기 때문에 JAR 파일만 복사 후 실행합니다.
build.gradle
파일 수정위 스크립트를 보면 /build/libs/*.jar
파일을 $JAR_FILE
파일로 복사합니다.
그런데 Spring Boot 2.5 버전부터는 빌드 시 일반 jar 파일 하나와 -plain.jar
파일 하나가 함께 만들어집니다.
그래서 빌드 시 plain jar 파일은 만들어지지 않도록 build.gradle
파일에 다음 내용을 추가해야 합니다.
jar {
enabled = false
}
build.gradle.kts
파일은 이렇게 작성하시면 됩니다.
tasks.getByName<Jar>("jar") {
enabled = false
}
이제 필요한 사전 작업은 모두 끝났으니 Github Actions 워크 플로우만 작성하면 됩니다.
Github Actions CI 편에서는 기본적으로 제공되는 gradle 샘플을 수정했지만 배포 플로우는 거의 다 수정해야 하므로 그냥 가장 심플한 워크 플로우를 선택합니다.
deploy.yml
파일 작성name: Deploy to Amazon EC2
on:
push:
branches:
- main
# 본인이 설정한 값을 여기서 채워넣습니다.
# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
AWS_REGION: ap-northeast-2
S3_BUCKET_NAME: sparta-spring-cicd-s3-bucket
CODE_DEPLOY_APPLICATION_NAME: cicd-backend
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: cicd-codedeploy-deployment-group
permissions:
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
# (1) 기본 체크아웃
- name: Checkout
uses: actions/checkout@v3
# (2) JDK 17 세팅
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
# (3) Gradle build (Test 제외)
- name: Build with Gradle
uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
with:
arguments: clean build -x test
# (4) 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) 빌드 결과물을 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 .
# (6) 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
전체 파일은 위와 같으며 스텝 별로 간단한 주석을 달아두었습니다.
결국 프로젝트를 빌드한 후 AWS S3 버킷에 푸시 후 CodeDeploy 를 수행하는 겁니다.
(4), (5), (6) 에 대해서만 간략한 설명을 덧붙이겠습니다.
# (4) 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 }}
AWS 에 접근하기 위해 인증하는 스텝입니다.
우리는 위에서 IAM 사용자를 만든 후 Access Key, Secret Key 를 Github 레포에 저장했습니다.
그러면 secrets
변수를 통해 우리가 저장한 키 값들을 가져와서 사용할 수 있습니다.
필요한 Access Key, Secret Key 등을 프로젝트 코드에 노출시키지 않은 채로 사용할 수 있다는 편리함이 있습니다.
# (5) 빌드 결과물을 S3 버킷에 업로드- name: Upload to AWS S3
run: |
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
--ignore-hidden-files \
--source .
원하는 파일들을 압축해서 AWS S3 에 업로드 하는 스텝입니다.
공식 문서를 참고하면 더 자세한 정보를 알 수 있습니다.
-application-name
: CodeDeploy 애플리케이션 이름-s3-location
: 압축 파일을 업로드 할 S3 버킷 정보-ignore-hidden-files
(optional): 숨겨진 파일까지 번들링할지 여부$GITHUB_SHA
라는 변수가 보이는데 간단하게 생각해서 Github 자체에서 커밋마다 생성하는 랜덤한 변수값입니다. (자세한 정보는 Github Context 참조)
이렇게 랜덤한 값을 사용하면 파일 업로드 시에 이름 중복으로 충돌날 일이 없습니다.
# (6) 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
위 스텝에서 S3 에 저장한 파일을 EC2 에서 땡겨온 후 압축을 풀고 스크립트를 실행합니다.
공식 문서를 참고하면 더 자세한 정보를 알 수 있습니다.
-application-name
: CodeDeploy 애플리케이션 이름-deployment-config-name
: 배포 방식인데 기본값을 사용-deployment-group-name
: CodeDeploy 배포 그룹 이름-s3-location
: 버킷 이름, 키 값, 번들타입이제 모든 세팅이 끝났으므로 배포를 진행해봅니다.
yaml 파일로 설정한 것처럼 main 브랜치에 push 되는 경우 Github Actions Workflow 가 수행됩니다.
워크 플로우가 정상적으로 수행되면 이렇게 커밋에 체크 표시가 생깁니다.
"Details" 를 눌러보거나 "Actions" 탭으로 이동하면 수행한 스텝이 나옵니다.
만약 실패한다면 어떤 스텝에서 실패했는지 확인할 수 있습니다.
!
AWS CodeDeploy 메뉴로 이동하면 배포 내역을 확인인할 수 있습니다.
EC2 서버에 접속해서 확인 해보면 Spring Boot 프로젝트 코드가 있으며 서버도 정상적으로 떠있는 것을 확인할 수 있습니다.
/var/log/aws/codedeploy-agent/codedeploy-agent.log
/opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log
참고
https://www.daleseo.com/github-actions-basics/
스파르타 ci/cd 강의자료
https://github.com/jojoldu/freelec-springboot2-webservice/issues/806
CD 구현 중 에러 해결