Nginx 무중단 배포: Blue-Green/Canary, graceful reload, 안전 롤백(OSS 기준 완전 가이드)
사용자 접속을 끊지 않고 새 버전을 노출하려면 Nginx의 graceful reload와 업스트림 전략이 핵심입니다.
이 글은 오픈소스 Nginx(무료판) 기준으로 Blue-Green, Canary, passive health(오류 전환),
systemctl reload nginx를 이용한 무중단 교체, 그리고 즉시 롤백 루틴까지 한 번에 정리합니다.
1) 큰 그림: “요청은 계속 흘러가고, 설정만 바뀐다”
- graceful reload:
nginx -t검증 후reload시, 기존 워커는 처리 중 요청을 마무리하고 새 워커가 설정을 반영합니다. - 업스트림 다중 포트: 앱을 2개 이상 포트(또는 서버)로 띄운 뒤, Nginx가 분산합니다.
- Blue-Green: 파란색(Blue)과 초록색(Green) 두 묶음을 준비하고 트래픽 스위칭만 바꿉니다.
- Canary: 일부분만 신버전으로 보내며 지표를 관찰합니다.
# 점검 & 무중단 적용
sudo nginx -t && sudo systemctl reload nginx
2) 업스트림 기본 템플릿(least_conn + keepalive)
# /etc/nginx/conf.d/app-upstream.conf
upstream app_pool {
least_conn; # 바쁜 서버에 덜 보내기
server 127.0.0.1:8081 max_fails=3 fail_timeout=10s;
server 127.0.0.1:8082 max_fails=3 fail_timeout=10s;
keepalive 64; # 연결 재사용
}
# /etc/nginx/sites-available/app.example.com (발췌)
server {
listen 443 ssl http2;
server_name app.example.com;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_read_timeout 65s;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_pass http://app_pool;
}
}
오픈소스 Nginx는 능동(Active) 헬스체크가 없습니다. 대신 위와 같이 passive로
오류/타임아웃 시 다른 노드로 전환합니다(max_fails, fail_timeout, proxy_next_upstream).
3) Blue-Green: 포트 스위칭으로 무중단 전환
앱을 2개 포트로 띄웁니다(예: Blue=8081, Green=8082). 배포는 항상 “유휴 쪽에 새 버전을 올리고 → 스위치 → 구버전 종료” 순서입니다.
# (예시) systemd 서비스 2개 - /etc/systemd/system/myapp-blue.service
[Service]
ExecStart=/usr/bin/java -jar app-blue.jar --server.port=8081
Restart=on-failure
# /etc/systemd/system/myapp-green.service
[Service]
ExecStart=/usr/bin/java -jar app-green.jar --server.port=8082
Restart=on-failure
스위칭 방법 A: upstream 가중치 변경
# /etc/nginx/conf.d/app-upstream.conf (전환 전)
upstream app_pool {
least_conn;
server 127.0.0.1:8081 weight=10; # Blue: 활성
server 127.0.0.1:8082 weight=0; # Green: 대기
}
# 전환 후 (Green 활성화)
upstream app_pool {
least_conn;
server 127.0.0.1:8081 weight=0;
server 127.0.0.1:8082 weight=10;
}
# 적용
sudo nginx -t && sudo systemctl reload nginx
스위칭 방법 B: 심볼릭 링크 원자 교체(정적/SSR)
# /etc/nginx/sites-available/app.example.com (정적 루트만 발췌)
root /var/www/current; # current → blue or green
# 전환 스크립트
ln -sfn /var/www/green /var/www/current \
&& sudo nginx -t && sudo systemctl reload nginx
4) Canary 릴리즈: 일부 트래픽만 신버전으로
쿠키/헤더/비율로 분기합니다. 장애 시 즉시 0%로 되돌리면 사실상 롤백입니다.
4-1) 비율 기반(사용자 무관, split_clients)
split_clients "${remote_addr}${request_uri}" $canary_bucket {
5% "canary";
* "stable";
}
map $canary_bucket $upstream_target {
default app_pool_stable;
canary app_pool_canary;
}
upstream app_pool_stable {
least_conn; server 127.0.0.1:8081;
}
upstream app_pool_canary {
least_conn; server 127.0.0.1:8082;
}
location / {
proxy_pass http://$upstream_target;
}
4-2) 쿠키 기반(내부 테스트/화이트리스트)
map $http_cookie $is_canary {
"~*CANARY=1" 1;
default 0;
}
map $is_canary $upstream_target {
1 app_pool_canary;
0 app_pool_stable;
}
팁: 비율/쿠키 스위치를 모두 넣어 두고 운영 중엔 비율 0%로 두면 즉시 롤백 동작을 만들 수 있습니다.
5) 배포/검증 루틴(복붙용)
- 사전 헬스: 신버전 포트 직접 체크
curl -sfk http://127.0.0.1:8082/actuator/health - 에러 지표 baseline: 현재 5xx/응답시간 기록
- Canary 5% 오픈: 5분 관찰(로그/지표)
- 점진 확대: 5 → 20 → 50 → 100%
- Blue 종료: 안정 확인 후 구버전 중지
# Nginx/앱 상태 동시 tail
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log
journalctl -u myapp-blue.service -f
journalctl -u myapp-green.service -f
6) 안전 롤백: 10초 내 되돌리기
- 가중치 0%로 즉시 전환(Blue-Green).
- 심볼릭 링크 원복 + reload.
- 버전 태그가 기록된 단일 스위치 스크립트를 만들어 둡니다.
# /usr/local/sbin/switch-version.sh
#!/usr/bin/env bash
set -euo pipefail
TARGET="${1:-stable}" # stable|canary|blue|green
sed -i "s/weight=10; # ACTIVE/weight=0; # ACTIVE/" /etc/nginx/conf.d/app-upstream.conf
sed -i "s/weight=0; # STANDBY/weight=10; # STANDBY/" /etc/nginx/conf.d/app-upstream.conf
nginx -t && systemctl reload nginx
echo "$(date -Is) switched to $TARGET" >> /var/log/deploy-switch.log
7) 자주 터지는 함정과 해결
- reload 후 502/504: 신버전 포트가 실제로 리스닝 중인지
ss -lntp | grep 8082확인. - 세션 유실: 세션을 서버 메모리에 두면 업스트림이 바뀔 때 로그인이 끊깁니다. 외부 세션 저장소(Redis 등)로 이동.
- Active health 오해: OSS Nginx에
health_check디렉티브는 없습니다. passive 전환과 짧은 타임아웃/재시도 조합으로 설계. - Keepalive 미설정:
upstream keepalive가 없으면 연결 재사용이 안 되어 레이턴시가 증가합니다. - 정적/SSR 혼재: 정적은 심볼릭 링크로, API는 업스트림 가중치로 스위치 수단을 분리하세요.
8) 관찰성: 에러·지연을 즉시 감지
- 지표: Nginx status(exporter)와 앱의 p95, 에러율, 502/504 카운트.
- 로그: 배포 윈도우 동안
$upstream_addr,$upstream_status필드 포함 포맷으로 분석.
# /etc/nginx/nginx.conf (log_format 발췌)
log_format main '$remote_addr "$request" $status $body_bytes_sent '
'$upstream_addr $upstream_status $request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
9) 체크리스트(배포 전)
- 신버전 포트 직접 헬스 OK, DB 마이그레이션 선적용
- Nginx 설정 문법 OK(
nginx -t), reload 성공 - 업스트림 keepalive,
proxy_next_upstream재시도 구성 - Canary 스위치(비율/쿠키) 준비, 롤백 스크립트 존재
- 로그 포맷에
$upstream_*필드 포함(분석 가능)
위 템플릿을 그대로 적용하면 “끊김 없이 배포”와 “즉시 롤백”이 습관이 됩니다. 다음 글에서는 HAProxy/Nginx 혼합 환경에서의 Blue-Green + Canary 설계를 비교하고, 다운타임 없는 DB 마이그레이션 팁까지 확장해 보겠습니다.