「HAProxy」- 在 TCP Mode 下,从 Haproxy 向 Nginx 传递客户端真实网络地址

  CREATED BY JENKINSBOT

问题描述

客户端 Client 访问对外开放的 HAProxy 服务,然后由 HAProxy 负责负载均衡,来向后端的 Nginx 服务转发流量。如图:

但是,从 Nginx 获取到的 IP 地址为 HAProxy 的IP地址,而不是客户端 Client 的真实 IP 地址。

该笔记将记录:在 HAProxy + Nginx 中,如何获取客户端的真实网络地址(Real IP Address)

系统环境

CentOS 7.0,三台机器:172.31.255.112(Clint);172.31.255.13(HAProxy);172.31.255.125(Nginx-00);

Nginx 1.14.0

HAProxy 1.5.18,在TCP模式下的负载均衡。当然也可以使用HTTP模式,但是效率低啊,我们要用TCP模式。(“使用HTTP效率低”完全是基于“TCP是四层的,而HTTP是七层的”进行的推断)

解决方案

官方已经给出解决方案,参考 Accepting the PROXY Protocol 页面。下面会简单的解释该过程,并标记出需要注意的地方。

在HAProxy的TCP模式下,如果要向Nginx发送客户端的IP地址,它们之间要使用「PROXY协议」。使用PROXY协议后,NGINX才可以从HTTP,SSL,HTTP/2,SPDY,WebSocket,TCP中得知原始IP地址。可以这么理解PROXY协议:HAProxy修改了TCP报文的信息,追加了客户端的IP地址等信息,然后在转发到Nginx。使用PROXY协议没有创建新的连接。

注意,这里讨论的是HTTP协议。对于HTTPS协议,在配置上会有少许的不同,后面会有所提及,而官方文章「Accepting the PROXY Protocol」中也有说明。

下面先介绍HAProxy的注意事项,然后再介绍Nginx的注意事项。

修改HAProxy配置

为了演示问题,这里的HAProxy简单配置。

因为要使用PROXY协议,需要在server中追加send-proxy配置:

listen nginx
        mode tcp
        bind *:80
	balance roundrobin
	server nginx-01 172.31.255.125:80 send-proxy weight 1 maxconn 10000 check inter 10s

注意,「PROXY协议」由两个版本,对应的HAProxy中的配置为send-proxysend-proxy-v2指令,这是用于HTTP协议的。如果是HTTPS协议要使用send-proxy-v2-ssl或者send-proxy-v2-ssl-cn指令,具体的含义参照「官方文档」中的说明。

通过「PROXY协议」传递给Nginx的信息是客户端IP地址、代理服务器IP地址、两个端口号。接下来就是配置Nginx服务接受PROXY协议。

修改Nginx配置

目前,HAProxy已经将客户端的IP地址通过PROXY协议发送给Nginx服务了。接下来就是在Nginx中获取客户端的IP地址,有两种办法:

	1. 在Nginx中,使用''$proxy_protocol_addr''和''$proxy_protocol_port''变量捕获原始客户端IP地址和端口。''$remote_addr''和''$remote_port''变量捕获HAProxy的IP地址和端口。
	2. 在Nginx中,启用RealIP模块。启用RealIP模块后:Nginx会重写''$remote_addr''和''$remote_port''变量的值,用客户端IP地址和端口替换负载均衡的IP地址和端口;而''$realip_remote_addr''和''$realip_remote_port''变量保留负载均衡的IP地址和端口;''$proxy_protocol_addr''和''$proxy_protocol_port''变量保留原始客户端IP地址和端口。

这对Nginx也有所要求:

	* 接受PROXY协议v2,需要NGINX 1.13.11及更高版本,或NGINX Plus R16及更高版本;
	* 接受HTTP的PROXY协议,需要NGINX 1.5.12及更高版本,或NGINX Plus R3及更高版本;(在nginx-1.5.12的http中,指令listen首次支持proxy_protocol)
	* 接受TCP的PROXY协议,需要NGINX 1.11.4及更高版本,或NGINX Plus R11及更高版本;(在nginx-1.11.4的stream中,指令listen首次支持proxy_protocol)
	* 对于TCP客户端PROXY协议支持,需要NGINX 1.9.3及更高版本,或NGINX Plus R7及更高版本;(在nginx-1.9.2中,加入了proxy_protocol指令,用于请求PROXY服务)
	* 如果使用RealIP模块,则需要进行安装,默认没有安装该模块。

在Nginx中进行如下配置:

http {
	#...
	server {
		set_real_ip_from 192.168.1.0/24;
		real_ip_header proxy_protocol;
		listen 80   proxy_protocol;

		#...
	}
}

有一点需要说明,指令set_real_ip_from的参数为负载均衡的IP地址或网段。该参数标记出负载均衡的网段,然后Nginx会进行递归排除,递归排除之后剩下的IP地址就是客户端的IP地址。这个地方不要配置错了。

配置测试

如果配置正确,并且使用了RealIP模块,如果后端是PHP,那$_SERVER[‘REMOTE_ADDR’]的地址为客户端的地址。

杂记

haproxy-1.5.18 + nginx-1.14.0 => 测试 HTTP 通过
haproxy-1.5.18 + tengine-2.1.2(nginx 1.6.2) => 测试 HTTP 通过

参考文献

forward client’s IP address to Nginx from Haproxy in tcp mode
Using Proxy Protocol with Nginx
Haproxy documentation
Send PROXY protocol header from HAProxy
How to setup HAProxy as Load Balancer for Nginx on CentOS 7