CICD 구축 Github action + code deploy + s3(추가. ASG Blue-Green 무중단 배포)

taehee kim·2023년 1월 20일
1


0. 기술 적용 배경

  • 백엔드 코드 수정시 매번 로컬에서 빌드한 후 scp 커맨드를 활용해서 빌드파일을 넘겨주고 터미널에서 실행하는 식으로 수동으로 배포.
    • 배포과정에서 실수도 자주 나올 뿐만 아니라 본인에게만 익숙한 방식이라 팀 단위로 일할 경우 다른 팀원들이 적용하기 힘들며 같은 시점에 배포를 시도하는 여러 팀원이 있을 경우 예상 못하는 충돌이 발생할 수 있음.

1. 기술 개념

CI/CD

  • CI/CD는 애플리케이션의 통합 및 테스트 단계에서부터 제공 및 배포에 이르는 애플리케이션의 라이프 사이클 전체에 걸쳐 지속적인 자동화와 지속적인 모니터링을 제공하는 파이프라인을 구축하는 것을 말함.

CI

  • "CI"는 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration)을 의미함.
  • 지속적인 통합이 제대로 구현되면 애플리케이션 코드의 새로운 변경 사항이 정기적으로 빌드 및 테스트를 거쳐 공유 리포지토리에 병합될 수 있음.
  • 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 하더라도 서로 충돌하지 않고 문제를 해결할 수 있음.

CD

  • "CD"는 지속적인 서비스 제공(Continuous Delivery) 및/또는 지속적인 배포(Continuous Deployment)를 의미함.
    • 리포지토리에 적용 된 개발자의 변경 사항이 프로덕션 환경에 자동으로 배포되는 것.

GIthub Action

  • Github Action이란 github repository내의 변경 사항을 자동적으로 빌드하고 테스트할 수 있게 하는 소프트웨어이다.
  • Jenkins와 달리 별도의 서버를 구동시킬 필요가 없다는 장점이 있다.

Code deploy

  • Code deploy란 배포 서비스로서 AWS의 EC2, ASG와 같은 서비스에 빌드파일을 자동적으로 배포하는데 활용할 수 있는 서비스이다.

2. 기술 적용 과정

1. EC2설정

2. EC2 IAM Role 생성

  • EC2가 S3와 Code deploy에 접근시키기 위해 Role을 부여해주어야한다.
  • role 생성 창에서
    • s3fullaccess, codedeployfullaccess를 선택해 역할을 만들고
    • 생성된 역할을 ec2에 설정해준다.

3. S3

  • AWS S3란 Bucket에 정적 파일을 담을 수 있는 원격 저장소이다.
  • Github Action CI를 통해 빌드된 파일을 S3에 저장하고 Code deploy가 이를 배포 시 사용할 수 있다.
  • 설정 과정
    • 디폴트로 모두 놔 둔 상태로 Bucket을 생성해 주면 된다.
    • Public access를 Block하고 Github action과 Code deploy에서만 접근할 수 있게 해야한다.

4. IAM User 생성

  • github action에서 s3, code deploy에 접근 할 수 있도록 유저를 만들고 access, secret key를 생성한다.
  • User 생성 창에서
    • s3fullaccess, codedeployfullaccess를 선택해 유저를 만들고
    • 생성된 access, secret key를 따로 저장해준다.
      • 생성된 키는 github action secret으로 등록하고 github action에서 s3에 파일 등록시 인증키로 활용한다.

5. Code deploy 설정

  • IAM Role을 생성한다.
    • 위에있는 EC2, lambda가 아니라 밑에 서비스 목록에서 CodeDeploy를 선택하고 세가지 선택지 중에 다시 순수한 CodeDeploy를 선택한다.
    • 자동으로 정책이 하나 등록되어있는데 AWSCodeDeployRole 이대로 둔 상태로 생성한다.
  • Code deploy를 생성한다.
  • Code deploy그룹을 생성한다.
    • Role을 위에서 생성한 IAM Role로 생성해 준다.
    • 환경 구성에서 EC2를 지정해준 태그를 사용하여 지정한다.

6. Github Action 설정

  • github repository의 settings -secrets-actions 에서 다음 세 변수를 설정한다.(4에서 생성된 키값)
    • AWS_ACCESS_KEY_ID
    • AWS_REGION
    • AWS_SECRET_ACCESS_KEY
  • github repository root 경로에서 .github/workflows라는 디렉토리를 만들고(github action 컨벤션이므로 반드시 경로를 지켜야함) 하위에 github action 설정파일을 작성한다. (deploy.yaml)
  • 작성된 결과
  • github action관련 참고자료
name: CI-CD

# 어떤 브랜치에 push시 github action CI를 동작시킬지
on:
  push:
    branches:
#      - dev
      - release/1.0.0
# 환경 변수 설정
# 생성한 Code Deploy와 S3의 이름을 설정한다.
env:
	CONTEXT: dev
  S3_BUCKET_NAME: s3-42partner-dev
  RESOURCE_PATH: ./module-api/src/main/resources/application.yml
  CODE_DEPLOY_APPLICATION_NAME: 42partner-CODE-DEPLOY
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: 42partner-CODE-DEPLOY-GROUP
# push 시 수행할 일들 정의
jobs:
  build:
# 실행 환경 이미지
    runs-on: ubuntu-latest
# 각 실행 스텝을 정의해준다.
# name은 실행 스텝의 이름으로 출력된다.
# uses는 github action의 특정한 action을 실행시키는 것이다.
# run은 커맨드를 실행시킨다.
# with는 uses에서 사용할 변수를 지정해준다.
# GitHub Actions 입장에서 바라보면 GitHub의 코드 저장소에 올려둔 코드를 
# CI 서버로 내려받은 후에 특정 브랜치로 전환하는 행위
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with: 
          java-version: 11

# github action에 secret으로 application.yml을 올려두고 빌드시 활용할 수 있도록 복사해준다.
      - run: mkdir ./module-api/src/main/resources
      - run: touch ./module-api/src/main/resources/application.yml
      - run: echo "${{ secrets.APPLICATION_DEV }}" > ./module-api/src/main/resources/application.yml
      - run: cat ./module-api/src/main/resources/application.yml

      - name: Grant execute permission for gradlew
        run: chmod +x ./module-api/gradlew
        shell: bash
# jar파일을 빌드한다.
      - name: Build with Gradle
        run: ./module-api/gradlew build -x test -Pprofile=${{ env.CONTEXT }}
        shell: bash
        
      - name: Make zip file
        run: zip -r ./$GITHUB_SHA .
        shell: bash

# AWS 계정의 key값을 입력하여 aws 커맨드를 사용할 수 있게 설정한다.
      - 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: ${{  secrets.AWS_REGION }}
     
# S3에 빌드된 파일을 전송해준다.
      - name: Upload to S3
        run: aws s3 cp --region ${{ secrets.AWS_REGION }} ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
# Code deploy에게 S3에 저장된 빌드파일을 배포하도록 명령 내린다.
      - name: Code Deploy
        run: |
          aws deploy create-deployment \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip

7. appspec.yml 설정

  • Code deploy에서 배포를 관리하는 데 사용되는 설정 파일.
version: 0.0
os: linux
# 파일의 source경로와 EC2내의 경로를 입력하고 파일이 중첩될 시 어떤 행동을 취할지 설정한다.
files:
  - source: /
    destination: /home/ec2-user/app/42Partner-Backend
    overwrite: yes
# 위에서 overwrite를 yes로 하더라도 파일이 중첩되면 배포가 실패할 수 있으므로 꼭 따로 또 설정한다.
file_exists_behavior: OVERWRITE

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

hooks:
  ApplicationStart:
# 배포 스크립트로 다음 스크립트를 활용한다.
    - location: scripts/gh_deploy.sh
      timeout: 60
      runas: root

8. 배포스크립트 작성

#!/bin/bash
PROJECT_PATH="/home/ec2-user/app/42Partner-Backend"
MODULE_NAME="module-api"
CONTEXT="dev"
JAR_PATH="$PROJECT_PATH/$MODULE_NAME/build/libs/*.jar"
DEPLOY_PATH=$PROJECT_PATH/$MODULE_NAME
DEPLOY_LOG_PATH="$PROJECT_PATH/$MODULE_NAME/deploy.log"
DEPLOY_ERR_LOG_PATH="$PROJECT_PATH/$MODULE_NAME/deploy_err.log"
APPLICATION_LOG_PATH="$PROJECT_PATH/$MODULE_NAME/application.log"
BUILD_JAR=$(ls $JAR_PATH)
JAR_NAME=$(basename $BUILD_JAR)

echo "===== 배포 시작 : $(date +%c) =====" >> $DEPLOY_LOG_PATH

echo "> build 파일명: $JAR_NAME" >> $DEPLOY_LOG_PATH
echo "> build 파일 복사" >> $DEPLOY_LOG_PATH
cp $BUILD_JAR $DEPLOY_PATH

echo "> 현재 동작중인 어플리케이션 pid 체크" >> $DEPLOY_LOG_PATH
CURRENT_PID=$(pgrep -f $JAR_NAME)

if [ -z $CURRENT_PID ]
then
  echo "> 현재 동작중인 어플리케이션 존재 X" >> $DEPLOY_LOG_PATH
else
  echo "> 현재 동작중인 어플리케이션 존재 O" >> $DEPLOY_LOG_PATH
  echo "> 현재 동작중인 어플리케이션 강제 종료 진행" >> $DEPLOY_LOG_PATH
  echo "> kill -9 $CURRENT_PID" >> $DEPLOY_LOG_PATH
  kill -9 $CURRENT_PID
fi
# spring.profiles.active는 application.yml에 명시하고 빌드시에 이미 포함됨.
DEPLOY_JAR="$DEPLOY_PATH/build/libs/$JAR_NAME"
echo "> DEPLOY_JAR 배포" >> $DEPLOY_LOG_PATH
nohup java -jar $DEPLOY_JAR >> $APPLICATION_LOG_PATH 2> $DEPLOY_ERR_LOG_PATH &

sleep 3

echo "> 배포 종료 : $(date +%c)" >> $DEPLOY_LOG_PATH

3. (추가) 단일 EC2가 아닌 ASG를 사용하는 Production 환경에 CICD적용.

  • main branch로 push가 일어나면 실제 운영환경에 배포자동화될 수 있도록 하는 부분을 추가하였다.
  • 운영환경에서는 ASG를 사용하기 때문에 Code deploy Group을 추가로 생성해야하고 Blue-Green deploy를 통해 무중단 배포하는 방향으로 구현하였다.

3-0. Blue-Green 무중단 배포

무중단 배포

  • 서버를 먼저 정지시키고 변경 후 새로 실행시키는 방식으로 배포를 하는 경우 기존의 서버 트래픽을 감당하던 instance의 일부가 갑자기 종료되기 때문에 예상치 못하는 여러 문제가 발생할 수 있다.
  • 무중단 배포란 기존 서버를 먼저 종료 시키지 않아 운영환경의 아키텍처 구조를 유지하면서 배포 하기 때문에 위와 같은 문제를 최소화 할 수 있다.
  • 기존 환경에서는 서비스를 종료 후 다시 실행시키면서 30초정도의 시간이 걸렸기 때문에 주로 사용량 이 없는 새벽 시간대에 배포하는 방법을 실행했고 이를 보완하기 위해 무중단 배포를 적용하게 되었다.

Blue-Green 배포

  • Blue-Green 배포는 기존의 배포 그룹(Blue group)을 그대로 유지한 상태로 새로운 버전의 배포 그룹(Green Gruop)을 같은 instance개수로 생성한 후 한번에 트래픽을 Blue에서 Green으로 전환하는 방법이다.

  • 무중단 배포의 방법은 Blue-Green 외에도 Rolling, Canary deploy등이 있지만 Blue-Green을 선택한 이유는 다음과 같다.

    • Rolling deploy의 경우 instance를 하나씩 교체하는 방식인데 동시에 여러 버전이 혼재할 수 있는 문제 때문에 선택하지 않았다.
    • Canary deploy의 경우 A/B Test등의 검증에 유리한 배포 방식이라고 알고 있는데 이에 대해 구현된 부분이 없기 때문에 선택하지 않았다.
    • Rollback 이 매우 빠르게 가능하다. 기존의 Blue로 다시 트래픽을 돌리면 된다.
    • 구현상 편리함: 이미 CICD를 AWS Code deploy를 통해 적용중이 었기 때문에 Code deploy Group을 새로 생성할때 몇가지 옵션을 선택하는 방식으로 간단하게 구현할 수 있다.

3-1. appspec.yml, deploy.sh.

  • 일단 code deploy에서 사용하는 appspec.yml과 Hook에서 실행될 deploy.sh 파일은 변경할 필요가 없다.
    • 모든 리소스를 같은 경로에 복사할 것이고 application start hook시 deploy.sh를 실행할 것이며 스크립트 실행 내용은 동일하기 때문이다.

3-2. github action ./github/workflows 에 prod용 파일 추가

  • main branch를 production에 사용할 것이기 때문에 on.push.branches 에 main을 넣어준다.
  • s3, code deploy등은 그대로 사용할 것이고 code deploy group만 새로 생성해주면 되기 때문에 CODE_DEPLOY_DEPLOYMENT_GROUP_NAME을 새로 생성한 code deploy group으로 변경해준다.
  • application.yml 파일을 따로 지정할 것이라면 github action secret에 추가해 줄수 있지만 필수 사항은 아니다.

CodeDeployDefault

  • AllAtOnce를 OneAtATime으로 꼭 변경해야한다.
  • AllAtOnce의 경우 한번에 모든 Instance를 개정하는데 이중 하나라도 성공하면 성공이라고 판단해버리기 때문에 원하는 수 만큼 개정이 이루어지지 않을 수 있다.
  • OneAtATime은 한번에 하나의 Instacne를 배포하며 마지막 Instance를 제외한 나머지 모든 Instance가 성공해야만 성공이라고 간주된다..
name: CI-CD

on:
  push:
    branches:
#      - dev
      - main
env:
  CONTEXT: prod
  S3_BUCKET_NAME: partner-cicd-s3
  RESOURCE_PATH: ./module-api/src/main/resources/application.yml
  CODE_DEPLOY_APPLICATION_NAME: 42partner-CODE-DEPLOY
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: 42partner-CODE-DEPLOY-PROD-GROUP

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      
      - name: Set up JDK 11
        uses: actions/setup-java@v1
        with: 
          java-version: 11

      - run: mkdir -p ./module-api/src/main/resources
      - run: touch ${{ env.RESOURCE_PATH }}
      - run: echo "${{ secrets.APPLICATION_PROD }}" > ${{ env.RESOURCE_PATH }}


      - name: Grant execute permission for gradlew
        run: chmod +x ./module-api/gradlew
        shell: bash
        
      - name: Build with Gradle
        run: ./module-api/gradlew build -x test
        shell: bash
        
      - name: Make zip file
        run: zip -r ./$GITHUB_SHA .
        shell: bash
        
      - 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: ${{ secrets.AWS_REGION }}
     
      - name: Upload to S3
        run: aws s3 cp --region ${{ secrets.AWS_REGION }} ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
        
      - name: Code Deploy
        run: |
          aws deploy create-deployment \
          --deployment-config-name CodeDeployDefault.OneAtATime \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip

3-3. Code deploy group 생성

  • github action을 통해 빌드된 파일이 S3에 저장되는 것은 동일하며 이후 Code deploy에서 프로덕션 환경에서의 ASG로 배포해야 하기 때문에 ASG설정과 Blue-Green배포를 포함한 Code Deploy Group을 새로 생성해준다.

생성 과정

  • Blue-Green선택
    • 자동적으로 auto scaling group 복제하기를 선택하고 ASG선택
    • Reroute traffic immediately- 성공시 즉시 트래픽을 라우팅해준다.
    • Terminate the original instances in the deployment group- Blue를 제거
      • 제거 시간을 지정해줄 수 있는데 제거 이전에는 롤백이 가능하기 때문에 그부분을 고려하여 적절한 시간을 선택한다.
    • Deployment configuration
      • 위에서 설명했듯이 CodeDeployDefault.OneAtATime로 설정해야한다.
    • Load Balancer- 트래픽을 전환해주기 위해 설정이 필요하며 Application Load Balancer를 선택하고 Target Group을 기존에 Load Balancer에 설정된것으로 지정한다.
    • Rollback을 원하는 경우 Rollback옵션을 활성화한다.

3-4. Load Balancer Target Group Health Check 옵션 조정

  • Target Group Health Check의 고급 옵션으로 들어가면 정상/비정상 Threshold와 check interval을 조정할 수 있는데 이를 너무 크게 할 경우 배포시간이 길어지기 때문에 적절히 조정한다.

3-5. does not give you permission to perform operations in the following AWS service: AmazonAutoScaling

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "ec2:CreateTags",
                "ec2:RunInstances"
            ],
            "Resource": "*"
        }
    ]
}

3-6. 배포 이전에 의문을 가졌던 것들.

Code deploy에의해서 배포 된 경우 ASG에 등록된 Launch Template은 변경 되지 않는데 Auto Scaling Group 정책에 의해서 Ec2가 실행 되는 경우 이전버전이 실행되지 않을까?

  • CICD시 S3빌드된 파일을 기반으로 배포가 진행되는데 만약 Launch Template을 바꾸지 않으면 ASG가 EC2를 자동 배포 할때 Launch Template으로 배포를 실행하여 이전버전이 실행될 것이라고 생각했다.
  • 이를 구글링해서 아무리 찾아도 관련내용이 없어서 수동으로 launch template을 업데이트 해야하나 고민하던 중에 code deploy 배포 기록을 보면서 해답을 찾게 되었다.

  • 위 사진은 Code deploy 배포 기록이다.
  • User action에 의해 발생하는 Blue/Green 배포는 CICD설정으로 인한 배포이고 위에 Autoscaling group action은 ASG scaling정책에 의한 배포이다.
  • ASG에 Code deploy Group을 지정해주면 Auto Scaling 시에도 code Deploy를 통해 배포가 이루어지고 이 때 최신 S3를 사용하여 배포하기 때문에 Launch Template을 수정하지 않아도 된다. 즉, ASG를 통해 새로 Ec2가 생성될 때도 동일하게 업데이트 된 상태로 배포 된다.
  • 따라서 Launch Template을 업데이트 해줄 필요는 없다.

Code deploy Hook에 대한 이해

Code deploy Hook 링크

  • 배포 실패 과정을 계속 지켜보고 이유를 살펴보면서 Code deploy Hook에 대해서 더 잘 이해하게 되었고 appspec.yml 파일의 내용을 심층적으로 알 수 있었다.
  • 큰 흐름
    • Start -> Bundle download -> install -> applicationStart -> change Traffic의 순으로 이루어지면 중간중간에 hook을 추가할 수 있다.
profile
Fail Fast

0개의 댓글