ip-hash 알고리즘의 동작 방식
nginx의 ip-hash 기반 로드 밸런싱은 요청의 클라이언트 IP 주소를 기반으로 후방 서버에 요청을 분산합니다. 내부적으로는 다음과 같은 해시 계산을 수행합니다:
for (;;) {
for (i = 0; i < 3; i++) {
hash = (hash * 113 + ip_addr[i]) % 6271;
}
p = hash % peers_count;
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << (p % (8 * sizeof(uintptr_t)));
if (!(tried_mask[n] & m)) {
peer = &peers[p];
if (!peer->down &&
(peer->max_fails == 0 || peer->fails < peer->max_fails) ||
(now - peer->accessed > peer->fail_timeout)) {
break;
}
tried_mask[n] |= m;
tries--;
}
if (++attempt_count >= 20) {
return get_next_round_robin_peer();
}
}
해당 코드에서 주의할 점은, 실제 IP 주소의 네 개 구간(예: 192.168.1.1) 중 상위 세 구간만 해시에 사용된다는 것입니다. 즉, 첫 세 번째 숫자가 동일한 모든 클라이언트는 동일한 후방 서버로 할당됩니다.
문제 발생 원인
이러한 설계는 일반적인 환경에서는 효과적이나, 특히 네트워크 레벨에서의 로드 밸런서(예: AWS ELB, F5, HAProxy 등)를 거쳐 올 경우 문제가 발생합니다. 이들 장치는 고정된 공유 IP 풀을 사용하며, 클라이언트의 실제 IP가 아닌 로드 밸런서의 내부 또는 공용 IP를 전달하게 됩니다.
결과적으로 여러 사용자가 동일한 중앙 로드 밸런서의 한 정적 IP를 통해 접속하면, nginx는 모든 요청을 동일한 백엔드 서버로 전달하게 되어 로드 밸런싱이 실패하고, 특정 서버에 과도한 부하가 발생합니다.
해결 방법
1. ip-hash 알고리즘 수정 (직접 코드 조정)
기존 해시 함수에서 전체 4개의 IP 구간을 포함하도록 변경할 수 있습니다. 예를 들어, 다음처럼 해시에 네 번째 구간까지 반영:
for (i = 0; i < 4; i++) {
hash = (hash * 113 + ip_addr[i]) % 6271;
}
이렇게 하면 동일한 첫 세 구간을 가진 클라이언트라도 네 번째 구간 차이에 따라 다른 백엔드 서버로 분배됩니다. 단, 이는 nginx 소스 코드를 직접 수정해야 하며, 업데이트 시 재컴파일이 필요합니다.
2. HTTP_X_FORWARDED_FOR 헤더 기반 진짜 클라이언트 IP 추출
보다 실용적인 방법은 X-Forwarded-For 헤더를 활용하여 실제 클라이언트의 IP를 식별하는 것입니다. 이를 위해 nginx의 ngx_http_realip_module 모듈을 사용합니다:
server {
listen 80;
server_name example.com;
location / {
# 로드 밸런서나 프록시의 공용 IP를 신뢰하지 않도록 설정
set_real_ip_from 10.48.10.0/24;
set_real_ip_from 61.22.22.22;
set_real_ip_from 192.168.0.0/16;
# 실제 클라이언트 IP를 추출할 헤더 지정
real_ip_header X-Forwarded-For;
# 중첩된 프록시에서도 최종 클라이언트 IP를 확인
real_ip_recursive on;
# 이제 ip-hash가 실제 클라이언트의 IP를 기준으로 작동
proxy_pass http://backend_pool;
}
}
이 설정을 통해 ip_hash 지시어가 실제로 사용자 각각의 고유한 IP를 기반으로 동작하게 되어, 균형 잡힌 분산이 가능합니다.
요약
- ip-hash는 기본적으로 클라이언트의 원본 IP를 기반으로 하지만, 중간 프록시가 존재할 경우 해당 정보가 손실됨.
- 해시 계산에서 네 번째 IP 구간 미반영 → 비균형 분산 발생.
- 해결책: 실제 클라이언트 IP를 추출하기 위한
real_ip_header및real_ip_recursive설정 권장. - 직접 코드 수정은 복잡하고 유지보수 어렵기에, 모듈 기반 접근이 더 안정적.