HAProxy 를 앞에 두면 백엔드 서버는 접속 출발지를 전부 HAProxy 의 IP 로 본다. 웹 서버 액세스 로그에 프록시 IP 만 남고, IP 기반 접근 제어와 지역 판별이 모두 무의미해진다. Keepalived 로 VIP 를 띄웠는지 여부는 이 문제와 무관하다. Keepalived 는 L3 에서 VIP 를 옮길 뿐이고, 원래 IP 를 지우는 것은 HAProxy 가 백엔드로 새 연결을 맺기 때문이다.
방법은 둘이다. HTTP 모드면 헤더에 실어 보내고, TCP 모드면 PROXY 프로토콜을 쓴다.
frontend web_front
bind *:80
mode http
option httplog
option forwardfor except 127.0.0.0/8
default_backend web_back
option forwardfor 는 요청에 X-Forwarded-For: <클라이언트IP> 를 붙인다. 백엔드 애플리케이션이 이 헤더를 읽어야 효과가 있다. nginx 라면 다음과 같다.
set_real_ip_from 10.0.0.10; # HAProxy 주소
real_ip_header X-Forwarded-For;
Apache httpd 라면 mod_remoteip 을 쓴다.
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.10
신뢰 범위를 반드시 지정한다. set_real_ip_from 이나 RemoteIPTrustedProxy 없이 헤더를 그대로 믿으면 클라이언트가 헤더를 위조해 로그와 접근 제어를 속일 수 있다. HAProxy 쪽에서도 들어온 헤더를 지우고 새로 쓰게 하는 편이 안전하다.
http-request del-header X-Forwarded-For
option forwardfor
mode tcp 에서는 HTTP 헤더를 건드릴 수 없다. HTTPS 를 복호화하지 않고 통과시키는 구성, 데이터베이스, LDAP, SMTP 같은 비 HTTP 프로토콜이 여기에 해당한다. 이때는 연결 맨 앞에 출발지 정보를 담은 한 줄(또는 바이너리 헤더)을 보내는 PROXY 프로토콜을 쓴다.
HAProxy 쪽은 백엔드 서버 줄에 send-proxy(v1) 또는 send-proxy-v2 를 붙인다.
frontend tcp_front
bind *:443
mode tcp
default_backend tcp_back
backend tcp_back
mode tcp
server web1 10.0.0.21:443 check send-proxy-v2
server web2 10.0.0.22:443 check send-proxy-v2
백엔드도 PROXY 프로토콜을 이해하도록 켜야 한다. 켜지 않으면 서버는 첫 줄을 프로토콜 데이터로 읽고 요청을 깨뜨린다. 반대로 켜 두면 PROXY 헤더 없이 들어오는 연결을 거부하므로, 헬스 체크나 직접 접속 경로가 있으면 함께 정리해야 한다.
nginx:
server {
listen 443 ssl proxy_protocol;
set_real_ip_from 10.0.0.10;
real_ip_header proxy_protocol;
}
HAProxy 를 백엔드로 두는 경우에는 bind ... accept-proxy 를 쓴다.
지원 여부는 제품마다 다르다. nginx, HAProxy, Postfix, Dovecot, AWS NLB, Envoy 는 지원하고, 지원하지 않는 서버에는 이 방식을 쓸 수 없다.
| 상황 | 방법 |
|---|---|
| HAProxy 가 TLS 를 종료하고 HTTP 로 전달 | option forwardfor |
| HTTPS 를 그대로 통과(TLS passthrough) | send-proxy-v2 |
| 비 HTTP 프로토콜 | send-proxy-v2 |
| 백엔드가 PROXY 프로토콜을 지원하지 않음 | HTTP 모드로 바꾸고 헤더를 쓰거나, 클라이언트 IP 보존을 포기 |