무중단 배포 구현하기 - 블루그린 배포

JSM·2023년 12월 7일
0

프로젝트

목록 보기
6/10
post-thumbnail

무중단 배포가 무엇일까?

  • 무중단 배포는 재배포하는 도중에도 서비스를 종료하지 않고 배포한것을 적용하는 것을 의미합니다.
  • 그럼 어떻게 무중단 배포를 구현할까요?
  • A라는 서비스를 사용자들이 사용하고 있다고 해보겠습니다.
  • 이때 A 서비스에 기능을 추가한 B 서비스가 등장합니다.
  • A 서비스를 제공하고 있는 도중에 B 서비스를 배포합니다.
  • 이 후 B 서비스가 안정화가 되면 A 서비스로 가는 트랙픽을 B 서비스로 바꿔줍니다.
  • 이러면 사용자는 재배포에서 발생하는 다운타임을 느끼지 못한채 새로운 기능이 추가된 서비스를 사용할 수 있는 것입니다!

무중단 배포에는 무엇이 있을까요?

  • 블루그린 배포 / 카나리 배포 / 롤링 업데이트 배포가 있습니다.
  • 블루 그린 배포가 위에서 언급한 방식대로 동작합니다. 기존 서비스는 블루 서비스라고 하면 새로 대체되는 서비스가 그린 서비스인 것이지요.
  • 그럼 카나리 배포와 롤링 업데이트가 무엇일까요?

카나리 배포

  • 카나리 배포는 새 버전을 소수의 사용자에게 선 배포합니다.
  • 이 후 안정성이 검토되면 점차적으로 더 많은 사용자의 트래픽을 배포된 서버로 옮기고 기존 서버를 종료시킵니다.

롤링 업데이트 배포

  • 기존 인프라 위에서 점진적으로 업데이트 하는 방식으로 서버의 총 숫자가 변하지 않습니다.
  • 예를 들어 기존 3개의 서버가 존재하면 서버 하나씩 점진적으로 업데이트 하는 방식입니다.

그렇다면 저희는 왜 블루그린 배포를 선택했을까요?

  • PM에서 자체적으로 무중단 배포를 제공합니다.
  • 하지만 저희는 서비스를 도커로 배포하기로 결정했고 이 과정에서 PM2의 무중단 배포의 장점이 사라집니다.
  • 따라서 무중단 배포의 장점을 그대로 가져가기 위해 무중단 배포를 선택했습니다.
  • 그중 블루 그린 배포가 도커 서비스 환경에서 가장 구현하기 적합할 것 같아서 적용했습니다.

도커로 배포를 한 이유가 무엇인가요?

  • 가장 간편하게 기존 서비스를 삭제하기 재시작할 수 있기 때문입니다.
  • 서비스의 수정 사항이 발생했을때 로컬에서 이미지를 빌드한 후 인스턴스에 접속한후 해당 이미지로 재배포만 이루어지면 되기 때문에 가장 간단합니다.
  • 즉 깃허브에 코드를 올리기 전에 실제 테스트가 가능하기 때문입니다.

블루 그린 배포는 어떻게 구현할까?

  • 일단 저희는 모든 서비스를 도커로 운영하고 있습니다. 즉 실제 서버 뿐 아니라 Nginx도 도커입니다.
  • 또한 클라이언트의 요청이 올때 Nginx 리버스 프록시를 통해 서버로 라우팅하고 있습니다.
  • 크게 2가지 처리를 해야합니다.
  1. 블루 컨테이너와 그린 컨테이너를 적절한 시기에 띄우고 종료하기
  2. Nginx 리버스 프록시를 통해 트래픽을 기존 서버에서 새로운 서버로 바꾸기

Nginx는 블루 컨테이너로 트래픽을 보냅니다

  • Nginx는 블루 컨테이너(기존 서버)와 연결되어 트래픽을 전달합니다.

새롭게 업데이트된 서버의 이미지로 그린 컨테이너를 띄웁니다.

  • 서버의 업데이트를 반영한 이미지를 통해 그린 컨테이너 서비스를 띄웁니다.

그린 컨테이너가 안정화되면 트래픽을 그린 컨테이너로 변경합니다.

  • 그린 컨테이너가 요청을 처리할 수 있는 상태가 되면 Nginx 설정을 리로드하여 트래픽을 변경합니다.

블루 컨테이너를 종료합니다.

  • 리소스를 잡아먹는 기존 블루 컨테이너를 종료합니다.

1. 도커 컴포즈 파일 작성하기

  • 먼저 블루 컨테이너와 그린 컨테이너 그리고 Nginx 서비스를 정의하는 컴포즈 파일을 작성합니다.
  • Nginx 리버스 프록시를 통해 443 포트로 접근하면 3001번 포트 또는 3002번 포트로 라우팅 되는 것입니다.
  • 여기서 3000 포트는 각 도커 내부에서 서비스 포트입니다.
version: '3.8'

services:
  blue:
    container_name: blue
    image: 이미지 이름
    ports:
      - 3001:3000
    env_file:
      - .env

  green:
    container_name: green
    image: 이미지 이름
    ports:
      - 3002:3000
    env_file:
      - .env

  nginx:
    container_name: nginx
    image: 이미지 이름
    ports:
      - 80:80
      - 443:443
    depends_on:
      - blue
      - green
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - /etc/letsencrypt:/etc/letsencrypt

2. Nginx 리버스 프록시 설정하기

  • Nginx 리버스 프록시를 통해 블루 또는 그린 서비스로 라우팅합니다.
  • 443 포트로 요청이 왔을때 로케이션을 통해 리버스 프록시를 진행합니다.
  • proxy_pass에 어떤 경로로 라우팅 할지 결정합니다.
upstream blue_backend {
    server blue:3000;
}

upstream green_backend {
    server green:3000;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 80;
    server_name 도메인;

    location / {
        return 301 https://$host$request_uri;
    }
}


server {
    listen 443 ssl;
    server_name 도메인;

    ssl_certificate /etc/letsencrypt/live/도메인/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/도메인/privkey.pem;

    location / {
        proxy_pass http://blue_backend;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Forwarded-Host $server_name;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

3. 새로운 서버 배포 후 Nginx 설정을 리로드합니다.

  • 기존 서버로 라우팅을 했다면 이제 새로운 서버를 띄웁니다.
  • 이제 새로운 서버로 라우팅을 수정하고 Nginx 설정을 리로드합니다.
  • 도커에 로그인한 후 새로운 서버 이미지를 받아올 준비를 합니다.
  • 현재 서비스가 블루 서비스인지 그린 서비스인지 체크한 후 상응하는 서비스로 재배포를 준비합니다.
  • Nginx 설정을 리로드하고 재배포가 일어납니다.
#도커 로그인
docker login -u ID -p PASSWORD

# 이미지 레지스트리
DOCKER_REPO=REGISTRY

# 현재 서비스 체크
if docker ps | grep -q blue; then
    echo "Blue service is active."
    CURRENT_SERVICE="blue"
    NEW_SERVICE="green"
else
    echo "Green service is active."
    CURRENT_SERVICE="green"
    NEW_SERVICE="blue"
fi

# 새로운 서비스로 Nginx 설정 수정 (리로드 전)
NGINX_UPSTREAM="${NEW_SERVICE}_backend"
sudo sed -i "s/proxy_pass http:\/\/.*_backend;/proxy_pass http:\/\/$NGINX_UPSTREAM;/" ./nginx/conf.d/nginx.conf

# 수정된 정보만 레지스트리로 푸시
docker build -f dockerfile-nginx -t $DOCKER_REPO/Nginx 이미지 .
docker push $DOCKER_REPO/Nginx 이미지

# 새로운 서버 배포
sudo docker pull $DOCKER_REPO/서버 이미지
sudo docker pull $DOCKER_REPO/Nginx 이미지
sudo docker-compose up -d $NEW_SERVICE

# Nginx가 없는 경우 Nginx 서비스 시작
if ! docker ps | grep -q nginx; then
    sudo docker-compose up -d nginx
fi

# 서비스가 안정적으로 활성화되기 위한 지연시간
sleep 60

# Nginx 설정 리로드 및 기존 서비스 다운
NGINX_CONTAINER=$(docker ps | grep "nginx" | awk '{print $1}')
docker exec $NGINX_CONTAINER nginx -s reload
sudo docker-compose stop $CURRENT_SERVICE

# 사용되지 않는 이미지 제거
docker image prune -f
profile
내 기술적 고민들을 모은 곳...

0개의 댓글