처음 개인 블로그 프로젝트를 운영하면서 배포할 때마다 서비스가 중단되는 문제가 있었습니다. 이때 블로그가 정상 작동하지 않아 이 문제를 해결하기 위해 Blue-Green 배포 방식을 도입했습니다.
먼저 현재 프로젝트의 아키텍처를 간단히 소개하겠습니다.
프론트엔드와 백엔드가 분리되어 있고, 각각 다른 저장소에서 관리됩니다. 프론트엔드는 Vercel에서 자동 배포되고, 백엔드는 Lightsail 인스턴스에서 직접 관리합니다.
Blue-Green 배포는 두 개의 동일한 프로덕션 환경을 유지하면서 무중단 배포를 구현하는 방식입니다.
배포 과정은 다음과 같습니다:
이 방식의 장점은 빠른 롤백이 가능하고, 트래픽을 서버 다운 없이 바로 전환할 수 있다는 점입니다.
Blue-Green 배포를 위해 Docker Compose 파일을 다음과 같이 분리했습니다.
MySQL과 Redis는 Blue와 Green 환경에서 공유합니다. 데이터베이스를 분리하면 데이터 동기화 문제가 발생할 수 있기 때문입니다.
services:
mysql:
image: mysql:8.0
container_name: blog-mysql
# ...
redis:
image: redis:7-alpine
container_name: blog-redis
# ...
networks:
blog-network:
external: true
# ...
services:
backend-blue:
build:
context: .
dockerfile: Dockerfile
image: blog-backend:${IMAGE_TAG:-latest}
container_name: blog-backend-blue
environment:
- SPRING_PROFILES_ACTIVE=prod
- ENV_COLOR=blue
# ... 기타 환경 변수
ports:
- "127.0.0.1:8081:8080"
volumes:
- ./logs/blue:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 10s
timeout: 5s
retries: 2
start_period: 30s
networks:
- blog-network
restart: unless-stopped
networks:
blog-network:
external: true
Blue 환경은 8081 포트에 바인딩됩니다. 127.0.0.1:8081:8080으로 설정해서 외부에서 직접 접근할 수 없게 했습니다.
모든 트래픽은 Nginx를 통해서만 접근 가능합니다.
Green 환경은 Blue와 거의 동일하지만, 포트와 환경 변수만 다릅니다.
services:
backend-green:
# ... (Blue와 동일한 설정)
container_name: blog-backend-green
ports:
- "127.0.0.1:8082:8080" # 다른 포트
volumes:
- ./logs/green:/app/logs
services:
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
image: blog/nginx:latest
container_name: blog-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- nginx_logs:/var/log/nginx
- ./nginx/ssl:/etc/nginx/ssl:ro
networks:
- blog-network
restart: unless-stopped
networks:
blog-network:
external: true
volumes:
nginx_logs:
driver: local
Nginx는 외부와 통신하는 컨테이너입니다. SSL 인증서도 여기서 관리합니다.
Nginx는 Blue와 Green 환경 사이에서 트래픽을 전환하는 핵심 역할을 합니다.
Blue와 Green 각각의 upstream 설정 파일을 만듭니다.
nginx/conf.d/upstream-blue.conf
upstream backend {
server blog-backend-blue:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
nginx/conf.d/upstream-green.conf
upstream backend {
server blog-backend-green:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
nginx/conf.d/active-upstream.conf : 현재 활성 환경을 가리킴 (Blue 또는 Green 이 복사됨)
nginx/conf.d/default.conf
# HTTP to HTTPS 리다이렉트
server {
listen 80;
server_name mydomain.com localhost _;
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 나머지는 HTTPS로 리다이렉트
location / {
return 301 mydomain.com$request_uri;
}
}
# HTTPS 설정
...
}
scripts/deploy/health-check.sh
#!/bin/bash
set -e
TARGET_ENV=$1
if [ "$TARGET_ENV" = "blue" ]; then
BACKEND_PORT=8081
else
BACKEND_PORT=8082
fi
BACKEND_URL="http://localhost:${BACKEND_PORT}/actuator/health"
MAX_RETRIES=20
RETRY_INTERVAL=2
TIMEOUT=3
# ...
배포 시간이 너무 길어지고 Blue와 Green이 동시에 실행되면서 인스턴스에 과부하가 걸린 문제가 있어 재시도 수를 20회로 줄여서 최대 40초 안에 실패를 감지하도록 개선했습니다.
scripts/deploy/blue-green-deploy.sh
#!/bin/bash
...
# 타겟 환경 결정
if [ "$ACTIVE_ENV" = "blue" ]; then
TARGET_ENV="green"
else
TARGET_ENV="blue"
fi
cd "$PROJECT_DIR"
# 타겟 환경 컨테이너 시작
docker compose -f docker-compose.yml -f docker-compose.${TARGET_ENV}.yml up -d
# Health check
...
# Upstream 전환
...
# 성공
echo "Previous : ${ACTIVE_ENV}"
echo "New: ${TARGET_ENV}"
exit 0
deploy_V1
deploy_V2 (업데이트)
Blue-Green 배포를 구현하면서 많은 문제가 있었지만 무중단 배포 시스템을 구축할 수 있었습니다.
현재 시스템으로 약 3분 정도에 무중단 배포가 가능하고, 문제 발생 시 30초 내로 롤백할 수 있습니다. 이후에는 Promeheus + Grafana를 이용핸 로그/모니터링을 추가해서 더 완성도 높은 CI/CD 파이프라인을 만들 계획입니다.