proxy protocol - 让haproxy反向代理的后端nginx拿到用户真实ip
一. 简介
之前使用awstats统计博客的访问数据统计得好好的。
后来用了haproxy在nginx之前做了反向代理,结果nginx的访问日志显示的ip都是127.0.0.1,一直想着怎么解决这个问题,最近终于解决了,这儿记录下。
如何解决的呢?答案就是:proxy protocol.
二. proxy protocol
proxy protocol是haproxy的作者Willy Tarreau于2010年开发和设计的一个Internet协议,通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取用户真实IP时非常有用。
代理协议分为v1和v2两个版本,v1人类易读,v2是二进制格式,方便程序处理。proxy protocol是比较新的协议,但目前已经有很多软件支持,如haproxy、nginx、apache、squid、mysql等等,要使用proxy protocol需要两个角色sender和receiver,sender在与receiver之间建立连接后,会先发送一个带有客户信息的tcp header,因为更改了tcp协议,需receiver也支持proxy protocol,否则不能识别tcp包头,导致无法成功建立连接。
三. haproxy配置
我们这次的场景,haproxy的角色是sender,nginx的角色是receiver.
haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
# daemon方式运行,取消该注释
# daemon
defaults
log global
mode tcp
option tcplog
option dontlognull
maxconn 2000
timeout connect 5000
timeout client 500000
timeout server 500000
frontend ssl
mode tcp
bind 0.0.0.0:443
tcp-request inspect-delay 3s
tcp-request content accept if { req.ssl_hello_type 1 }
acl tls req.ssl_hello_type 1
acl has_sni req.ssl_sni -m found
use_backend nginx if tls has_sni
backend nginx
mode tcp
server webserver 127.0.0.1:50443 send-proxy
只需要在backend nginx中的server webserver 127.0.0.1:50443后面加上send-proxy就可以了,haproxy代理访问的时候,就会先把真实ip带上.
四. nginx配置
nginx的角色是receiver.
nginx配置的修改有两点
1.server配置修改
/usr/local/nginx/conf/vhosts/wangbin.io/wangbin.io.conf
# wangbin.io
# www.wangbin.io
server {
listen 50443 ssl http2 proxy_protocol;
listen [::]:50443 ssl http2 proxy_protocol;
server_name wangbin.io;
server_name www.wangbin.io;
# ssl
ssl_certificate /vps/save/certificate/acme/*.wangbin.io_ecc/fullchain.cer;
ssl_certificate_key /vps/save/certificate/acme/*.wangbin.io_ecc/*.wangbin.io.key;
ssl_trusted_certificate /vps/save/certificate/acme/*.wangbin.io/fullchain.cer;
# ecc
ssl_certificate /vps/save/certificate/acme/*.wangbin.io_ecc/fullchain.cer;
ssl_certificate_key /vps/save/certificate/acme/*.wangbin.io_ecc/*.wangbin.io.key;
# log
access_log logs/wangbin.io/wangbin.io/access-wangbin.io.log siyou325;
error_log logs/wangbin.io/wangbin.io/error.log;
# header
add_header X-Robots-Tag "";
root /vps/hosts/www;
index index.html index.htm index.php;
location ~ \.(jpg|png|gif|js|css|swf|flv|ico)$ {
expires 12h;
}
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?$1 last ;
break;
}
}
location ~* ^/(doc|logs|app|sys)/ {
deny all;
}
location ~ .*\.(php|php5)?$ {
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
}
# 禁止访问.svn目录,防止svn信息泄漏,必加项
location ~ ^(.*)\/\.svn\/ {
deny all;
}
}
修改的地方
listen 50443 ssl http2 proxy_protocol;
listen [::]:50443 ssl http2 proxy_protocol;
listen后面加上了proxy_protocol.
需要注意的是, proxy_protocol是针对端口生效的,如果一个端口支持proxy_protocol,则该端口所有server的域名都会支持.
2. log_format的修改
之前是
log_format siyou325 '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
修改为
log_format siyou325 '$proxy_protocol_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
将$remote_addr改为了$proxy_protocol_addr,之前$remote_addr一直是127.0.0.1,现在$proxy_protocol_addr是用户的真实地址了.
五. 另外两种解决方法
其实解决这个问题有3种方法
- option forwardfor
- send-proxy
- source 0.0.0.0 usesrc clientip
option forwardfor
最简单,但是只支持7层http协议。
send-proxy
就是本文介绍的proxy protocol了,既可以工作在4层tcp协议上,也可以工作在7层http协议上。
source 0.0.0.0 usesrc clientip
这个实现最复杂,需要iptables、tproxy、ip rule、 ip route命令的知识,试了好久也没有成功,可能和我只在一台机器上部署有关,后面有时间再折腾。
六. 结尾
proxy protocol解决问题的方式真是很优雅,给力!
参考:
- https://www.cnblogs.com/smail-bao/p/6693630.html
- https://www.52os.net/articles/PROXY_protocol_pass_client_ip.html