Infra/CI, CD

Jenkins → GitHub Action 이전기 (GitHub Action으로 AWS CICD 구축 하기, AWS Code Deploy)

민돌v 2024. 2. 21. 18:00
이번글은 GitAction 의 사용 방법에 대한 기록입니다.

 

서두

  • 현재 사이드 프로젝트의 CI/CD 프로세서 구조는 Jenkins 를 활용하고있습니다.
  • GItHub Merge → Jenkins (Main Branch Build & Jar file deploy) → ec2 shell script 실행 의 구조인데
    Jenkins 를 사용하면 몆가지 불편한 점이 생겨 Git Action을 사용하는 방안으로 변경해보고자 합니다.

 

불편했던 점

  1. EC2 프리티어가 끝나고, test 코드가 많아지면서 빌드시간과 용량이 커져 비용이 늘어가는게 보인다.
  2. Jenkins 서버가 메모리 과부하로 종료되는 경우가 발생해 복구하는게 번거롭다
  3. Jenkins 자체적으로 Job의 다양한 구성방식이 존재해 여러 Job 의 관리가 번거롭다고 느껴진다. (각 Job의 구성방식이 다를경우)

 

GitHub Action 을 사용하려는 이유

  1. 사용하는 Repository가 Public 일 경우 무료이다. (Private 일 경우, 1달에 500MB, 2,000분까지 무료)
  2. Jenkins 와 달리 GitHub Action 은 자체적인 컴퓨팅 자원을 제공해주어 따로 물리서버를 관리하지 않아도 된다.
  3. GitHub Action 의 컴퓨팅 스펙은 아래와 같아 사이드 프로젝트에 적용하기에 충분하고도 남는다고 생각이 들었다.
    • 2-core CPU
    • 7 GB of RAM memory
    • 14 GB of SSD disk space
  4. GitHub Action 사용시 해당 Repo 에 대한 CI/CD 관리가 GitHub Repository 한 공간에서 관리할 수 있다.
  5. YAML 파일 하나로 구축할 수 있고, Git Repo에서 CI/CD 구축 과정에대한 버전관리가 가능해진다.

 

이러한 이유로 사용하던 Jenkins 를 접고 GitHub Action으로 넘어가면서, 해당 과정을 기록하고자 합니당

 

 

 

[목차]

  1. GitHub Action 이란
  2. GitHub Action Workflow 생성하기
  3. Github Actions Workflows 구성하기
  4. Github Actions - Git Submodule 연동
  5. Github Action 배포
    1. Github Action 에서 AWS 에 Access 하기
    2. Github action으로 aws s3 upload 하기
    3. Github action 에서 AWS Code Deploy 실행하기
  6. Github Action 종속성 캐시 설정하기
  7. github action yml 전체 코드
  8. 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 을 연동해보고자 합니다.

git submodule repo

매우 간단하게 작업할 수 있습니다. (이게 Github action의 GitHub에 대한 연동성 장점이 아닐까요..?)

  1. steps 에서 Check Out 작업 시점에 설정할 수 있었습니다.
  2. [with] 키워드의 하위에 submodules: true 를 통해 Submodules 을 연동할 수 있습니다.

3. token 은 Git Action 에서 Private Repository 에 접근하기 위해 필요한 값입니다.

  • 해당 Repository 의 [Setting - Security - Secretes and variables - actions] 탭에서 만들 수 설정할 수 있습니다.


5. Github Action 배포

배포는 AWS CodeDeploy 서비스를 사용하여 진행하고자 합니다.

✔️ AWS CodeDeployaws 환경에서는 무료(s3 의 용량또한 미미함)이며, blue-grean 배포 방식을 선택하여 실행할 수 있기때문에 무중단 배포에 가깝다는 장점이 있었습니다.

[과정]

  1. git actions build → make jar
  2. s3 uploade (버전 관리)
  3. 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 자체 설정으로 파일의 생명주기도 설정 할 수 있습니다.)

yml

// 서버의 로그들을 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

AWS cli 를 이용하여 s3 에 upload

 


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

 


조건

  1. Code Deploy 에서 사용할 s3 에 저장된 파일은 압축 파일 형식일 것 (zip, tar, tar.gz, tgz)
  2. 저장된 파일안에는 [실행할 어플리케이션, AppSpec File(appspec.yml)]이 포함될 것 (참고1, 참고2)
  3. 배포할 EC2 물리서버에는 Code Deploy Agent 서비스가 다운받아져 있을 것 (참고)

✔️ 1번 조건 (s3 압축파일 업로드)

  • 자 원래는 jar파일만을 올리고 싶었으나, 어차피 github action 도 무료(조건부) - code deploy도 ec2 배포면 무료, s3 에 압축파일 올리면 용량도 얼마 안나갈거 같아서,, 프로젝트를 통으로 압축해서 올리고자 합니다.
  • 그래도 버전관리를 위해 Github action 에 Date 환경변수를 설정하여 넘겨주었습니다. (Time zone 을 설정하지 않으면 utc 기준입니다.)

zip 파일 생성
생성된 파일 s3 업로드


✔️ 2번 조건 (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. 종속성 캐시

 

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 파일을 배포하여 실행하는 과정이며

그 순서는

  1. github [commit/push]
  2. github action [CI]
  3. ec2 upload
  4. code deploy 실행 (blue/grean → auto scaling) [CD]
  5. EC2 에 jar 배포 후 백그라운드 실행 입니다!

 

나중에 auto scaling으로 인한 scale-out 이 빈번하게 일어난다면 비용 및 관리상 ELB로 전환해보는 것도 생각해보면 좋을거같네요

 

끝!

 

 


 

🚀 Jenkins → GitHub Action 이전기

GitHub Action

AWS CI CD 구축 하기

 

 


참고