택시짱의 개발 노트

Docker로 Spring Boot Blue, Green 배포 해보기 본문

BackEnd

Docker로 Spring Boot Blue, Green 배포 해보기

택시짱 2024. 6. 19. 19:06

최근에 면접관으로 참여 하면서 배포 방식에 대한 이야기가 자주 나오고 있어서 그 중 여러 분들이 사용하고 도입 했던 Blue Green 배포를 한번 해보았습니다.

저도 개념만 알고 있지 직접 해당 배포 방식을 구현 및 적용 해본적이 없어 간단하게 해봤습니다.

 

구성은 아래와 같습니다.

spring boot 의 jar를 docker container로 생성 ( blue, green ) 하고

앞에 nginx를 두어 load balancing 를 해주도록 했습니다.

Dockerfile

# 베이스 이미지로 OpenJDK를 사용합니다.
FROM openjdk:17-ea-17-jdk-slim

# curl 설치
RUN apt-get update && apt-get install -y curl

# 임시 디렉토리 설정
VOLUME /tmp

# 빌드된 JAR 파일을 ARG로 받습니다.
ARG JAR_FILE=practice-server/build/libs/*-SNAPSHOT.jar

# JAR 파일을 이미지에 복사합니다.
COPY ${JAR_FILE} app.jar

# 환경 변수 설정
ENV SPRING_PROFILES_ACTIVE=local

# 애플리케이션을 실행합니다.
ENTRYPOINT ["sh", "-c", "java -jar /app.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"]

docker-compose.yml

version: "3.8"
services:
  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - blue
      - green

  blue:
    container_name: blue
    build:
      context: .
      dockerfile: Dockerfile

    environment:
      - SPRING_PROFILES_ACTIVE=blue
    ports:
      - "8081:8081"

  green:
    container_name: green
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - SPRING_PROFILES_ACTIVE=green
    ports:
      - "8082:8082"

Blue, Green 의 env를 가진 서버를 띄우기 위한 application.yaml

  • docker-compose.yaml 에서 SPRING_PROFILES_ACTIVE blue, green 설정
server:
  port: 8081
spring:
  config:
    activate:
      on-profile: blue
---
server:
  port: 8082

spring:
  config:
    activate:
      on-profile: green

이제 docker-compose를 실행하면 nginx, blue, green 가 올라갑니다. ($ docker-compose up)

예를들어 현재 blue가 트래픽을 받고 있는 상황인데 새로운 버전의 배포가 필요할 때

새로운 버전이 반영된 green 을 생성하여 Nginx의 로드밸런서를 이용하여 blue → green 으로 옮겨 줘야 합니다.

  • blue가 트래픽 받고 있는 상황
  • blue 에서 green 으로 트래픽 변경

위와 같은 트래픽 변경을 하기 위해서는 Nginx 에서 라우팅 blue → green 으로 변경 하도록 해야 하는데

아래는 nginx container를 생성할때 사용 했던 nginx.conf 파일 입니다.

events {
    worker_connections 512;
}

http {
    upstream blue {
        server blue:8081;
    }

    upstream green {
        server green:8082;
    }

    server {
        listen 80;

        location / {
            proxy_pass <http://$backend>;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        set $backend blue;  # <-------- 요기에서 라우팅을 변경 해주도록 함
    }
}

Nginx는 항상 동작하고 있기 때문에 동적으로 blue, green 을 변경 해 줘야 합니다.

blue, green 의 배포 과정을 정리 하자면 ( 현재 blue 가 트래픽을 요청 받고 있다고 가정 )

  1. code 작성 후 새로운 버전 생성
  2. 새로운 버전이 적용된 green server 구동
  3. nginx에서 green server 가 정상적으로 동작하는 확인 후 트래픽을 blue → green 으로 변경
  4. 사용되지 않는 blue server 는 down

위 동작을 shell script 로 작성 해봤습니다.

#!/bin/bash

# spring boot build
echo -e "Spring Boot 빌드 중입니다..."
./gradlew build

# 현재 backend 확인
ACTIVE_SERVER_URL="<http://0.0.0.0/active>"
RESPONSE=$(curl -s $ACTIVE_SERVER_URL)

# 서버 응답에서 <h1>blue</h1> 패턴 찾기
echo -e "서버 응답: $RESPONSE"
if echo "$RESPONSE" | grep -q "blue"; then
  NOW_BACKEND="blue"
  NEW_BACKEND="green"
  echo -e "현재 활성화된 서버는 $NOW_BACKEND 입니다. 새로운 서버는 $NEW_BACKEND 입니다."
elif echo "$RESPONSE" | grep -q "green"; then
  NOW_BACKEND="green"
  NEW_BACKEND="blue"
  echo -e "현재 활성화된 서버는 $NOW_BACKEND 입니다. 새로운 서버는 $NEW_BACKEND 입니다."
else
  echo -e "응답을 확인할 수 없습니다."
  NEW_BACKEND="blue"
fi

# NEW_BACKEND 빌드
echo -e "${NEW_BACKEND} 서버를 빌드 중입니다..."
docker-compose build --no-cache $NEW_BACKEND

# NEW_BACKEND 시작
echo -e "${NEW_BACKEND} 서버를 시작 중입니다..."
docker-compose up -d $NEW_BACKEND

# 새로운 서버가 안정화되도록 대기 (60초)
echo -e "새로운 서버가 안정화되도록 대기 중입니다 60초..."
for i in {1..60}; do
  echo -e "$i/60..."
  sleep 1
done

# 새로운 서버의 안정성 확인 (5초 간격으로 3번 시도)
if [ "$NEW_BACKEND" = "blue" ]; then
  NEW_BACKEND_URL="<http://0.0.0.0:8081>"
else
  NEW_BACKEND_URL="<http://0.0.0.0:8082>"
fi

SUCCESS=0
for i in {1..3}; do
  echo -e "${NEW_BACKEND} 서버에 요청 시도 중... ($i/3)"
  RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $NEW_BACKEND_URL/healthcheck)

  echo -e "$RESPONSE"
  if [ "$RESPONSE" -eq 200 ]; then
    SUCCESS=1
    break
  fi
  sleep 5
done

if [ "$SUCCESS" -ne 1 ]; then
  echo -e "${NEW_BACKEND} 서버가 정상적으로 시작되지 않았습니다."
  exit 1
fi

# Nginx 설정 파일 업데이트
NGINX_CONF_TEMPLATE="./nginx/nginx-template.conf"
NGINX_CONF="./nginx/nginx.conf"

sed "s/{{BACKEND}}/$NEW_BACKEND/" $NGINX_CONF_TEMPLATE > $NGINX_CONF

# Nginx 컨테이너 내부로 설정 파일 복사
docker cp $NGINX_CONF nginx:/etc/nginx/nginx.conf

# Nginx 컨테이너에서 Nginx 재시작
docker exec nginx nginx -s reload

echo -e "Nginx 설정이 업데이트되었습니다. 현재 backend는 $NEW_BACKEND 입니다."

# 현재 운용되는 서버 한번 더 확인
echo -e "새롭게 생성된 서버를 확인합니다."

# 서버 응답에서 <h1>blue</h1> 패턴 찾기
RESPONSE=$(curl -s $ACTIVE_SERVER_URL)
echo -e "서버 응답: $RESPONSE"

echo -e "${NOW_BACKEND} 를 종료합니다..."
docker-compose stop $NOW_BACKEND
docker-compose rm -f $NOW_BACKEND

docker container prune -f
docker image prune -f

위 shell script 동작 하면 현재 blue인지 green 인지 확인 한 후 알맞은 서버를 띄우고 트래픽 전환 까지 하게 됩니다.

  • blue → green
  • green → blue

이렇게 blue, green 배포를 직접 구현 해보니 blue, green 의 단점이 약간 보였는데요

  1. blue, green 을 하기 위해서 동작 되고 있는 서버와 같은 동일한 자원을 생성 해야 한다는 점
  2. blue → green or green → blue 로 전환 하면서 기존 서버에 트래픽이 끊어 진다는 점

위 2가지 정도를 단점으로 느끼게 됬습니다.

장점은 빠른 롤백이 가능 해 보였습니다.

저도 개념만 알고 있지 직접 해당 배포 방식을 구현 및 적용 해본적이 없어 간단하게 해봤습니다.

반응형
Comments