Docker Swarm集群中Nginx获取客户端真实IP的解决方案咨询
我来帮你搞定这个问题——这个坑我之前部署Swarm栈的时候也踩过,根源是Docker Swarm的Ingress网络默认会做SNAT(源地址转换),导致后端拿到的是Swarm内部节点的IP,而不是客户端的真实外部IP。咱们从Swarm端口配置和Nginx配置两方面入手解决:
1. 修改docker-compose.yml的端口发布规则
首先,你需要调整负责接收外部请求的服务(如果是Nginx当入口,就改Nginx的配置;如果是PHP直接对外,就改PHP服务)的端口发布方式,关闭SNAT,让Swarm直接把客户端IP透传给后端。
比如如果Nginx是你的入口服务,修改后的docker-compose.yml里的Nginx服务配置应该是这样:
version: '3' services: nginx: image: nginx:alpine ports: - target: 80 published: 80 protocol: tcp mode: host # 关键:用host模式绑定端口,避免Swarm做SNAT deploy: mode: global # 因为host模式下每个节点只能跑一个实例,用global让每个节点都部署 placement: constraints: - node.role == worker # 可选,指定只部署到worker节点 php: # 你的PHP服务配置...
注意:用
mode: host的话,每个节点上只能运行该服务的一个实例,因为端口会直接绑定到节点的物理网卡,所以deploy模式建议用global,或者用replicated并设置max_replicas_per_node: 1。
2. 配置Nginx把真实IP传递给PHP
即使Swarm透传了IP,Nginx还需要正确配置才能把真实IP传给PHP。在你的Nginx配置文件(比如default.conf)的server块里添加这些内容:
server { # 你的其他Nginx配置,比如root、location等... # 启用真实IP解析,处理Swarm代理层的IP转换 real_ip_header X-Forwarded-For; real_ip_recursive on; # 信任Swarm内部的IP段,根据你的Swarm实际网络调整 set_real_ip_from 10.255.0.0/16; # 默认Ingress网络的子网 set_real_ip_from 172.17.0.0/16; # Docker默认桥接网络,按需添加 # 把真实IP传递给PHP-FPM location ~ \.php$ { # 你的fastcgi_pass等配置... fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for; } }
解释一下:
real_ip_header告诉Nginx从X-Forwarded-For头里取真实IP,set_real_ip_from指定哪些IP是可信的代理(比如Swarm的内部节点),这样Nginx就会把这些代理发来的请求里的客户端真实IP提取出来。最后通过fastcgi_param把真实IP传给PHP,这样PHP就能通过$_SERVER['REMOTE_ADDR']或者$_SERVER['HTTP_X_FORWARDED_FOR']拿到真实的客户端IP了。
3. 验证配置是否生效
重新部署你的Swarm栈:
docker stack deploy -c docker-compose.yml your-stack-name
然后在PHP里新建一个测试文件info.php:
<?php echo "真实IP(REMOTE_ADDR):" . $_SERVER['REMOTE_ADDR'] . "<br>"; echo "X-Forwarded-For:" . $_SERVER['HTTP_X_FORWARDED_FOR'] . "<br>"; ?>
访问这个文件,就能看到客户端的真实外部IP了。
补充小提示
- 如果你的PHP服务是直接对外暴露端口(没有Nginx反向代理),那只需要把PHP服务的ports配置改成
mode: host,就能直接拿到真实IP。 - 不确定Swarm的内部IP段的话,可以用
docker network inspect ingress查看Subnet字段,把对应的网段添加到set_real_ip_from里。
内容的提问来源于stack exchange,提问作者mayconfsbrito

