haproxy工作在前端用户和后端的Server之间,作为”中间人”,haproxy会建立两个连接,一是用户端与haproxy建立一个连接,另一个是haproxy与后端的server建立一个连接。
所有proxy类服务的程序都会有一个相同的问题,就是处于proxy后端的server上不能够看到用户源IP地址,而只能看到haproxy的IP地址。
要解决这个问题,可以使用tproxy的方法,可以参考我之前的blog文档
但是这个方法有个比较明显的缺点就是比较难以经过防火墙,另外后端的server的默认网关必须是haproxy。
haproxy作者Willy Tarreau开发了一个”Proxy protocol”用来向后端Server传递用户源IP地址。
目前Proxy protocol有v1和v2两个版本,v1偏重人类可读,v2是二进制格式,易于程序处理。
proxy protocol有两种角色:sender和receiver 。sender在与receiver之间每个新连接建立成功以后,都会先发送一个带有PROXY header信息的的包。如果receiver没有正确配置,不能识别这个包则会丢弃,连接不能成功建立。
Proxy protocol v1的格式如下:
PROXY关键字+空格+协议栈+源IP+目的IP+源端口+目的端口+\r\n
Bash
PROXY TCP4 198.51.100.22 203.0.113.7 35646 80\r\n
这样,下一跳主机通过接受到的这些信息就可以正确处理用户的连接,就像用户是直接连接到自己一样。
通过以上分析,我们看到可以利用这个proxy protocol传递用户的源IP地址。具体的部署结构如下图所示:
因为haproxy对于后端只要求路由可达即可,所以load-balancer可以与reverse-proxy处于不同地理位置。这样就大大扩展了灵活性。
具体的配置非常简单,简单说一下:
[ reverse-proxy ]上,使用”send-proxy”关键字
server srv1 192.168.10.1 check send-proxy
[Load-balancer]上,在bind部分使用”accept-proxy”关键字
bind 192.168.1.1:80 accept-proxy
在bind后边使用了accept-proxy以后,haproxy会当作proxy protocol中的源IP真实存在一样,可以将这个源IP用与ACL,内容过滤,日志,透明代理等等。
后端的服务器上配置默认网关为load-balancer
[Load-balancer]上,配置使用source 0.0.0.0 usesrc clientip
backend bk_app
[…]
目前已经有多个软件支持proxy protocol,例如: nginx,percona server
[haproxy和nginx配合的示例]:
1.在haproxy上使用send-proxy参数
server srv1 192.168.10.1 check send-proxy
2.nginx主配置文件/etc/nginx/nginx.conf中定义新的log格式
log_format elb_log ‘$proxy_protocol_addr – $remote_user [$time_local] ‘ ‘”$request” $status $body_bytes_sent “$http_referer” ‘ ‘”$http_user_agent”‘;
3.在nginx站点配置中加入以下粗体部分:
server {
}
4.nginx的access log中会记录如下:
96.251.49.3 – – [20/Mar/2014:03:50:47+0000] “GET / HTTP/1.1” 200 396 “-” “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1897.2 Safari/537.36”
其中96.251.49.3就是用户的真实IP.
set_real_ip_from 和 real_ip_header对于使用proxy protocol不是必须的,但建议这样使用。
5.对于在HTTP协议中则相对简单,只需要在haproxy中配置两个option参数。
backend http
forwardfor这个选项配置的header是X-Forwarded-For, X-Forwarded-for经过多级代理以后会有多个IP,格式是:X-Forwarded-For: client, proxy1, proxy2。我们通常只需要第一个,不过这个可是可以伪造的。
另外在实际使用中,有时候需要自己定义这个header的名字,在haproxy中操作也很简单,在配置文件中加入如下自定义header即可。
http-request set-header X-Client-IP %[src]
主要的CDN厂家,也会自己插入一个用户源IP的主机头,例如 :
网宿: Cdn-Src-Ip –> HTTP_CDN_SRC_IP
Akamai: Ture-Client-IP –> HTTP_TURE_CLIENT_IP
Cloudflare: CF-Connecting-IP –> HTTP_CF_CONNECTING_IP
根据 RFC 3875:
Meta-variables with names beginning with “HTTP_” contain values read from the client request header fields, if the protocol used is HTTP. The HTTP header field name is converted to upper case, has all occurrences of “-” replaced with “_” and has “HTTP_” prepended to give the meta-variable name.
http header中的变量都会转换成大写,”-“会被替换成下划线,并且在前边加入”HTTP_”前缀。
============================
[haproxy和percona server配合的示例]:
percona server自5.6.25-73.0这个版本以后,添加了proxy protocol支持,具体配置方法如下:
1. haproxy中配置send-proxy参数
2. percona server在 /etc/my.cnf中:
proxy_protocol_networks = haproxy_ip
bind_address=192.168.56.2 (具体的网卡接口名称,不能0.0.0.0)
为什么需要这样配置,详见参考文档7
=============================
[NetScaler和F5 BIGIP处理PROXY protocol的示例]:
NetScaler和F5内部没有内置支持proxy protocol,因此与proxy protocol配合时会有问题,可以通过如下方式去除proxy protocol相关信息。如果愿意,可以写更复杂的规则来处理。
参考文档:
原文: