배포할 때 잠깐이라도 서비스가 멈추면 사용자는 곧장 알아챕니다. 특히 결제, 게임, 실시간 서비스라면 단 몇 초의 다운타임도 비즈니스에 영향을 줍니다. Blue-Green 배포는 두 개의 동일한 환경을 운영하면서 트래픽을 한 번에 전환하는 가장 단순하고 검증된 무중단 배포 패턴입니다.

Blue-Green 무중단 배포


핵심 아이디어

환경 역할 사용자 트래픽
Blue 현재 운영 (v1.0) 100% 수신
Green 다음 버전 (v1.1) 대기 0% — 검증만

배포 절차:

  1. Green에 신규 버전을 배포
  2. Green이 정상 동작하는지 헬스체크
  3. 로드밸런서/프록시 설정만 변경 → 트래픽이 Green으로 전환
  4. Blue는 즉시 폐기하지 않고, 문제 발생 시 즉시 롤백 가능한 상태로 유지

핵심은 트래픽 전환이 단일 설정 변경이라는 점입니다. 코드 푸시도, 빌드도, 재시작도 아닌 — Nginx의 upstream만 바꿉니다.


1단계 — 두 개의 애플리케이션 인스턴스

같은 서버에서 두 인스턴스를 동시에 운영합니다(다른 포트로).

# Blue (운영 중)
systemctl status myapp-blue   # 포트 8080
# Green (대기)
systemctl status myapp-green  # 포트 8081

systemd 템플릿 유닛으로 관리하면 깔끔합니다.

# /etc/systemd/system/myapp@.service
[Unit]
Description=MyApp instance %i
After=network.target

[Service]
User=deploy
WorkingDirectory=/opt/myapp/%i
ExecStart=/opt/myapp/%i/bin/start.sh
EnvironmentFile=/opt/myapp/%i/env
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl enable myapp@blue myapp@green
sudo systemctl start myapp@blue

2단계 — Nginx upstream 정의

# /etc/nginx/conf.d/myapp.conf

upstream blue {
    server 127.0.0.1:8080;
    keepalive 32;
}

upstream green {
    server 127.0.0.1:8081;
    keepalive 32;
}

# 현재 활성 upstream을 가리키는 별명
upstream active {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    location / {
        proxy_pass http://active;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

active가 가리키는 포트만 바꾸면 트래픽이 전환됩니다.


3단계 — 전환 스크립트

active의 포트만 바꿔서 nginx reload 하는 스크립트입니다.

#!/bin/bash
# /usr/local/bin/switch-deploy.sh
set -euo pipefail

TARGET="${1:-}"  # blue 또는 green
CONF="/etc/nginx/conf.d/myapp.conf"

if [[ "$TARGET" != "blue" && "$TARGET" != "green" ]]; then
  echo "Usage: $0 <blue|green>"
  exit 1
fi

PORT=8080
[[ "$TARGET" == "green" ]] && PORT=8081

# active upstream의 포트만 치환
sed -i "/upstream active/,/}/ s|server 127.0.0.1:[0-9]*;|server 127.0.0.1:${PORT};|" "$CONF"

# Nginx 설정 검증 후 reload
sudo nginx -t
sudo systemctl reload nginx

echo "Switched to $TARGET (port $PORT)"
sudo chmod +x /usr/local/bin/switch-deploy.sh

4단계 — 배포 스크립트 (Green에 배포)

#!/bin/bash
# /usr/local/bin/deploy-green.sh
set -euo pipefail

NEW_VERSION="$1"
APP_DIR="/opt/myapp/green"

echo "[1/4] Stopping green..."
sudo systemctl stop myapp@green

echo "[2/4] Deploying version $NEW_VERSION to green..."
cd "$APP_DIR"
git fetch origin
git checkout "$NEW_VERSION"
npm ci --production
# 또는 pip install / cargo build / docker pull 등

echo "[3/4] Starting green..."
sudo systemctl start myapp@green

echo "[4/4] Health check..."
for i in {1..30}; do
  if curl -fsS http://127.0.0.1:8081/healthz > /dev/null; then
    echo "Green is healthy"
    exit 0
  fi
  sleep 1
done

echo "Green failed to start" >&2
exit 1

성공하면 다음과 같이 트래픽을 전환합니다:

sudo /usr/local/bin/deploy-green.sh v1.1.0
sudo /usr/local/bin/switch-deploy.sh green

5단계 — 헬스체크 엔드포인트

/healthz는 단순 200을 반환하는 게 아니라 실제 의존성까지 체크해야 합니다.

@app.route('/healthz')
def healthz():
    try:
        db.execute("SELECT 1")          # DB 연결
        redis.ping()                     # 캐시 연결
        return {"status": "ok"}, 200
    except Exception as e:
        return {"status": "fail", "error": str(e)}, 503

DB가 죽었는데 단순 200을 반환하면, Green으로 전환하자마자 모든 요청이 500으로 떨어집니다.


6단계 — 롤백

문제가 생기면 한 줄로 되돌립니다.

sudo /usr/local/bin/switch-deploy.sh blue

이게 Blue-Green의 가장 큰 가치입니다. Blue가 살아 있는 한 언제든 단일 명령으로 즉시 복귀가 가능합니다.


주의해야 할 함정

상태가 있는 연결

  • WebSocket, SSE, gRPC 스트림처럼 장기 연결을 유지하는 클라이언트는 reload 시 그대로 유지됩니다. 새 연결만 Green으로 갑니다. 점진적으로 자연스러운 전환이 됩니다.
  • 다만 세션 어피니티가 필요한 경우(예: 인메모리 세션) 두 인스턴스 모두에 세션 스토어가 공유돼야 합니다. Redis 세션 스토어 등을 두는 것이 안전합니다.

DB 마이그레이션

  • Blue와 Green이 동시에 같은 DB를 보고 있으므로, 이전 버전과 호환되는 마이그레이션만 적용해야 합니다. 컬럼 삭제, 타입 변경 같은 파괴적 변경은 다음 절차로 분리하세요.
    1. 새 컬럼 추가 (이번 배포)
    2. 코드를 새 컬럼 사용으로 교체 (다음 배포)
    3. 옛 컬럼 삭제 (그 다음 배포)

환경 변수 / 비밀

  • Green 환경 변수가 Blue와 다르면 의도하지 않은 차이가 생깁니다. 환경 변수 파일을 같은 곳에서 읽도록 통일하세요.

연결 풀 / 캐시 워밍업

  • Green이 막 떴을 때 첫 요청은 DB 연결 풀이 비어 있어 느릴 수 있습니다. 헬스체크에서 가벼운 쿼리를 한 번 보내거나, /warmup 엔드포인트를 만들어 주요 쿼리를 미리 실행해두면 좋습니다.

Canary 배포로 확장

Blue-Green을 한 단계 발전시키면 Canary 배포가 됩니다. Nginx의 가중치 기능으로 트래픽을 부분 전환할 수 있습니다.

upstream active {
    server 127.0.0.1:8080 weight=9;  # Blue 90%
    server 127.0.0.1:8081 weight=1;  # Green 10%
    keepalive 32;
}

10%로 시작 → 30% → 50% → 100% 단계로 모니터링하면서 늘리면 됩니다. 문제 발생 시 가중치만 0으로 바꿔 즉시 차단합니다.


마무리

Blue-Green은 거창한 도구가 아닙니다. 두 개의 인스턴스 + 한 줄 짜리 Nginx upstream 변경만 있으면 다운타임 0초 배포가 가능합니다. 진입 비용이 낮으면서 효과는 즉시 체감됩니다.

TCP-80.NET의 일본 VPS 4GB 플랜이면 Blue-Green 환경 두 개를 모두 띄우기에 충분합니다. systemd 템플릿이나 Nginx 설정에 막히는 부분이 있으면 @tcp80net으로 한국어 문의 가능합니다.