본문 바로가기

CICD

Github Action & Docker 를 사용해서 배포 속도 개선

지난번에 Docker를 사용해서 배포를 하였지만 도커 이미지를 만들고 허브에 업로드하는 작업이

속도가 꽤 걸리는 것 같았고, AWS EC2 서버에 API 서버를 올리기 때문에 

AWS를 사용해서 배포하는 것으로 변경하기로 하였다.

 

 

배포 과정

1. Github Action에서 코드 빌드 후 압축해서 S3에 Push

2. CodeDeploy 실행 

3. AppSpec로 EC2에 배포 명령 

4. EC2에서 S3에 있는 파일 가져온 후 EC2에 배포

 

 

 EC2 서버 생성 과정은 생략

EC2 설정

EC2 선택 -> 작업 -> 인스턴스 설정 -> 태그 관리

 

 

태그 관리에서 키로 태그를 추가합니다

 

EC2 -> 태그에서 해당 인스턴스에 대한 태그를 확인할 수 있습니다.

 

 

IAM 역할 추가

IAM -> 역할 -> 역할 만들기

 

AWS 서비스 -> EC2로 만든다

 

AmazonS3FullAccess 권한을 추가한다.

 

 

원하는 역할 이름을 입력한 뒤 생성

 

EC2 인스턴스 페이지 -> 작업 -> 보안 -> IAM 역할 수정

 

위에서 만들었던 IAM 역할을 추가한다.

 

 

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

 

명령어 입력

 

AWS S3 생성

S3 -> 버킷 만들기

원하는 버킷이름을 설정합니다.

추가적인 변경없이 기본값으로 버킷을 만들어줍니다.

 

 

CodeDeploy 생성

IAM -> 역할 만들기

CodeDeploy를 선택합니다.

 

역할 이름만 설정하고 IAM을 만들어줍니다.

 

 

CodeDeploy -> 배포 -> 애플리케이션 -> 애플리케이션 생성

 

이름을 설정하고 컴퓨팅 플랫폼을 EC2/온프레미스로 선택합니다.

 

만들어진 애플리케이션을 선택 후 배포그룹을 생성해줍니다.

 

배포 그룹 이름을 입력하고 위에서 만들었떤 CodeDeploy를 선택합니다.

 

환경 구성에서 태그로 설정해두었던 인스턴스를 선택하고 

배포 설정을 CodeDeployDefault.AllAtOnce를 선택합니다.

 

 

Github Action에서 사용할 사용자 추가

IAM -> 액세스 관리 -> 사용자 -> 사용자 추가

이름을 설정하고 직접 정책 연결을 합니다.

AWSCodeDeployFullAccess, AmazonS3FullAccess 2 가지 권한을 추가합니다.

 

사용자를 만들고 나면 Access Key와 Secret Key를 받을 수 있습니다.

 

Git Repository -> Settings -> Secrets  -> Actions 에서

Access Key와 Secret Key를 설정해줍니다.

 

AppSpec 파일 작성

AppSpec는 CodeDeploy에서 배포를 위해 참조하는 파일입니다.

프로젝트의 어떤 파일들을 EC2의 어떤 경로에 복사하고, 배포 프로세스 이후에 수행할 스크립트를 지정할 수 있습니다.

해당 파일은 Repository의 루트 디렉토리에 위치해야합니다.

 

appspec.yml

version: 0.0
os: linux

files:
  - source: /
    destination: /home/ubuntu/app
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  AfterInstall:
    - location: stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: start.sh
      timeout: 60
      runas: ubuntu

 

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes
  • source : 인스턴스에 복사할 디렉토리 경로
  • destination : 인스턴스에서 파일이 복사되는 위치
  • overwrite : 복사할 위치에 파일이 있는 경우 대체
permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu-
  • object: 권한이 지정되는 파일 또는 디렉터리
  • pattern (optional): 매칭되는 패턴에만 권한 부여
  • owner (optional): object 의 소유자
  • group (optional): object 의 그룹 이름

 

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu
  • location: hooks 에서 실행할 스크립트 위치
  • timeout (optional): 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨
  • runas (optional): 스크립트를 실행하는 사용자

 

stop.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/omo.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

 

start.sh

 

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/omo.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/omo.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

 

 

 

 

workflow

# 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:
  push:
    branches: [ "main" ]

permissions:
  contents: read

env:
  AWS_REGION: ap-northeast-2
  AWS_S3_BUCKET: omo-develop
  AWS_CODE_DEPLOY_APPLICATION: omo-deploy
  AWS_CODE_DEPLOY_GROUP: omo-deploy-group

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'corretto'

      - name: Copy appliaction.yml
        run: |
          echo "${{ secrets.PROD_YML }}" | base64 --decode > src/main/resources/application.yml          

      - name: chomod gralew
        run: |
          chmod +x gradlew

      - name: Build with Gradle
        run: |
          ./gradlew clean build -x test

      - name: AWS credential 설정
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: ${{ env.AWS_REGION }}
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}

      - name: Upload to AWS S3
        run: |
          aws deploy push \
            --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} \
            --ignore-hidden-files \
            --s3-location s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip \
            --source .

      - name: Deploy to AWS EC2 from S3
        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=$GITHUB_SHA.zip,bundleType=zip

workflow는 지난 docker 이미지 배포편에서 설명한 것을 제외한 것을 설명만 추가했습니다.

 

- 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 .
  • --application-name: CodeDeploy 애플리케이션 이름
  • --s3-location: 압축 파일을 업로드 할 S3 버킷 정보
  • --ignore-hidden-files (optional): 숨겨진 파일까지 번들링할지 여부

$GITHUB_SHA 라는 변수가 보이는데 간단하게 생각해서 Github 자체에서 커밋마다 생성하는 랜덤한 변수값입니다. 

랜덤한 값을 사용하여 파일 업로드 시 중복으로 충돌날 일이 없습니다.

 

- 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
  • --application-name: CodeDeploy 애플리케이션 이름
  • --deployment-config-name: 배포 방식인데 기본값을 사용
  • --deployment-group-name: CodeDeploy 배포 그룹 이름
  • --s3-location: 버킷 이름, 키 값, 번들타입

 

배포 확인

Github Action

CodeDeploy

 

 

만일 오류가 날 경우 해당 로그를 확인하여 수정해주면 됩니다.

  • CodeDeploy 배포 로그 확인: /var/log/aws/codedeploy-agent/codedeploy-agent.log
  • CodeDeploy Hooks 로그 확인: /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log

 

 

Docker -> AWS

Docker를 사용해서 배포를 진행했을 때는 평균적으로 시간대가 3분 40~50초 정도 걸렸지만

AWS를 사용함으로써 평균적으로 1분 40~50초 정도 걸리게 되었다.

 

도커 이미지 빌드 속도와 크기를 줄여서 배포 속도를 올려볼 수 있지 않을까 생각했지만 AWS로 배포하는 것보다 느릴 것으로 판단해서 이렇게 진행하게 되었다.