반응형

개요

  • 현재 CD 는 어떤 구성으로 되어있는가?
  • 잘 돌아가나? 
  • 문제점 파악 및 효율적인 방안 고민
  • CodeBuild + Github Action 구성 방안
  • 현재는?

 

현재 회사에서는 Github Action을 Self Hosted 로 사용하고있다 (2024.8 ~ 현재까지)

조금은 특이? 하게 사용하고 있다.

원래의 Action을 Self Hosted로 사용하는 대부분의 활용예제는 EC2 Computing 1대 = Action 방식으로 사용하고 있었고,

내가 생각했을때는 좀 비효율적인 방식이라고 생각하여, Computing Spec 하나에 Github Action Image를 여러개 둬서 병렬 처리로 하는 방식을 활용했다.

 


현재 CD는 어떤구성으로 되어있는가?

 

 

대략적인 구조도는 이런 형태로 되어있다. 순서대로 CD 구성을 보면

  1. 개발자는 Git Repository에 Merge / Tag 배포를 진행하게 된다.
  2. 자체 Action 내에서 Lambda를 호출하고 해당 Lambda 함수는 Self Hosted로 된 EC2를 (Stopping -> Running) 상태로 변경한다.
  3. 이 후 Running 상태의 EC2는 각각의 action 이미지를 빌드하여 CD를 진행하게 된다.

잘 돌아가나?

6~7 개월 사용하다보니, 잘 돌아가긴 하지만 살짝살짝 애매한 부분이 하나씩 들어났다.

 

1. 동작의 불확실 성

아무래도 람다 - EC2 형태로 구성하다보니, 가끔 Ec2가 안뜨거나, Lambda 함수가 호출이 안되는 등 

꽤 여러문제가 발생하였다. 사실 비용문제때문에 Action이 안돌때는 저렇게 한건데....

 

저 형태를 떼어내려 하니, 결국 사용하지 않을때는 EC2는 Stop 형태로 만들어야 한다 이런 결론이 나왔다.

2. EC2 관리

Self hosted로 된 EC2를 관리하다보니, 처음 EC2 + Action로 관리를 하니 아래파일들을 만들게되었다.

파일이름 내용
Dockerfile Docker 환경에서 Action runner 실행을 위한 파일
runner-install.sh Github Action Runner 추가 스크립트
runner-remove.sh Github Action Runner 삭제 스크립트
runner-docker-up.sh 람다에서 호출하는 Action Runner 추가 스크립트
runner-docker-down.sh 람다에서 호출하는 Action Runner 제거 스크립트
runner-instance-update.sh EC2 최신상태 유지 (리눅스 패키니, Action Runner, Image)
relaim-disk-space.sh 오래된 리소스 정리 (Disk, Docker System, Volume...)
slack-noti-sh Slack Noti

 

사실 이런 문제 말고도, EC2를 관리하는 유지보수는 적지 않았다.

Multi Account로 관리하고 있는 만큼 환경 별로 EC2를 1개씩 둬야 했고,

그에따라 디스크 정리 및 여러부분을 관리하였다 (cron이나 여러부분에서 자동화를 하더라도... 문제는 존재함)

 


문제점 파악 및 효율적인 방안 고민

그럼 총 2가지 측면에서 고려해봐야 한다고 생각했다.

  1. 동작의 불확실성을 최대한 피하고, 사용할때만 CD 구성이 필요함
  2. EC2 자체의 관리를 최소화 하며, 설치 및 유지보수 비용이 간단해야 한다.

그 과정에서 고민한 결과들을 보면 하나로 대락적으로 모아졌다. -> Serverless

 


CodeBuild + Github Action 구성

언젠가 본적이 있었다 (2024년 인가...)

그 당시에는 굉장히 experiment 한 기능으로 생각했는데, 어쩌면 우리가 찾는 기능일지 몰라 테스트를 진행하고

관련해서 정말 간단하게 구성해보자고 팀원들과 PoC를 진행하였다.

 

PoC 및 실제 환경에 적용하기 위한 단계를 만들었는데 아래와 같다.

  • CodeBuild 구성을 어떻게 관리할 것인가?
  • 각 환경별로 Action 호출을 어떻게 할 것 인가?
  • Action 스크립트는 어떻게 관리할 것 인가?

CodeBuild 구성을 어떻게 관리할 것인가?

이건 꽤 간단하였다.

우리 우리팀은 Terraform으로 대부분의 AWS 인프라를 관리하고 있었고,

개발 / 스테이징 / 운영 별로 하나하나씩 만들되 구성 자체는 Terraform 으로 관리하는 것을 목표로 하였다.

또한 CodeBuild의 경우 buildspec 을 Update를 해줘야 하는데, 그 부분도 Terraform 으로 관리하는것으로 진행하였다.

 

version: 0.2

env:
  parameter-store:
    DOCKERHUB_PASS: /common/DOCKERHUB_PWD
    DOCKERHUB_USERNAME: /common/DOCKERHUB_USERNAME

phases:
  install:
    commands:
      - echo $CODEBUILD_BUILD_ID
      - echo "Installing AWS Copilot CLI for ARM64..."
      - aws s3 cp s3://[s3-bucket]/ssm ssm
      - chmod +x ssm
      - sudo mv ssm /usr/local/bin/ssm

  pre_build:
    commands:
      - docker login --username $DOCKERHUB_USERNAME --password $DOCKERHUB_PASS

  post_build:
    commands:
      - export CODEBUILD_PROJECT_NAME=$(echo $CODEBUILD_BUILD_ID | cut -d":" -f1)
      - S3_ARTIFACT_FILE="s3://codebuild-${CODEBUILD_PROJECT_NAME}/results/failure_build_${CODEBUILD_BUILD_ID}.txt"
      - export CODEBUILD_BUILD_SUCCEEDING=2
      - aws s3 ls "$S3_ARTIFACT_FILE" > /dev/null 2>&1 || (echo "No error artifacts."; export CODEBUILD_BUILD_SUCCEEDING=1;)
      - if [ "${CODEBUILD_BUILD_SUCCEEDING}" -ne 1 ]; then
          aws s3 cp "$S3_ARTIFACT_FILE" "build-status.txt";
          if grep -q "BUILD_FAILED=true" ./build-status.txt; then
            echo "Build failed in GitHub Actions.";
            export CODEBUILD_BUILD_SUCCEEDING=0;
          else
            echo "Build succeeded in GitHub Actions.";
          fi
        fi
      - if [ "${CODEBUILD_BUILD_SUCCEEDING}" -eq 0 ]; then exit 1; fi

 

 

각 환경별로 Action 호출을 어떻게 관리할 것 인가?

개발 / 스테이징 / 운영별로 Action을 만들고 러너의 대한 호출은 Action에 Workflow 이름 패턴으로 구분하였다.

code build 에서는 필터그룹을 활용하여, 실제 부합하는 조건에 Action만 호출할 수 있다.

  • ^(action.*dev-deploy)$
  • ^(action.*stg-deploy)$
  • ^(action.*prd-deploy)$

 

Action 스크립트는 어떻게 관리할 것인가?

 

기본적으로 CD 구성은 Service Repository 와 CI/CD 에필요한 파일들은 구분해놨기 때문에,

해당 부분은 아래 그림과 같이 구성하였다. (그림으로 대체)

 

name: action-[service]-dev-deploy
on:
  push:
    tags:
      - '[service]-dev-20[2-3][0-9][0-1][0-9][0-3][0-9]-[0-9][0-9]'
  workflow_dispatch:

## [Required] 
env:
  ENVIRONMENT: dev
  SERVICE_NAME: [service]
  SLACK_CHANNEL: [slack-channel]
  S3_BUCKET: [s3-bucket]
  DOCKERFILE_PATH: [dockerfile-path]

## [Require Change runs-on]
jobs:
  build:
    runs-on:
      - [codebuild-name]-${{ github.run_id }}-${{ github.run_attempt }}
      - buildspec-override:true
    strategy:
      matrix:
        node-version: [20.x]

    steps:
      - name: Checkout tag
        uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name: Get package version
        run: |
          echo "CURRENT_TAG=$(node -p "require('./apps/[service]/package.json').version")" >> $GITHUB_ENV
          echo tag $CURRENT_TAG
      - name: Noti Workflow start
        uses: 8398a7/action-slack@v3
        with:
          icon_emoji: ":fire:"
          channel: ${{ env.SLACK_CHANNEL }}
          status: ${{ job.status }}
          author_name: "[Devops] ${{ env.SERVICE_NAME }}-${{ env.ENVIRONMENT }}"
          fields: repo,message,commit,author,action
          text: "(1 / 2) Action Start - Tag: ${{ env.CURRENT_TAG }}"
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DEVOPS_NOTI }}
        if: always()

      # [Require Change] ref ... 
      - name: Checkout build script repository
        uses: actions/checkout@v4
        with:
          repository: [build repostiroy name]
          token: ${{ secrets.ACCESS_TOKEN }}
          path: devops-repo
          ref: main

      - name: Run build script
        run: |
          $SHELL devops-repo/repository/${{ github.repository }}/${{ env.SERVICE_NAME }}/build-deploy.sh $SERVICE_NAME $ENVIRONMENT $CURRENT_TAG $S3_BUCKET $DOCKERFILE_PATH
      - name: Get and upload failed build status
        if: ${{ failure() }}
        run: |
          CODEBUILD_PROJECT_NAME=$(echo $CODEBUILD_BUILD_ID | cut -d":" -f1)
          echo "BUILD_FAILED=true" > build-status.txt
          aws s3 cp build-status.txt s3://codebuild-${CODEBUILD_PROJECT_NAME}/results/failure_build_${CODEBUILD_BUILD_ID}.txt
      
      ## Action Success
      - name: Noti Workflow Success
        if: success()
        uses: 8398a7/action-slack@v3
        with:
          icon_emoji: ":white_check_mark:"
          channel: ${{ env.SLACK_CHANNEL }}
          status: ${{ job.status }}
          author_name: "[Devops] ${{ env.SERVICE_NAME }}-${{ env.ENVIRONMENT }}"
          fields: repo,message,commit,author,action
          text: "(2 / 2) ✅  *Action* - Tag: ${{ env.CURRENT_TAG }}"
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DEVOPS_NOTI }}

 


현재는? 잘 돌아가나...

아직까진 문제는 없었다.

기본적으로 Serverless 형태로 돌아가 문제로 삼았던 부분은 해결되었다.

BootStrapping 시간도 1~2분 대에 끝나기 때문에 Build 시간이 문제가 되진 않았다.

캐시같은 경우에는 Local Cache가 아닌, --cache-from 로 해결하였다.

 

반응형

+ Recent posts