서비스 오픈 일주일 전, 운영팀에서 연락이 왔습니다. "지금 WAS가 서버 한 대인데, 이게 죽으면 어떻게 됩니까?" 개발팀은 코드만 짰고, 인프라 담당자는 "로드밸런서 붙이면 됩니다"라고 했지만 — 어떤 로드밸런서를 어떻게 구성해야 하는지, Health Check는 어디로 잡아야 하는지, Active 장비가 죽으면 Standby로 어떻게 넘어가는지 아무도 정확히 모릅니다.
이 모듈에서는 L4/L7 차이를 구분하고, VIP+keepalived 기반 이중화 원리를 이해하며, Nginx upstream으로 Health Check와 분산 알고리즘을 직접 설정하는 과정을 다룹니다.
- 1L4와 L7 로드밸런서의 동작 계층 차이와 사용 기준을 설명할 수 있다
- 2VIP와 keepalived VRRP로 LB 이중화가 동작하는 원리를 이해할 수 있다
- 3Nginx upstream에서 분산 알고리즘과 Health Check를 설정할 수 있다
- 4Active/Standby Failover 동작 순서를 추적하고 검증할 수 있다
- 5HAProxy, Nginx upstream, F5의 상황별 선택 기준을 설명할 수 있다
L4 vs L7 — 어느 계층에서 결정하나
L4 LB: IP와 포트만 본다
로드밸런서를 처음 접하면 "그냥 트래픽 나눠주는 거 아닌가?"라고 생각하기 쉽습니다. 하지만 어느 계층에서 결정을 내리느냐에 따라 할 수 있는 것과 없는 것이 완전히 달라집니다. L4 LB는 OSI 4계층(Transport Layer)에서 동작하기 때문에 패킷의 IP 주소와 TCP/UDP 포트 번호만 볼 수 있습니다. HTTP 메서드가 GET인지 POST인지, URL이 /api/인지 /static/인지 알 수 없습니다.

L4 LB 동작 방식:
클라이언트 → [VIP:443] → L4 LB → 백엔드 서버 A (192.168.10.11:443)
백엔드 서버 B (192.168.10.12:443)
백엔드 서버 C (192.168.10.13:443)
L4 LB는 TCP 연결 수립 단계(SYN 패킷)에서 분산 결정을 내립니다. 연결이 맺어진 후의 HTTP 요청 내용은 보지 않습니다. 덕분에 처리 오버헤드가 낮고 속도가 빠릅니다.
대표적인 사용 사례:
- TCP 기반 서비스 (DB, Redis, SMTP)
- 단순 HTTP 트래픽 분산 (URL 기반 라우팅 불필요)
- 매우 높은 처리량이 필요한 환경
장단점:
| 항목 | 내용 |
|---|---|
| 처리 속도 | 빠름 (패킷 레벨 처리) |
| URL 기반 라우팅 | 불가 |
| SSL Termination | 불가 (패킷을 해석 못함) |
| 구현 예 | AWS NLB, HAProxy (TCP mode), LVS |
L7 LB: HTTP 내용까지 본다
API 요청과 정적 파일 요청을 서로 다른 서버군으로 보내야 합니다. API는 WAS 클러스터로, /static/은 CDN Origin으로, /admin/은 어드민 서버로 분리하고 싶습니다. L4 LB는 URL을 볼 수 없어서 이런 라우팅이 불가능합니다. HTTP 내용을 읽어야만 가능한 이 역할을 L7 LB가 합니다.
L7 LB는 OSI 7계층(Application Layer)에서 동작합니다. HTTP 요청을 완전히 파싱하기 때문에 URL 경로, Host 헤더, 쿠키, HTTP 메서드 등을 기반으로 분산 결정을 내릴 수 있습니다. 실제 운영 환경에서 웹 서비스를 위해 가장 많이 쓰이는 방식입니다.
L7 LB 동작 방식 (콘텐츠 기반 라우팅 예시):
클라이언트 GET /api/users → L7 LB → API 서버 A (192.168.10.11:8080)
클라이언트 GET /static/... → L7 LB → CDN Origin (192.168.10.20:80)
클라이언트 GET /admin/... → L7 LB → Admin 서버 (192.168.10.30:8080)
장단점:
| 항목 | 내용 |
|---|---|
| URL/헤더 기반 라우팅 | 가능 |
| SSL Termination | 가능 (인증서를 LB에서 처리) |
| 헬스체크 정밀도 | HTTP 응답 코드, body까지 확인 가능 |
| 처리 오버헤드 | L4보다 높음 |
| 구현 예 | Nginx upstream, HAProxy (HTTP mode), AWS ALB |
VIP와 keepalived — LB 이중화 원리
VIP와 VRRP — Failover가 동작하는 방식
로드밸런서 자체가 단일 장애점(SPOF)이 되면 안 됩니다. LB 이중화는 Active/Standby 구성으로 해결합니다. 이때 핵심 개념이 VIP(Virtual IP)입니다.
VIP는 Active LB 장비가 보유하는 가상 IP 주소입니다. 클라이언트와 DNS는 항상 이 VIP로 접속합니다. Active LB가 장애로 죽으면 Standby LB가 VIP를 인계받아 서비스를 이어받습니다. 이 과정에서 클라이언트는 IP 변경을 인지하지 못합니다.
VRRP(Virtual Router Redundancy Protocol) 기반 keepalived:
[Active LB] 192.168.1.10 (Real IP) ← VIP 192.168.1.100 보유
↕ VRRP heartbeat (224.0.0.18, UDP)
[Standby LB] 192.168.1.11 (Real IP) ← VIP 없음, 대기 중
Failover 발생 순서:
- Active LB가 응답 없음 (VRRP 패킷 중단)
- Standby가
MASTER_DOWN_INTERVAL내에 감지 - Standby가 VIP를 자신의 NIC에 추가 (
ip addr add 192.168.1.100/24 dev eth0) - Gratuitous ARP 발송 → 스위치/라우터 ARP 캐시 갱신
- 트래픽이 Standby(이제 새 Active)로 유입
keepalived 설정 예시 (Active 장비):
# /etc/keepalived/keepalived.conf (Active)
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51 # Active/Standby 동일해야 함
priority 100 # Standby는 90 등 낮게 설정
advert_int 1 # 1초마다 VRRP 광고
authentication {
auth_type PASS
auth_pass secure1234 # Active/Standby 동일
}
virtual_ipaddress {
192.168.1.100/24 # VIP
}
# LB 프로세스 죽으면 우선순위 낮춰 Failover 유도
track_script {
chk_nginx
}
}
vrrp_script chk_nginx {
script "systemctl is-active nginx"
interval 2
weight -20 # nginx 죽으면 priority를 20 낮춤
}
# /etc/keepalived/keepalived.conf (Standby)
vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 90 # Active(100)보다 낮게
advert_int 1
# ... (나머지 동일)
}

Health Check 전략
TCP/HTTP/Script — 세 가지 Health Check 방식
Health Check를 제대로 설정하지 않으면, 서버가 실제로 동작 불능이 되어도 LB가 계속 트래픽을 보냅니다. 반대로 너무 민감하게 설정하면 일시적 지연에도 서버를 제외시켜 불필요한 Failover가 생깁니다.
세 가지 방식 비교:
| 방식 | 확인 내용 | 신뢰도 | 적합 상황 |
|---|---|---|---|
| TCP | 포트 연결 가능 여부 | 낮음 | 포트만 열려있으면 통과 |
| HTTP | 지정 URL의 응답 코드 | 중간 | 앱 레벨 확인 가능 |
| Script | 커스텀 스크립트 실행 결과 | 높음 | DB 연결, 임계치 체크 등 복합 조건 |
Nginx upstream Health Check 설정:
# /etc/nginx/conf.d/upstream.conf
upstream backend_pool {
# 분산 알고리즘 (기본: Round Robin)
# least_conn; # 최소 연결 수 서버로
# ip_hash; # 클라이언트 IP 기반 고정 (Sticky Session)
server 192.168.10.11:8080 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.10.12:8080 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.10.13:8080 weight=2 max_fails=3 fail_timeout=30s;
# weight=2: 다른 서버의 2배 트래픽 수신
keepalive 32; # 백엔드 연결 풀 유지
}
server {
listen 80;
location / {
proxy_pass http://backend_pool;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Health Check 실패 서버 재시도
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
}
# Health Check 엔드포인트 (모니터링용)
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
HAProxy Health Check 설정 (더 세밀한 제어):
# /etc/haproxy/haproxy.cfg
backend web_servers
balance roundrobin
option httpchk GET /actuator/health # Spring Boot health endpoint
http-check expect status 200
server web1 192.168.10.11:8080 check inter 5s rise 2 fall 3
server web2 192.168.10.12:8080 check inter 5s rise 2 fall 3
# inter 5s: 5초마다 체크
# rise 2: 2번 성공하면 UP으로 복구
# fall 3: 3번 실패하면 DOWN으로 제외
분산 알고리즘 선택
실습 — 상태 확인
nginx -T는 include된 모든 설정 파일을 합쳐서 출력합니다. upstream 블록을 grep해서 현재 백엔드 풀 구성을 확인합니다. max_fails, fail_timeout, weight 값을 검토합니다.
# Nginx 전체 설정에서 upstream 관련 설정 확인
nginx -T | grep -A 20 upstream
# LB를 통한 헬스 엔드포인트 응답 확인
curl -v http://192.168.1.100/health
# upstream 각 서버 직접 확인 (LB 우회)
curl -v http://192.168.10.11:8080/actuator/health
curl -v http://192.168.10.12:8080/actuator/health
# Nginx 상태 모듈 (stub_status 활성화된 경우)
curl http://localhost/nginx-status
nginx -T | grep -A 20 upstream- nginx -T 출력에 upstream 블록이 나타나고 server 목록이 정확한가
- curl http://VIP/health 가 200 OK를 반환하는가
- 각 백엔드 서버 직접 접속 시 모두 응답하는가
- Nginx error.log에 upstream 관련 에러가 없는가
Active LB 장비에서 실행하면 Real IP와 VIP 두 개가 모두 보여야 합니다. Standby 장비에서는 Real IP만 보입니다.
# 현재 이 장비가 VIP를 보유하고 있는지 확인
ip addr show | grep -E 'inet.*eth'
# Active 장비 예시:
# inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0
# inet 192.168.1.100/24 scope global secondary eth0 ← VIP
# keepalived 서비스 상태 및 현재 역할 확인
systemctl status keepalived
# keepalived 로그에서 MASTER/BACKUP 전환 이력 확인
journalctl -u keepalived -n 50 --no-pager | grep -E "MASTER|BACKUP|Entering"
ip addr show | grep -E 'inet.*eth'- Active LB에서 ip addr show 출력에 VIP가 secondary로 보이는가
- Standby LB에서 VIP가 보이지 않는가
- systemctl status keepalived가 active (running) 상태인가
- journalctl에서 'Entering MASTER STATE' 이력이 보이는가
트러블슈팅
원인: Health Check 경로를 /로 설정했을 때 자주 발생합니다. /는 단순히 웹 서버가 살아있는지만 확인하고, 애플리케이션이 실제로 정상인지(DB 연결, 의존 서비스 연결 등)는 확인하지 않습니다. Nginx나 Tomcat은 떠 있지만 DB 연결 풀이 고갈된 상태에서 LB는 계속 정상으로 판단합니다.
# 현재 Health Check 경로 확인
nginx -T | grep -E "(health|check|upstream)"
grep -r "httpchk\|health" /etc/haproxy/
# 앱이 제공하는 실제 헬스 엔드포인트로 변경
# Spring Boot: /actuator/health (DB, Redis 연결까지 체크)
# 직접 만든 앱: /health/ready (의존성 체크 로직 포함)
# 헬스 엔드포인트 응답 내용 확인
curl -s http://192.168.10.11:8080/actuator/health | python3 -m json.tool
# 정상: {"status":"UP","components":{"db":{"status":"UP"},...}}
# 비정상: {"status":"DOWN","components":{"db":{"status":"DOWN"},...}}
해결: Health Check 경로를 앱의 /actuator/health 또는 커스텀 /health/ready 엔드포인트로 변경하고, 해당 엔드포인트가 DB 연결, 캐시 서버 연결까지 체크하도록 앱 팀과 협의합니다.
원인: Standby LB에 maxconn 설정이 Active보다 낮게 잡혀 있거나, Standby가 오래 대기하다가 갑자기 Active가 되면서 TCP 연결 큐가 쌓립니다. HAProxy의 경우 Standby의 기본 maxconn이 다르게 설정된 경우입니다. Nginx의 경우 worker_processes나 worker_connections가 낮을 수 있습니다.
# HAProxy Standby 서버의 maxconn 확인
grep -E "maxconn|timeout" /etc/haproxy/haproxy.cfg
# HAProxy 런타임 통계 확인 (socat 필요)
echo "show info" | socat stdio /var/run/haproxy/admin.sock | grep -E "MaxConn|CurrConns"
# Nginx worker 설정 확인
nginx -T | grep -E "worker_processes|worker_connections"
# 현재 연결 수 확인
ss -s
netstat -an | grep ESTABLISHED | wc -l
해결: Active와 Standby의 maxconn, worker_connections 설정을 동일하게 맞춥니다. Failover 발생 시 systemctl reload haproxy 또는 nginx -s reload로 연결 큐를 초기화합니다. 장기적으로는 양쪽 LB 설정을 Ansible이나 Chef로 동기화하여 설정 차이가 생기지 않게 관리합니다.
실제 업무에서 이 지식이 쓰이는 상황:
인프라 담당자가 LB를 처음 구성할 때 가장 많이 받는 질문 두 가지가 있습니다. "Health Check 경로 어떻게 설정하나요?"와 "Active 장비가 죽으면 어떻게 됩니까?"입니다.
1. 신규 서비스 LB 구성 체크리스트:
# 1. upstream 서버 목록과 분산 알고리즘 결정
# 2. Health Check 경로 앱 팀과 협의 (/actuator/health 권장)
# 3. max_fails, fail_timeout 설정 (3회 실패, 30초 제외)
# 4. VIP 설정 및 keepalived 구성
# 5. Failover 테스트: Active keepalived 중지 후 VIP 이동 확인
systemctl stop keepalived # Active에서 실행
ip addr show # Standby에서 VIP 인계 확인
2. 장애 발생 시 LB 점검 순서:
# LB 상태 확인
systemctl status nginx haproxy keepalived
# VIP 보유 장비 확인
ip addr show | grep 'VIP주소'
# 백엔드 서버 상태 확인 (HAProxy 통계 페이지)
curl http://localhost:8404/stats # HAProxy stats 페이지 (설정 필요)
# Nginx upstream 에러 확인
grep "upstream" /var/log/nginx/error.log | tail -20
3. HAProxy vs Nginx upstream vs F5 선택 기준:
| 제품 | 적합한 상황 |
|---|---|
| Nginx upstream | 이미 Nginx 사용 중인 환경, 간단한 L7 분산 |
| HAProxy | 세밀한 Health Check, TCP/HTTP 혼용, 통계 대시보드 필요 |
| F5 BIG-IP | 금융/공공기관, 하드웨어 어플라이언스 필요, 벤더 지원 계약 필요 |
| AWS ALB/NLB | 클라우드 네이티브 환경, 관리 부담 최소화 |
LB 이중화와 Health Check를 올바르게 구성하는 것만으로도 서비스 가용성을 99.9% 이상으로 끌어올릴 수 있습니다. 다음 모듈에서는 폐쇄망 환경에서 내부 서버가 외부 기관과 통신하는 Proxy/NAT 구조를 다룹니다.