Blue Green 무중단 배포 적용하기

Chan Young Jeong·2024년 3월 17일
1

프로젝트 Dplanner

목록 보기
5/5

기존 방식

기존 Dplanner 서버 배포 과정은 다음과 같았습니다.

  1. Main Branch에 Commit을 하면

  2. Git WebHook을 이용해 Jenkins 서버에 요청을 보냅니다.

  3. Jenkins에서 빌드 파일을 만들어

  4. SSH를 통하여 빌드 파일을 전달하고

  5. 각 서버에 있는 deploy.sh 파일을 실행하게 됩니다.

어느정도 CI/CD가 자동화 되어 있었지만, 배포를 하는 도중 서비스가 중단 될 수 밖에 없었고, 만약 배포 파일이 잘못 됐을 때 애플리케이션을 롤백할 수 없기 때문에 수동으로 배포를 어쩔 수 없이 다시 진행해야하는 문제가 발생한다.

이를 해결하기 위해 무중단배포를 도입하도록 하였습니다.

도입 후 전체 배포 과정은 다음과 같습니다.

무중단 배포 방식

블루 그린 배포

블루 그린 배포는 구버전과 신버전 인스터를 생성한 후 로드 밸런서를 이용해 트래픽을 신버전으로 전환하는 배포 방식입니다. 배포를 완료한 후 구버전의 인스턴스를 하지 않는다면 해당 인스턴스는 남아있기에 빠른 롤백이 가능합니다. 하지만 그만큼 시스템 자원이 2배로 필요하다는 단점이 존재합니다.

블루 그린 배포 방식을 선택한 이유는 현재 Dplanner 는 Docker를 사용하여 하나의 EC2에서도 포트를 다르게 하여 여러개의 스프링 서버를 띄울 수 있는 상황이기에, 여러 EC2를 사용하지 않더라도 블루 그린을 도입할 수 있기에 블루 그린 배포를 방식을 사용하게 되었습니다.

CI

CI 과정을 요약하면 빌드 파일을 만들어 도커 이미지를 빌드 한 후 도커 허브에 저장합니다.

pipline

  1. 깃 메인 브랜치에서 클론을 떠온 후,
  2. 필요한 설정 값, secret 값들을 주입해주고
  3. 빌드 파일을 만듭니다.
  4. 도커 이미지를 빌드합니다.
  5. 도커에 로그인한 후
  6. 해당 이미지를 도커 허브에 pull합니다.
  7. 리소스 정리
pipeline {
    agent any

    environment {
        DOCKER_IMAGE_VERSION = 'latest'
    }

    stages {
        stage('Git Clone') {
            steps {
                git branch: 'main', url: 'https://github.com/dpplanner/api.git'
            }
        }

		...  설정 값 inject ...

        stage('Build Gradle') {
            steps {
                dir('./dplanner') {
                    sh 'chmod +x ./gradlew'
                    sh './gradlew clean build'
                }
            }
        }

        stage('Build Docker Image') {
            steps {
                dir('./dplanner') {
                    sh 'sudo docker build --no-cache -t dplanner/api:$DOCKER_IMAGE_VERSION -f ./deploy/Dockerfile . --platform linux/x86_64'
                }
            }
        }

        stage('Docker Login') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'docker-login', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
                    script {
                        sh 'sudo docker login -u $USERNAME -p $PASSWORD'
                    }
                }
            }
        }

        stage('Push to Docker Hub') {
            steps {
                script {
                    sh 'sudo docker push dplanner/api:$DOCKER_IMAGE_VERSION'
                }
            }
        }

        stage('Clean Docker Image') {
            steps {
                script {
                    sh 'sudo docker rmi dplanner/api:$DOCKER_IMAGE_VERSION'
                }
            }
        }
    }
}

DockerFile

도커 이미지 빌드에 사용되는 도커 파일입니다.

FROM eclipse-temurin:17-jdk-alpine

ARG JAR_FILE=../build/libs/dplanner-0.0.1-SNAPSHOT.jar

COPY ${JAR_FILE} dplanner.jar

ENTRYPOINT ["java","-jar", "-Dspring.profiles.active=production", "/dplanner.jar"]

CD

CD과정은 CI가 완료되면, 즉 도커 허브에 이미지가 올라가면 블루 그린 배포를 시작합니다.

pipline

pipeline {
    agent any

    environment {
        DOCKER_IMAGE_VERSION = 'latest'
    }

    stages {
        stage('Deploy') {
            steps {
                script {
                    sshagent(credentials:['aws_key']) {
                        sh "ssh -o StrictHostKeyChecking=no ${REMOTE_USER}@${REMOTE_HOST} uptime"
                        sh "ssh -t ${REMOTE_USER}@${REMOTE_HOST} sudo docker pull dplanner/api:${DOCKER_IMAGE_VERSION}"
                        sh "ssh -t ${REMOTE_USER}@${REMOTE_HOST} sudo sh ./deploy.docker.sh"
                    }
                }
            }
        }
    }
}

deploy.sh

블루그린 배포를 하는 쉘 스크립트입니다.
진행 순서는 다음과 같습니다.

1. switch_container(): 이 함수는 블루 그린 배포 과정에서 핵심 부분을 담당합니다. 현재 블루 환경에서 새로운 그린 환경으로의 전환을 관리합니다. 현재 블루 환경의 상태를 확인하고, 그린 환경으로의 전환 여부를 결정합니다. 블루 환경이 동작 중이 아니라면 그린 환경으로의 전환을 시도하고, 그린 환경이 동작 중이라면 블루 환경으로의 전환을 시도합니다. 전환 후에는 해당 환경의 서비스 상태를 확인하기 위해 health_check() 함수를 호출합니다.

2. health_check(): 이 함수는 주어진 URL에서 서비스의 상태를 확인합니다. 주어진 URL에 대해 일정 횟수(MAX_RETRIES)만큼 요청을 시도하고, 응답이 있으면 해당 응답을 파싱하여 서비스의 상태를 확인합니다. 만약 서비스가 "UP" 상태인 경우에는 0을 반환하고, 그렇지 않은 경우에는 1을 반환합니다. 만약 일정 횟수의 요청을 시도한 후에도 응답이 없을 경우에는 배포를 실패로 처리하고 종료합니다.

3. switch_conf(): 이 함수는 nginx의 설정 파일을 교체하고, 서버에게 설정 변경을 적용하기 위해 nginx를 다시 로드합니다. 이 함수는 블루 그린 배포 과정에서 새로운 환경으로의 전환 후에 호출됩니다.

4. down_container(): 이 함수는 이전에 사용되었던 환경(블루 또는 그린)의 컨테이너를 종료하고 삭제합니다. 이 함수는 블루 그린 배포 과정에서 새로운 환경으로의 전환 후에 호출됩니다.

#!/bin/bash

APP_NAME="dplanner"
DEFAULT_CONF_PATH="/etc/nginx/sites-enabled"
DEFAULT_CONF="default"
MAX_RETRIES=10

# 컨테이너 스위칭
switch_container() {
  IS_BLUE=$(docker-compose -p "${APP_NAME}-blue" -f docker-compose.blue.yml ps | grep Up)
  if [ -z "$IS_BLUE" ]; then
      echo "### GREEN => BLUE ###"
      docker-compose -p "${APP_NAME}-blue" -f docker-compose.blue.yml up -d
      BEFORE_COMPOSE_COLOR="green"
      AFTER_COMPOSE_COLOR="blue"

      sleep 30

      health_check "http://127.0.0.1:9001/actuator/health"
  else
      echo "### BLUE => GREEN ###"
      docker-compose -p "${APP_NAME}-green" -f docker-compose.green.yml up -d
      BEFORE_COMPOSE_COLOR="blue"
      AFTER_COMPOSE_COLOR="green"

      sleep 30

      health_check "http://127.0.0.1:9000/actuator/health"
  fi
}

# 컨테이너 상태 체크
health_check() {
    local RETRIES=0
    local URL=$1
    while [ $RETRIES -lt $MAX_RETRIES ]; do
      echo "Checking service at $URL... (attempt: $((RETRIES+1)))"
      sleep 3

      RESPONSE=$(curl -s "$URL")
      if [ -n "$RESPONSE" ]; then
        STATUS=$(echo "$RESPONSE" | jq -r '.status')
        if [ "$STATUS" = "UP" ]; then
          echo "health check success"
          return 0
        fi
      fi

      RETRIES=$((RETRIES+1))
    done;

    echo "Failed to check service after $MAX_RETRIES attempts."
    docker-compose -p "${APP_NAME}-${AFTER_COMPOSE_COLOR}" -f "docker-compose.${AFTER_COMPOSE_COLOR}.yml" down
    echo "### DEPLOY FAILED ###"
    exit 1

}

switch_conf() {
    cp "${DEFAULT_CONF_PATH}/${AFTER_COMPOSE_COLOR}" "${DEFAULT_CONF_PATH}/${DEFAULT_CONF}"
    nginx -s reload
}

down_container() {
  # 이전 컨테이너 종료
    docker-compose -p "${APP_NAME}-${BEFORE_COMPOSE_COLOR}" -f "docker-compose.${BEFORE_COMPOSE_COLOR}.yml" down
    echo "### $BEFORE_COMPOSE_COLOR DOWN ###"
}

switch_container
switch_conf
down_container

docker-compose 파일

docker-compose-blue.yml

version: '3.1'

services:
  api:
    image: dplanner/api:latest
    container_name: dplanner-blue
    ports:
      - '9001:8080'
networks:
  default:
    external:
      name: dplanner-private-network

docker-compose-green.yml

version: '3.1'

services:
  api:
    image: dplanner/api:latest
    container_name: dplanner-green
    ports:
      - '9000:8080'
networks:
  default:
    external:
      name: dplanner-private-network

nginx green, blue 설정파일

Nginx 설정 파일은 8080 포트에서 들어오는 HTTP 요청을 blue라면 127.0.0.1의 9001 포트로 , green이라면 9000포트로 프록시하는 역할을 합니다.

blue

server {
        listen 8080 default_server;
        listen [::]:8080 default_server;


        location / {
                proxy_pass http://127.0.0.1:9001;
        }
}

green

server {
        listen 8080 default_server;
        listen [::]:8080 default_server;


        location / {
                proxy_pass http://127.0.0.1:9000;
        }
}

0개의 댓글