Nginx 로드밸런싱 완전 정복
Nginx는 강력한 리버스 프록시와 로드밸런서 기능을 내장하고 있습니다. upstream 블록만으로 Round Robin, Least Connections, IP Hash, Weighted 방식을 모두 구현할 수 있으며, 추가 소프트웨어 없이도 프로덕션 수준의 로드밸런싱 환경을 구성할 수 있습니다.
upstream 블록 기본 구조
Nginx 로드밸런싱의 핵심은 upstream 블록입니다. 여기에 백엔드 서버 목록과 분산 방식을 정의하고, proxy_pass로 연결합니다.
# /etc/nginx/conf.d/load-balancer.conf
upstream backend_servers {
# 기본값: Round Robin
server app1.example.com:8080;
server app2.example.com:8080;
server app3.example.com:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
알고리즘별 설정
Round Robin (기본값)
별도의 지시어 없이 server 목록만 나열하면 Round Robin이 적용됩니다.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
요청이 들어올 때마다 10.0.0.1 → 10.0.0.2 → 10.0.0.3 → 10.0.0.1 순서로 분배됩니다.
Weighted Round Robin
성능이 다른 서버가 혼재할 때 가중치를 설정합니다. 가중치가 높을수록 더 많은 요청을 처리합니다.
upstream backend {
server 10.0.0.1:8080 weight=5; # 전체의 5/8 처리
server 10.0.0.2:8080 weight=2; # 전체의 2/8 처리
server 10.0.0.3:8080 weight=1; # 전체의 1/8 처리
}
실전 예시: 신규 고사양 서버(32코어)와 구형 서버(8코어)를 혼합 운영할 때 성능 비율에 맞게 가중치를 설정.
Least Connections
활성 연결 수가 가장 적은 서버로 요청을 보냅니다. 응답 시간이 긴 API 서버에 적합합니다.
upstream backend {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
가중치와 함께 사용할 수 있습니다:
upstream backend {
least_conn;
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=1;
}
IP Hash
클라이언트 IP의 해시값으로 서버를 결정합니다. 같은 IP는 항상 같은 서버로 연결됩니다.
upstream backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
주의사항:
- 모바일 사용자나 NAT 환경에서는 많은 사용자가 같은 IP를 공유해 특정 서버에 부하 집중 가능
- 서버를 추가하면 해시 재계산으로 기존 매핑이 바뀔 수 있음
Hash (사용자 정의 키)
임의의 변수를 해시 키로 사용합니다. URI 기반으로 캐시 서버를 분산할 때 유용합니다.
upstream backend {
hash $request_uri consistent; # consistent: 일관된 해시링 사용
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
consistent 옵션을 추가하면 Consistent Hashing이 활성화되어, 서버 추가·제거 시 영향받는 키가 최소화됩니다.
server 파라미터 완전 정리
upstream backend {
server 10.0.0.1:8080
weight=3 # 가중치 (기본값: 1)
max_conns=100 # 최대 동시 연결 수 (초과 시 큐 대기 또는 503)
max_fails=3 # fail_timeout 내 최대 실패 횟수 (기본값: 1)
fail_timeout=30s # 실패 카운트 기간 & 서버 배제 기간 (기본값: 10s)
backup; # 다른 모든 서버 다운 시에만 사용하는 예비 서버
server 10.0.0.2:8080;
server 10.0.0.3:8080 down; # 수동으로 비활성화 (점검 중)
}
max_fails와 fail_timeout 동작 원리
30초 안에 3번 실패 → 해당 서버를 30초 동안 배제
30초 후 → 자동으로 복구 시도 (실제 요청 1건 보내서 성공하면 복귀)
실전 설정 예시:
upstream backend {
server app1.example.com:8080 max_fails=3 fail_timeout=60s;
server app2.example.com:8080 max_fails=3 fail_timeout=60s;
server backup.example.com:8080 backup; # 모든 서버 장애 시 임시 대응 페이지
}
Keepalive 연결 풀 설정
기본적으로 Nginx는 백엔드 서버와 매 요청마다 새로운 TCP 연결을 맺습니다. keepalive를 설정하면 연결을 재사용해 성능이 크게 향상됩니다.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 32; # 각 worker당 유지할 유휴 연결 수
keepalive_requests 100; # 하나의 연결로 처리할 최대 요청 수 (Nginx 1.15.3+)
keepalive_time 1h; # 연결 최대 유지 시간
keepalive_timeout 60s; # 유휴 연결 타임아웃
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # keepalive를 위해 HTTP/1.1 필수
proxy_set_header Connection ""; # Connection: close 헤더 제거
}
}
성능 효과: TCP 3-way handshake 생략으로 요청당 수 ms 단축. 고TPS 환경에서 매우 효과적.
실전 구성: 완전한 로드밸런서 설정
아래는 프로덕션에서 바로 사용할 수 있는 완전한 설정 예시입니다.
# /etc/nginx/conf.d/app-lb.conf
# 로그 포맷 정의 (업스트림 서버 정보 포함)
log_format upstream_info '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream: $upstream_addr '
'upstream_time: $upstream_response_time '
'request_time: $request_time';
# 업스트림 그룹 정의
upstream app_backend {
least_conn;
server 10.0.0.1:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
upstream static_backend {
server 10.0.0.10:80;
server 10.0.0.11:80;
keepalive 16;
}
server {
listen 80;
server_name example.com www.example.com;
access_log /var/log/nginx/access.log upstream_info;
error_log /var/log/nginx/error.log warn;
# 정적 파일은 별도 서버 그룹으로
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
proxy_pass http://static_backend;
proxy_set_header Host $host;
proxy_cache_valid 200 1d;
expires 7d;
}
# API는 최소 연결 방식
location /api/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# 재시도 설정
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
# 그 외 요청
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 장애 페이지
error_page 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}
proxy_next_upstream: 자동 재시도
서버 응답에 오류가 있을 때 자동으로 다음 서버로 재시도하는 설정입니다.
proxy_next_upstream error timeout http_502 http_503 http_504;
# error : 서버 연결 오류
# timeout : 응답 타임아웃
# http_502 : Bad Gateway
# http_503 : Service Unavailable
# http_504 : Gateway Timeout
proxy_next_upstream_tries 3; # 최대 3번까지 재시도
proxy_next_upstream_timeout 10s; # 재시도 전체 제한 시간
주의: POST, PUT 등 멱등성이 없는 요청에 재시도를 적용하면 중복 처리 위험이 있습니다.
# 안전하게: GET 요청만 재시도
proxy_next_upstream error timeout http_502 http_503;
# non_idempotent 옵션을 추가하면 POST도 재시도 (위험 - 신중하게 사용)
Nginx 상태 페이지로 모니터링
stub_status 모듈을 활성화하면 간단한 통계를 확인할 수 있습니다.
server {
listen 8080;
server_name localhost;
location /nginx_status {
stub_status;
allow 127.0.0.1;
allow 10.0.0.0/8; # 내부 모니터링 서버 허용
deny all;
}
}
접속하면 아래와 같은 정보가 출력됩니다:
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106
로드밸런싱 동작 확인
테스트용 간단 설정
# 3개 백엔드 서버를 Docker로 빠르게 실행
docker run -d --name app1 -p 8081:80 \
-e "RESPONSE=Server-1" nginx
docker run -d --name app2 -p 8082:80 \
-e "RESPONSE=Server-2" nginx
docker run -d --name app3 -p 8083:80 \
-e "RESPONSE=Server-3" nginx
upstream test_backend {
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
# 라운드 로빈 확인
for i in {1..6}; do
curl -s http://localhost/ | grep -o "Server-[0-9]"
done
# 출력 예시:
# Server-1
# Server-2
# Server-3
# Server-1
# Server-2
# Server-3
로그에서 업스트림 서버 확인
tail -f /var/log/nginx/access.log | grep upstream
# 출력 예시:
# upstream: 10.0.0.1:8080 upstream_time: 0.023
# upstream: 10.0.0.2:8080 upstream_time: 0.015
# upstream: 10.0.0.3:8080 upstream_time: 0.031
핵심 요약
| 알고리즘 | 지시어 | 적합한 상황 |
|---|---|---|
| Round Robin | (기본값) | 동일 스펙 서버, 짧은 요청 |
| Weighted RR | weight=N | 서버 스펙 다를 때 |
| Least Connections | least_conn | 처리 시간 불균일 |
| IP Hash | ip_hash | 세션 공유 없이 고정 라우팅 |
| Custom Hash | hash $var | URI 기반 캐시 분산 |
다음 페이지에서는 Apache mod_proxy_balancer를 사용한 로드밸런싱을 알아봅니다.