[CICD] Jenkins 대신 Github Action 으로 배포하기

Hocaron·2024년 4월 14일
3

DevOps

목록 보기
4/5

회사에서 배포 환경을 Jenkins 에서 Github Action을 변경하는 작업을 진행했다. 사용자인 나도 굳이 젠킨스에 접속하지 않고, Github 에서 CICD 를 할 수 있어 편했다.

그래서 진행하고 있는 사이드 프로젝트에 적용해보았고, 팀의 반응은 좋았다🎉
나는 별도의 배포 스크립트를 따로 작성해서 CD를 구성하였고, letsencrypt 를 활용해 인증서 등록 및 갱신도 스크립트로 해주었다.

AWS 비용을 최소화하려는 노력을 많이 하였다💸

배포는 어떻게 하는건가요?


배포 방법은 간단하다. Github Action 특정 워크 플로우에서 내가 배포하고 싶은 서비스 이미지 태그를 입력하면 된다. 물론 이미지는 ECR 등 팀에서 사용하는 도커 이미지 레지스트리에 존재해야한다.

배포 과정을 살펴보자

블루/그린 배포 방식으로 무중단 배포 방식을 선택했다.

Run workflow 버튼 클릭 시에 실행되는 Github action 코드

name: Heylu Server Dev CD

on:
  workflow_dispatch:
    inputs:
      tag:
        description: '개발환경에 배포할 서비스 버전을 입력합니다.'
        required: true

jobs:
  trigger:
    name: 개발 환경 ${{ inputs.tag }} 배포한다
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Custom Deployment Task
        env:
          TAG: ${{ inputs.tag }}
        run: |
          echo "Image Name: $IMAGE_NAME"
          echo "Tag: $TAG"

      - name: Excuting remote ssh commands
        uses: appleboy/ssh-action@v1.0.3
        env:
          TAG: ${{ inputs.tag }}
        with:
          host: ${{ secrets.REMOTE_IP }}
          username: ${{ secrets.REMOTE_USER }}
          key: ${{ secrets.REMOTE_PRIVATE_KEY }}
          envs: TAG
          script: |
            aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 281727968100.dkr.ecr.ap-northeast-2.amazonaws.com
            cd /home/ec2-user/heylu-server/deploy/dev
            sh deploy.sh $TAG
  1. workflow_dispatch:

    • 사용자가 서비스 버전을 입력하고 해당 버전을 배포할 수 있는 트리거이다.
    • inputs 내에 tag라는 인자를 받는다. 개발 환경에 배포할 서비스 이미지 버전을 의미한다.
  2. jobs:

    • trigger
      • 작업을 실제로 정의하는 부분이다.
      • 작업의 이름은 ${{ inputs.tag }}로 설정되며, 사용자가 입력한 서비스 버전이 포함된다.
      • runs-on을 사용하여 이 작업이 실행될 환경을 지정합니다. 여기서는 ubuntu-latest를 사용하여 Ubuntu 환경에서 실행된다.
  3. steps:

    • Checkout
      • GitHub 리포지토리를 체크아웃하는 단계입니다. actions/checkout@v4 액션을 사용하여 수행되며, fetch-depth: 1은 최신 커밋만 가져온다.
    • Custom Deployment Task
      • 사용자가 입력한 서비스 버전과 이미지 이름을 출력하는 단계입니다. 이전 단계에서 TAG를 환경 변수로 정의하고 있다.
    • Executing remote ssh commands
      • 원격 서버로 SSH 연결을 설정하고, AWS ECR에 로그인한 후에 원격 서버의 특정 디렉토리로 이동하여 배포 스크립트를 실행하는 단계이다.
      • uses를 사용하여 SSH 연결을 설정하는데 필요한 액션을 지정한다.
      • env를 사용하여 TAG 환경 변수를 정의한다. 사용자가 개발환경에 배포할 이미지 태그가 쉘 스크립트 환경 변수로 넘어가서 원하는 버전의 형상이 배포될 수 있도록 한다.
      • with를 사용하여 SSH 연결에 필요한 정보를 지정하고, script를 사용하여 원격 서버로 전달될 스크립트를 정의한다.

sh deploy.sh $TAG 에서 실행되는 deploy.sh 코드

#!/bin/bash

# 태그 값으로 받은 변수
export TAG=$1

# BLUE가 실행중인지 확인
APP_NAME=heylu-server
EXIST_BLUE=$(docker-compose -p heylu-server-blue -f docker-compose.blue.yml ps | grep heylu-server-blue)

if [ -z "${EXIST_BLUE}" ] # -z는 문자열 길이가 0이면 true.BLUE가 실행중이면 false
then
  # start blue
  START_CONTAINER=blue
  TERMINATE_CONTAINER=green
  START_PORT=8070
  TERMINATE_PORT=8071
else
  # start green
  START_CONTAINER=green
  TERMINATE_CONTAINER=blue
  START_PORT=8071
  TERMINATE_PORT=8070
fi

echo " ========== [start] change ${APP_NAME}-${TERMINATE_CONTAINER} to ${APP_NAME}-${START_CONTAINER} =========="

echo "[step 1] deploy ${APP_NAME}-${START_CONTAINER}"
docker-compose -p ${APP_NAME}-${START_CONTAINER} -f docker-compose.${START_CONTAINER}.yml pull
docker-compose -p ${APP_NAME}-${START_CONTAINER} -f docker-compose.${START_CONTAINER}.yml up -d
for RETRY_CNT in {1..10}
do
  echo "Health Check Start...(${RETRY_CNT})"

  HEALTH_CHECK_RESPONSE=$(curl -s http://127.0.0.1:${START_PORT}/health | grep 'UP')
  if [ -z "${HEALTH_CHECK_RESPONSE}" ] # 실행되었다면 break
  then
        echo "health check fail..${HEALTH_CHECK_RESPONSE}"
  else
        echo "health check success!"
        break
  fi

  if [ ${RETRY_COUNT} -eq 11 ]
  then
          echo "deployment failed."
          exit 1
  fi

  echo "wait 5 seconds..."
  sleep 5
done



echo "deploy ${APP_NAME}-${START_CONTAINER} success!"

# sed 명령어를 이용해서 아까 지정해줬던 service-url.inc의 url값을 변경해줍니다.
# sed -i "s/기존문자열/변경할문자열" 파일경로 입니다.
# 종료되는 포트를 새로 시작되는 포트로 값을 변경해줍니다.
# ex ) sudo sed -i "s/8080/8081/" /nginx/conf.d/app.conf
echo -e "\n[step - 2] change port ${TERMINATE_PORT} to ${START_PORT}"
sed -i "s/${TERMINATE_PORT}/${START_PORT}/" /home/ec2-user/heylu-server/deploy/infra-dev/nginx/conf.d/app.conf

# 새로운 포트로 스프링부트가 구동 되고, nginx의 포트를 변경해주었다면, nginx 재시작해줍니다.
# docker exec -it {nginx container name} nginx -s reload
echo -e "\n[step - 3] nginx reload.."
docker exec -i nginx nginx -s reload


# 기존에 실행 중이었던 docker-compose는 종료시켜줍니다.
echo -e "\n[step - 4] exit ${APP_NAME}-${TERMINATE_CONTAINER}"
docker-compose -p ${APP_NAME}-${TERMINATE_CONTAINER} -f docker-compose.${TERMINATE_CONTAINER}.yml down

echo "exit ${APP_NAME}-${TERMINATE_CONTAINER} success!"

echo " ========== [end] change ${APP_NAME}-${TERMINATE_CONTAINER} to ${APP_NAME}-${START_CONTAINER} =========="
  1. 환경 변수 설정:

    • 스크립트가 실행될 때 전달된 첫 번째 인자를 환경 변수 TAG에 할당한다.
    • export TAG=$1
  2. 블루/그린 배포 전략 선택:

    • 현재 블루 또는 그린 컨테이너가 실행 중인지 확인한다.
    • 만약 실행 중이지 않다면, 블루 컨테이너를 시작하고 그린 컨테이너를 종료할 준비를 한다.
    • 그렇지 않다면, 그린 컨테이너를 시작하고 블루 컨테이너를 종료할 준비를 한다.
  3. 컨테이너 배포:

    • 시작할 컨테이너의 Docker 이미지를 pull하고, 해당 컨테이너를 백그라운드에서 실행한다.
    • 건강 상태 체크를 위해 여러 번의 요청을 보내며, 컨테이너가 정상적으로 실행됐는지 확인한다.
    • 만약 건강 상태 체크가 실패하면, 배포에 실패한 것으로 간주하고 스크립트를 종료한다.
  4. 포트 변경:

    • Nginx의 설정 파일에서 이전 포트를 새로운 포트로 변경한다.
    • sed -i "s/${TERMINATE_PORT}/${START_PORT}/" /home/ec2-user/heylu-server/deploy/infra-dev/nginx/conf.d/app.conf
  5. Nginx 재시작:

    • Nginx를 재시작하여 변경된 설정이 적용되도록 한다.
    • docker exec -i nginx nginx -s reload
  6. 이전 컨테이너 종료:

    • 이전에 실행 중이던 컨테이너를 종료한다.
    • docker-compose -p ${APP_NAME}-${TERMINATE_CONTAINER} -f docker-compose.${TERMINATE_CONTAINER}.yml down
profile
기록을 통한 성장을

0개의 댓글