본문 바로가기
서버 인프라 실무

HAProxy + Nginx 혼합 배치: Blue-Green/Canary 설계 비교, 액티브 헬스체크, 무중단 DB 마이그레이션 팁

by yamoojin83 2025. 10. 27.

HAProxy + Nginx 혼합 배치: Blue-Green/Canary 설계 비교, 액티브 헬스체크, 무중단 DB 마이그레이션 팁

이전 글에서 Nginx 단독으로 graceful reload 기반 무중단 배포(Blue-Green/Canary)를 만들었습니다. 이번에는 HAProxy(4/7계층 로드밸런서)Nginx(리버스 프록시/정적/캐시/헤더)혼합해서 더 단단한 배포 파이프라인을 설계해봅니다. 핵심은 HAProxy의 액티브 헬스체크/스틱 테이블Nginx의 정적/캐시/보안 헤더/리라이트를 역할 분리로 묶는 것입니다. 마지막으로 배포와 함께 자주 동반되는 DB 스키마 마이그레이션을 무중단으로 처리하는 실전 팁도 정리합니다.


1) 왜 혼합 구조인가: 역할 분리 다이어그램

[Client] --HTTPS--> [Nginx edge: TLS, 정적, 캐시, WAF/헤더]
                          |
                          v
                    [HAProxy: LB, 액티브 헬스, 라우팅/가중치, 스틱테이블]
                          |
               +----------+-----------+
               |                      |
           [App vBlue]            [App vGreen]
            :8081                  :8082
  • Nginx: TLS 종료, 정적/이미지, 캐시, 압축, 보안 헤더, URL 리라이트.
  • HAProxy: 가중치 기반 라우팅(Blue/Green/Canary), 액티브 헬스체크, 레이트 리밋/스틱 테이블.

2) Blue-Green: HAProxy에서 스위칭, Nginx는 변하지 않는다

Nginx는 외곽(에지) 고정, 트래픽 스위칭은 HAProxy에서만 수행합니다. 배포 시 유휴 그룹(Green)에 새 버전을 올린 뒤, 헬스가 OK면 가중치 전환 → 모니터링 → 구버전 종료 순서로 진행합니다.

2-1) HAProxy 기본 설정(HTTP)

# /etc/haproxy/haproxy.cfg (발췌)
global
  log /dev/log local0
  maxconn 10000

defaults
  log global
  mode http
  option httplog
  option dontlognull
  timeout connect 5s
  timeout client  65s
  timeout server  65s
  default-server inter 2s fall 3 rise 2   # 액티브 헬스 주기/판정

frontend fe_app
  bind 127.0.0.1:9000
  default_backend be_app

backend be_app
  balance leastconn
  option httpchk GET /actuator/health
  http-check expect status 200
  server blue  127.0.0.1:8081 weight 10 check
  server green 127.0.0.1:8082 weight 0  check

weight를 전환하는 순간 트래픽이 이동합니다. 실패 서버는 자동 격리됩니다.

2-2) Nginx는 HAProxy만 백엔드로

# /etc/nginx/sites-available/app.example.com (발췌)
server {
  listen 443 ssl http2;
  server_name app.example.com;
  # TLS/헤더/캐시 등은 기존 구성
  location / {
    proxy_pass http://127.0.0.1:9000;  # <-- HAProxy로 집약
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
  }
}

3) Canary(점진 배포): 비율·쿠키·헤더 전략

HAProxy는 ACL + map/stick-table로 유연한 분기가 가능합니다.

3-1) 비율 기반(가중치)

backend be_app
  balance roundrobin
  option httpchk GET /actuator/health
  http-check expect status 200
  server stable 127.0.0.1:8081 weight 95 check
  server canary 127.0.0.1:8082 weight 5  check

3-2) 쿠키/헤더 기반(내부 QA만 canary)

frontend fe_app
  acl is_canary hdr_sub(cookie) -i CANARY=1
  use_backend be_canary if is_canary
  default_backend be_stable

backend be_stable
  balance leastconn
  server s1 127.0.0.1:8081 check

backend be_canary
  balance leastconn
  server c1 127.0.0.1:8082 check

: 비율 0% + 쿠키 기반을 상시 둬서, 장애 시 즉시 0% 롤백이 가능합니다.


4) 액티브 헬스체크: Nginx의 빈 곳을 매운다

  • Nginx OSS는 능동 헬스체크가 없어 proxy_next_upstream 같은 수동 전환만 지원합니다.
  • HAProxy는 option httpchk, http-check expect상시 상태 확인 후 불량 노드를 빼 줍니다.
# 핸드쉐이크만으로도 헬스 가능(HTTPS 백엔드)
backend be_app_tls
  mode tcp
  option ssl-hello-chk
  server app1 127.0.0.1:8443 check ssl verify none

5) 스틱 테이블로 폭주 IP 제어(로그인/검색 보호)

frontend fe_app
  stick-table type ip size 200k expire 10m store http_req_rate(10s)
  http-request track-sc0 src
  acl abuse  sc_http_req_rate(0) gt 50
  http-request deny if abuse

과도한 요청을 에지(Nginx)에서 1차 제한하고, 코어(HAProxy)에서 2차 제한하면 더 견고합니다.


6) 배포 스크립트(Blue→Green 전환) 예시

# /usr/local/sbin/hap-switch.sh
#!/usr/bin/env bash
set -euo pipefail
TARGET="${1:-green}"   # blue|green
if [ "$TARGET" = "green" ]; then
  echo "set server be_app/blue weight 0"   | socat stdio tcp4:127.0.0.1:9999
  echo "set server be_app/green weight 10" | socat stdio tcp4:127.0.0.1:9999
else
  echo "set server be_app/blue weight 10"  | socat stdio tcp4:127.0.0.1:9999
  echo "set server be_app/green weight 0"  | socat stdio tcp4:127.0.0.1:9999
fi
echo "$(date -Is) switched to $TARGET" >> /var/log/hap-switch.log

stats socket :9999를 HAProxy에 열어두면 런타임에 weight 등을 바꿀 수 있습니다.


7) 무중단 DB 마이그레이션: 배포와 함께 가는 실전 루틴

애플리케이션 무중단이어도 스키마 변경이 다운타임을 만들면 소용이 없습니다. 다음 원칙을 지키세요.

  • 호환성 우선: 새 코드와 구 스키마가 공존 가능해야 합니다(Expand-Migrate-Contract).
  • 작게 쪼개기: DDL을 여러 번으로 나눠 락/재빌드 시간을 줄입니다.
  • 읽기/쓰기 분리: 필요한 경우 일시적으로 쓰기 경로를 큐잉/버퍼링합니다.

7-1) Expand 단계(추가/널허용/디폴트)

-- 1) 새 컬럼 추가(널허용)
ALTER TABLE orders ADD COLUMN note VARCHAR(255);

-- 2) 코드 배포: 새 컬럼 쓰기 시작(기존 컬럼도 유지)
-- 3) 백필 작업(배치/잡)
UPDATE orders SET note = concat('ord#', id) WHERE note IS NULL;

7-2) Migrate 단계(인덱스/타입 변경은 안전하게)

  • PostgreSQL: CONCURRENTLY로 인덱스 생성 → 스위치.
  • MySQL: 온라인 DDL(엔진/버전 제약), 필요 시 gh-ost/pt-osc 사용.
-- PostgreSQL 예시
CREATE INDEX CONCURRENTLY idx_orders_note ON orders(note);
DROP INDEX CONCURRENTLY idx_orders_old;

7-3) Contract 단계(구 컬럼/제약 제거)

-- 코드가 새 컬럼만 사용함을 확인 후
ALTER TABLE orders DROP COLUMN old_note;

체크리스트:

  • DDL 전: 슬로우 쿼리/락 위험 경로 파악, 트래픽 저점 시간 선택
  • 배포 전: 앱이 양쪽 스키마를 모두 처리 가능(기존 필드 null 허용)
  • 배포 중: HAProxy Canary 5% → 20% → 100% 확대, 에러/지연 추적
  • 배포 후: Contract DDL은 별도 윈도우에서 진행

8) 관찰성: 어느 계층에서 문제가 났는지 즉시 알기

  • HAProxy: http_err_rate, hrsp_5xx, 백엔드별 status/헬스.
  • Nginx: $upstream_status, $upstream_response_time 포함 로그 포맷.
  • 앱/DB: p95/에러율/DB 락 대기/슬로우 쿼리.

9) 자주 겪는 함정과 해결

  • 헬스 OK인데 5xx 증가 → 헬스 엔드포인트가 실제 의존성(외부 API/DB)을 검증하는지 재점검.
  • 세션 유실 → 외부 세션 저장소(예: Redis)로 이동, 또는 cookie-based 스티키 세션.
  • DB 마이그 중 락 장기화 → 배치 크기 축소, 오프피크 실행, 온라인 DDL 툴로 우회.
  • 롤백 늦음 → HAProxy 런타임 스위치(stats socket) 스크립트를 미리 준비.

10) 마무리

Nginx는 에지, HAProxy는 코어 로드밸런싱에 집중시키면, 배포·복구·확장성 모두 향상됩니다. Blue-Green/Canary 스위치를 HAProxy로 일원화하고, 스키마 변경은 Expand→Migrate→Contract로 작게 나눠서 진행하세요. 이 구조가 잡히면 “배포는 평범한 일”이 됩니다.