[Blue Green 무중단 배포] docker + nginx + git actions 으로 간단 구현

inho ha·2024년 6월 5일
0

인천의 한 수요일.
사이드 프로젝트에서 배포할 때마다 20초 정도 접속 불가한 것에 대해 찝찝함이 느껴졌다.
센서에서 쏜 정보를 저장하는 서버인데, 하필 배포 타이밍에 중요한 정보를 전송할까봐 무중단 배포를 적용하기로 했다.

Blue Green Deployment

기존에 떠있는 서버를 유지하고, 새로운 서버를 동시에 띄운 뒤에
새로운 서버가 정상적으로 뜬다면 새로운 서버를 사용해서 서비스하고
기존에 떠있던 서버는 내리는 방식

기존에 떠있는 서버가 Blue
새로운 서버가 Green
Green이 정상적으로 동작하는 것이 확인되면 Blue로 승격시키는 방식

장점

다운타임 최소화

  • 새로운 서버가 준비된 뒤에 트래픽을 블루에서 그린으로 전환하기 때문에 다운타임이 거의 없다.

빠른 롤백

  • 문제 발생 시, 트래픽을 다시 블루 환경으로 전환하여 즉시 이전 버전으로 돌아갈 수 있다.

단순함

  • 배포 프로세스가 단순하여 오류 발생 가능성이 낮다.

단점

비용 증가

  • 동시에 blue, green 두 개의 서버가 떠야 하기 때문에 일시적으로 두 배의 리소스를 필요로 한다.

구현 방법

다양한 방법이 있지만 최대한 간단하게 구현해 보는 것이 목적이다.
기존 CI/CD 로직을 조금 변경하여 간단하게 구현해 본다.
전체적인 프로세스는 아래와 같다.

  1. Docker image 를 빌드하여 DockerHub에 업로드
  2. ssh-action 으로 서버에 Docker image pull & up
  3. Curl 로 서버 동작 확인 이후
  4. nginx conf 파일에서 port 번호를 green port로 수정 & restart
  5. Blue Docker down
  6. Green 을 Blue 로 변경

Docker image 를 빌드하여 DockerHub에 업로드

name: Backend CD
on:
  push:
    branches: [ master ]

jobs:
  deploy:
    runs-on: ubuntu-22.04
    steps:
      - name: 저장소 Checkout
        uses: actions/checkout@v3

      - name: 도커 이미지 빌드 # (2)
        run: docker build -t ${{ secrets.DOCKER_IMAGE }} .

      - name: Docker Hub 로그인 # (3)
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Docker Hub 퍼블리시 # (4)
        run: docker push ${{ secrets.DOCKER_IMAGE }}

git workflows 으로 작성한 내용이다.
master branch에 머지 되면 실행되는 jobs

Repository secrets 으로 등록한 도커 이미지 파일 이름과 아이디 비밀번호를 사용하여 Docker Hub에 빌드한 이미지를 올린다.

ssh-action 으로 서버에 Docker image pull & up

      - name: Set Dynamic Project Name
        run: echo "PROJECT_NAME=deploy-${{ github.run_id }}" >> $GITHUB_ENV

      - name: Deploy to EC2
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            docker pull ${{ secrets.DOCKER_IMAGE }}:latest 
            docker compose -p ${{ env.PROJECT_NAME }} -f ~/docker-compose.green.yml up -d

github.run_id 는 git actions 가 실행되는 run_id 로 실행 시마다 변경된다.
이를 Docker project name으로 사용하여 Blue와 Green 을 구분한다.

아까 Dokcer Hub에 올린 이미지를 pull & up 해주고

Curl 로 서버 동작 확인 이후

      - name: Test Green Environment
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            ~/test-green.sh

서버 내부에 있는 테스트 스크립트를 실행 시킨다.

#!/bin/bash

for i in {1..10}; do
  curl -f http://localhost:8081 && break || sleep 10;
done

위 내용의 스크립트이다.
10초에 한 번씩 최대 10번을 curl 를 통해 Green 서버가 준비되었는지 확인한다.

nginx conf 파일에서 port 번호를 green port로 수정 & restart

      - name: Switch Traffic to Green Environment
        if: ${{ success() }}
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            sudo cp ~/nginx.green.conf /etc/nginx/sites-enabled/default
            sudo service nginx restart  

테스트에 성공하고 나면 트래픽을 Green으로 이동시켜준다.
준비해둔 Green conf 파일을 nginx 설정 파일로 사용하여 nginx 를 재실행한다.

Blue Docker down

      - name: Cleanup Old Blue Environment
        if: ${{ success() }}
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            ~/cleanup-blue.sh
            docker image prune -a -f 
            
      - name: Set cleanup-blue file
        if: ${{ success() }}
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            echo "docker compose -p ${{ env.PROJECT_NAME }} -f ~/docker-compose.blue.yml down" > ~/cleanup-blue.sh

트래픽을 Green으로 이동시킨 뒤에 Blue를 Down 시키는 스크립트를 실행한다.
이후 사용하지 않는 이미지를 제거하여 저장 공간을 확보한다.

docker compose -p deploy-9363951795 -f ~/docker-compose.blue.yml down

이런 식의 간단한 스크립트이다.
여기서 -p 옵션의 파라미터 deploy-9363951795 는 매번 변경된다.
Blue를 down 시킨 뒤에 이 파라미터는 해당 Green의 project name으로 변경되어 이제 Blue가 된 Green을 종료시키는 스크립트로 변경된다.

Green 을 Blue 로 변경

      - name: Prepare New Blue Environment
        if: ${{ success() }}
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            cp ~/docker-compose.green.yml ~/docker-compose.new-blue.yml
            mv ~/docker-compose.blue.yml ~/docker-compose.green.yml
            mv ~/docker-compose.new-blue.yml ~/docker-compose.blue.yml
            cp ~/nginx.green.conf ~/nginx.new-blue.conf
            mv ~/nginx.blue.conf ~/nginx.green.conf
            mv ~/nginx.new-blue.conf ~/nginx.blue.conf
            cp ~/test-green.sh ~/test-new-blue.sh
            mv ~/test-blue.sh ~/test-green.sh
            mv ~/test-new-blue.sh ~/test-blue.sh

현재 Green을 띄우기 위해 사용했던 compose 파일이나 설정 파일, 스크립트 파일들을 Blue 파일과 스왑 해준다.
이렇게 Green가 Blue로 된다.
이렇게 파일을 스왑 해주는 이유는 Blue와 Green은 다른 포트 번호로 실행되는데, Blue 와 Green 에 대하여 고정된 포트 번호를 사용한다면
Green을 Blue로 변경을 위해서 실행 중인 서버의 포트 번호를 변경시켜줘야 한다.
이는 매우 복잡해보여 Blue와 Green의 포트 번호를 스왑해주고 실행 중인 서버의 포트 번호는 그대로 두어 논리적으로 Blue로 변경해 주는 것이다.

후기

기존 CI/CD 로직에서 간단하게 Blue Green Deployment를 적용했다는 점에서 만족한다.
설정 파일을 스왑 하는 방식으로 Green 을 Blue로 변경시키는 것이 조금 불안해 보이긴 한다.
그래도 간단한 무중단 배포 구현에서는 나쁘지 않아 보인다.

유저들이 많이 사용하는 중요한 서비스라면 ArgoCD, Jenkins 등을 사용하여 구현하는 것도 좋지만
개인 사이드 프로젝트에서는 이렇게 간단하게 구현하는 것도 괜찮을지도?

profile
inho ha / ian(swatchon) / iha(42seoul)

0개의 댓글