Jenkins → GitHub Action 이전기 (GitHub Action으로 AWS CICD 구축 하기, AWS Code Deploy)
이번글은 GitAction 의 사용 방법에 대한 기록입니다.
서두
- 현재 사이드 프로젝트의 CI/CD 프로세서 구조는 Jenkins 를 활용하고있습니다.
- GItHub Merge → Jenkins (Main Branch Build & Jar file deploy) → ec2 shell script 실행 의 구조인데
Jenkins 를 사용하면 몆가지 불편한 점이 생겨 Git Action을 사용하는 방안으로 변경해보고자 합니다.
불편했던 점
- EC2 프리티어가 끝나고, test 코드가 많아지면서 빌드시간과 용량이 커져 비용이 늘어가는게 보인다.
- Jenkins 서버가 메모리 과부하로 종료되는 경우가 발생해 복구하는게 번거롭다
- Jenkins 자체적으로 Job의 다양한 구성방식이 존재해 여러 Job 의 관리가 번거롭다고 느껴진다. (각 Job의 구성방식이 다를경우)
GitHub Action 을 사용하려는 이유
- 사용하는 Repository가 Public 일 경우 무료이다. (Private 일 경우, 1달에 500MB, 2,000분까지 무료)
- Jenkins 와 달리 GitHub Action 은 자체적인 컴퓨팅 자원을 제공해주어 따로 물리서버를 관리하지 않아도 된다.
- GitHub Action 의 컴퓨팅 스펙은 아래와 같아 사이드 프로젝트에 적용하기에 충분하고도 남는다고 생각이 들었다.
- 2-core CPU
- 7 GB of RAM memory
- 14 GB of SSD disk space
- GitHub Action 사용시 해당 Repo 에 대한 CI/CD 관리가 GitHub Repository 한 공간에서 관리할 수 있다.
- YAML 파일 하나로 구축할 수 있고, Git Repo에서 CI/CD 구축 과정에대한 버전관리가 가능해진다.
이러한 이유로 사용하던 Jenkins 를 접고 GitHub Action으로 넘어가면서, 해당 과정을 기록하고자 합니당
[목차]
- GitHub Action 이란
- GitHub Action Workflow 생성하기
- Github Actions Workflows 구성하기
- Github Actions - Git Submodule 연동
- Github Action 배포
- Github Action 에서 AWS 에 Access 하기
- Github action으로 aws s3 upload 하기
- Github action 에서 AWS Code Deploy 실행하기
- Github Action 종속성 캐시 설정하기
- github action yml 전체 코드
- AWS CodeDeploy BlockTraffic 시간 단축
1. GitHub Action 이란
GitHub Actions은 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD(지속적 통합 및 지속적 전달) 플랫폼입니다.
리포지토리에 대한 모든 풀 요청을 빌드 및 테스트하거나 병합된 풀 요청을 프로덕션에 배포하는 워크플로를 생성할 수 있습니다.
✔️ 즉, GitHub Action 이란 Github에서 제공해주는 CI/CD 파이프라인 플랫폼 서비스 입니다.
2. GitHub Action Workflow 생성하기
- 먼저 생성을 원한는 Repository의 Actions 탭에 들어갑니다.
- 처음부터 작성할 예정이면 "set up a workflow yourself" 를 클릭하시면되고, 어느정도 구성된 파일을 원한다면 아래 템플릿도 존재합니다.
- 저는 [Publish Java Package with Gradle] 템플릿을 클릭해보았습니다.
↓
GitHub Action 을 사용하기 위해서는 해당 Repository 의 [./github/workflows] 폴더안에 Yml 파일이 존재해야합니다.
해당 과정은 그걸 위함입니다.
마치 GitHub 에서 바로 README.md 파일을 생성하든 YML 파일이생성되네요.
커밋 후 Actions 탭에 들어가보니 당연하게도 워크플로우 실행기록 또한 비어있습니다.
3. Github Actions Workflows 구성하기
✔️ 이제 Main Branch 에 Merge 혹은 Push 했을 경우 GIt Actions 이 실행되면서 build → test → deploy 가 되도록 workflow를 작성해보고자 합니다.
1) Name
- name : 해당 워크플로우에 대한 설명, 주석과 같은 느낌
- run-name : 옵션 선택사항이며, github actions 의 실행 리스트에서 표시되는 내용이라고 합니다. 해당 작업자가 누구인지 나타냅니다.
2) On
- github actions 가 실행될 트리거를 설정하는 문법입니다.
- 다양한 옵션이 있으며, 특정 PR 에 발행된 Tag 로 필터링 한다든지 등 다양한 옵션으로 트리거를 설정할 수 있습니다.
- 현재 해당 프로젝트는 아직 런칭하지 않았기 때문에 빠른 작업을 위해 Main Branch 에 Push (Merge 포함) 로 트리거를 설정해두었습니다.
- 다른 예제 링크 : docs
3) Jobs
- jobs : 워크플로우에 실행되는 모든 작업을 그룹화합니다.
- runs-on : 해당 작업들을 실행할 가상머신을 지정하는 구문입니다.
4) Steps
- Actions 에서 실행되는 작업의 단계를 그룹화하는 구문입니다.
- uses : steps 하위의 "uses" 키워드를 통해서 각 단계의 실행 작업을 지정하는 것 같습니다.
- 템플릿에 설정된 내용을 살펴보니 jdk 17 & gradle 을 설정한 후 해당 repository 를 빌드하는 것 같아
하위의 [./gradlew build] → [./gradlew clean build] 로만 수정해 주었습니다.
4. Github Actions - Git Submodule 연동
✔️ 이제는 주요한 계정정보가 담겨있는 Private 저장소인 Git Submodule 을 연동해보고자 합니다.
매우 간단하게 작업할 수 있습니다. (이게 Github action의 GitHub에 대한 연동성 장점이 아닐까요..?)
- steps 에서 Check Out 작업 시점에 설정할 수 있었습니다.
- [with] 키워드의 하위에 submodules: true 를 통해 Submodules 을 연동할 수 있습니다.
3. token 은 Git Action 에서 Private Repository 에 접근하기 위해 필요한 값입니다.
- 해당 Repository 의 [Setting - Security - Secretes and variables - actions] 탭에서 만들 수 설정할 수 있습니다.
5. Github Action 배포
배포는 AWS CodeDeploy 서비스를 사용하여 진행하고자 합니다.
✔️ AWS CodeDeploy 는 aws 환경에서는 무료(s3 의 용량또한 미미함)이며, blue-grean 배포 방식을 선택하여 실행할 수 있기때문에 무중단 배포에 가깝다는 장점이 있었습니다.
[과정]
- git actions build → make jar
- s3 uploade (버전 관리)
- code deploy 실행 → s3 결과 가져와서 EC2 배포
Code Deploy에 대한 자세한 설명은 생략하도록 하겠습니다. (블로그 참고 : code deploy설명 글 )
1) Github Action 에서 AWS 에 Access 하기
- 깃허브 액션의 워크플로우부터 설정해보면 아래와 같은 Job 단계가 필요합니다.
- AWS의 민감한 정보가 포함되니 위와같이 GitHub Action Secret 에 저장해주었습니다.
- AWS_REGION : 배포 환경이 존재하는 (EC2) 지역 위치
- AWS_ACCESS_KEY_ID : IAM 사용자 엑세스 키 ID
- AWS_SECRET_ACCESS_KEY : IAM 사용자 Secret 엑세스 키
💡 AWS 에서 IAM 유저를 생성해 줄때에는 [AWSCodeDeployFullAccess, AmazonS3FullAccess] 이 2개의 정책을 할당해 주어야합니다.
2) Github action으로 aws s3 upload 하기
✔️ AWS Configure 설정을 마쳤다면 AWS cli 명령어를 사용할 수 있습니다.
따라서, s3 에 upload 하고 ec2 deploy 하는 방법은 꽤 다양하게 할 수 있습니다. (참고 : aws cli docs)
[aws cli s3 upload 예시]
- 목적에 따라 `sync` 와 `cp` 2가지 명령어를 사용하여 upload 할 수 있습니다.
- 저는 이왕 s3 를 사용하는겸 배포 버전관리를 위해 cp 를 이용하기로 했습니다. (s3 자체 설정으로 파일의 생명주기도 설정 할 수 있습니다.)
// 서버의 로그들을 s3의 버킷에 동기화
aws s3 sync ./storage/logs s3://s3domain.com/logs
// 서버의 특정파일을 s3의 버킷에 복사 (이름지정해서 복사가능)
aws s3 cp ./storage/logs/mylog.log s3://s3domain.com/logs/mylog-cp.log
// 현재날짜 넣어서 복사
aws s3 cp ./storage/logs/mylog.log s3://s3domain.com/logs/mylog-`date +%Y%m%d`.log
// 현재날짜 넣어서 복사 (위와 동일함)
aws s3 cp ./storage/logs/mylog.log s3://s3domain.com/logs/mylog-`date +%F`.log
3) Github action 에서 AWS Code Deploy 실행하기
- 이제 s3 에 올라간 jar 파일을 Code deploy를 이용하여 배포하고자 합니다.
- Code Deploy를 사용하는 방법도 여러가지가 있으며, 제가 하고자하는 s3를 사용하여 EC2 에 즉시 배포하는 방법은 몆가지 조건이 필요합니다.
github action workflow
- 조건을 맞추기 전에 먼저, github action 에서 CI 완료 후 code deploy 를 실행시키기 위한 AWS CLI를 작성해봅시다
- 간단합니다. 먼저 AWS 콘솔에서 Code deploy 어플리케이션과 배포그룹을 만들어주어야 합니다.
- 이후 [aws deploy create-deployment] 명령어를 사용해 위해서 작업했던 S3 에 저장된 파일을 사용해 Code Deploy 배포를 생성해 실행해 주는 과정을 거칩니다.
- name: Deploy 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=${{ secrets.S3_DEPLOY_BUCKET }},key=deployfile-${{ steps.now.outputs.date }}.zip,bundleType=zip
조건
- Code Deploy 에서 사용할 s3 에 저장된 파일은 압축 파일 형식일 것 (zip, tar, tar.gz, tgz)
- 저장된 파일안에는 [실행할 어플리케이션, AppSpec File(appspec.yml)]이 포함될 것 (참고1, 참고2)
- 배포할 EC2 물리서버에는 Code Deploy Agent 서비스가 다운받아져 있을 것 (참고)
✔️ 1번 조건 (s3 압축파일 업로드)
- 자 원래는 jar파일만을 올리고 싶었으나, 어차피 github action 도 무료(조건부) - code deploy도 ec2 배포면 무료, s3 에 압축파일 올리면 용량도 얼마 안나갈거 같아서,, 프로젝트를 통으로 압축해서 올리고자 합니다.
- 그래도 버전관리를 위해 Github action 에 Date 환경변수를 설정하여 넘겨주었습니다. (Time zone 을 설정하지 않으면 utc 기준입니다.)
✔️ 2번 조건 (appspec file 추가)
- 참고 : https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file-structure-files.html
- 프로젝트를 통으로 압축하기로 했으니, 프로젝트 최상단 루트경로에 appspec file 을 추가해줍니다.
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/directory
permissions:
- object: /home/ec2-user/directory/
owner: ec2-user
group: ec2-user
hooks:
AfterInstall:
- location: deploy.sh
timeout: 60
runas: ubuntu
- version
- appspec.yml 파일 버전을 정의합니다. 현재는 0.0 이외의 버전이 지원되지 않습니다.
- files
- 배포할 파일 및 디렉토리를 정의합니다.
- source : 복사할 파일이 위치한 경로입니다.
- destination : EC2 서버내 복사될 경로 입니다.
- permissions
- 애플리케이션 파일에 대한 권한을 정의합니다. 위의 예시에서는 "/home/{계정 이름}/directory" 디렉토리의 그룹과 소유자를 설정합니다. (root 라면 ubuntu)
- hooks
- 배포 단계에서 실행할 훅 스크립트를 정의합니다. 위의 예시에서는 배포 후 실행할 스크립트인 deploy.sh 파일을 지정하고, 스크립트 실행 시간 제한을 60초로 설정합니다.
- AfterInstall
- 여러 배포 단계 중 AfterInstall 단계에서 스크립트를 실행합니다.
deploy.sh
#! /bin/sh
echo "Java Version"
java -version
echo "Start Spring Boot Application!"
CURRENT_PID=$(ps -ef | grep chatserver-0.0.1-SNAPSHOT.jar | grep java | awk '{print $2}')
echo $CURRENT_PID
if [ -z $CURRENT_PID ]; then
echo ">현재 구동중인 어플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -15 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 10
fi
echo ">어플리케이션 배포 진행!"
nohup java -jar ~/deploy/chatserver-0.0.1-SNAPSHOT.jar >> ~/deploy/logs/$(date '+%Y-%m-%d')_api.log 2>&1 &
✔️ 3번 조건 (code deploy agent 설정)
- EC2에 CodeDeploy로 지정한 위치에서 파일을 받아 실행하기 위해서는 Code Deploy Agent가 설치되있어야만 합니다.
- EC2에 콘솔로 접속한 후 아래의 명령어를 순서대로 입력해줍니다.
(1) Agent 설치파일을 다운
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
(2) 해당 파일에 실행권한을 추가
chmod +x ./install
(3) 설치 진행
sudo ./install auto
→ 만약 `ruby ./ no such file or directory` 에러 문구가 발생한다면 해당 파일이 ruby로 이루어져있으므로 ruby 를 다운받아야 합니다.
sudo apt install ruby
(4) code deploy agent 실행 확인
sudo service codedeploy-agent status
(5) EC2 인스턴스 재부팅 시, 자도응로 agent 실행하도록 Sh 파일 생성
sudo vim /etc/init.d/codedeploy-startup.sh
스크립트 내용 작성
#!/bin/bash
echo 'Starting codedeploy-agent'
sudo service codedeploy-agent restart
실행권한 추가
sudo chmod +x /etc/init.d/codedeploy-startup.sh
6. 종속성 캐시
- 참고 : https://docs.github.com/ko/actions/using-workflows/about-workflows#caching-dependencies
- 현재 프로젝트가 작아 build 속도가 그렇게 느리지 않지만 재밌는 문서를 봐서 미리 적용해 보고자합니다.
- gradle 의 반복적으로 다운되는 종속성들을 미리 캐싱해두어 github action에서 build 속도를 개선하는 방법입니다.
workflow
- path : 캐싱할 경로 - In Action 이 모두 끝나고 Post Action 에서 이 경로에 있는 모든 데이터를 캐싱
- key : 캐시 데이터를 구분하기 위한 키 - In Action 에서 캐시 데이터를 restore 할 때 사용
- restore-keys: restore 를 못했을 경우 다른 키를 사용하기 위한 대체키
- name: Cache with Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ github.repository }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ github.repository }}-gradle-
이를 적용하고 git action을 돌리면 아래 사진과 같이 Cache not found for input keys 로그가 나타납니다.
이는 에러가 아닌 첫 액션때 캐시 히트가 되지 않아서 입니다.
↓
2번째 acition부터는 이렇게 github 에 저장된 캐시 정보를 불러올 수 있습니다.
저장된 캐시항목을 보고싶다면 [action -> cache] 에서 볼 수 있습니다.
7. github action 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 package using Gradle and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
name: THT ChatServer
run-name: ${{ github.actor }} is learning GitHub Actions
on:
push:
branches:
- main
env:
AWS_CODE_DEPLOY_APPLICATION: aws-code-deploy-appliacation-name
AWS_CODE_DEPLOY_GROUP: aws-code-deploy-appliacation-deploy-group-name
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: CheckOut
uses: actions/checkout@v4
with:
token: ${{ secrets.ACTION_TOKEN }}
submodules: true
- name: Set up JDK 17
uses:
actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0
- name: Cache with Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ github.repository }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ github.repository }}-gradle-
- name: Build with Gradle
run: ./gradlew clean build
- name : set time zone
uses: szenius/set-timezone@v1.2
with:
timezoneLinux: "Asia/Seoul"
timezoneMacos: "Asia/Seoul"
timezoneWindows: "Seoul Standard Time"
- name: make env now date
id: now
run: echo "date=`date +%Y%m%d_%H:%M:%S`" >> "$GITHUB_OUTPUT"
- name: Make Zip File
id: file
run: zip -qq -r ./deployfile-${{ steps.now.outputs.date }}.zip .
shell: bash
- name: AWS credential
uses: aws-actions/configure-aws-credentials@v1
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Upload to AWS S3
run: aws s3 cp ./deployfile-${{ steps.now.outputs.date }}.zip s3://${{ secrets.S3_DEPLOY_BUCKET }}/deployfile-${{ steps.now.outputs.date }}.zip
- name: Deploy 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=${{ secrets.S3_DEPLOY_BUCKET }},key=deployfile-${{ steps.now.outputs.date }}.zip,bundleType=zip
8. AWS CodeDeploy BlockTraffic 시간 단축
- 추가적으로, 잦은 배포를 위해서는 빠른 속도가 생명인데 Code Deploy 실행 시 BlocTraffic 단계에서 너무 많은 시간을 잡아먹어 이를 개선해보고자 합니다.
- [ec2 > load balancing > Target groups > ... > attribute > edit ] 에서 Deregistration delay를 낮추면 됩니다.
- 저는 300 → 60으로 수정하였습니다.
대략 80% 빨라졌네요 👏
하나씩 정리하다보니 쓸데없이 길어졌는데 단순한 EC2 에 jar 파일을 배포하여 실행하는 과정이며
그 순서는
- github [commit/push]
- github action [CI]
- ec2 upload
- code deploy 실행 (blue/grean → auto scaling) [CD]
- EC2 에 jar 배포 후 백그라운드 실행 입니다!
나중에 auto scaling으로 인한 scale-out 이 빈번하게 일어난다면 비용 및 관리상 ELB로 전환해보는 것도 생각해보면 좋을거같네요
끝!
🚀 Jenkins → GitHub Action 이전기
GitHub Action
AWS CI CD 구축 하기
참고
- github action docs : https://docs.github.com/ko/actions/learn-github-actions/understanding-github-actions
- aws cli docs : https://awscli.amazonaws.com/v2/documentation/api/latest/index.html#
- aws code deploy 튜토리얼 : https://docs.aws.amazon.com/codedeploy/latest/userguide/applications-create.html