有一台阿里云的服务器,我用 docker compose 起了一套测试环境,需要获取用户真实 IP,Google 了挺久一直没解决,所以跑 V2EX 来问问大佬们。
Web Server 是 Openresty,后端是 PHP
docker-compose 文件部分如下:
version: '3.8'
services:
openresty:
image: ${OPENRESTY_IMAGE}
container_name: openresty
build:
context: ./service/openresty
ports:
- "80:80"
- "443:443"
volumes:
- ${SOURCE_DIR}:/home/wwwroot/:rw
- ${OPENRESTY_SSL_DIR}:/etc/nginx/ssl:rw
- ${OPENRESTY_CONF_DIR}:/etc/nginx/conf.d/:rw
- ${OPENRESTY_CONF_FILE}:/usr/local/openresty/nginx/conf/nginx.conf:ro
- ${OPENRESTY_LUA_DIR}:/usr/local/openresty/nginx/lua/:rw
- ${OPENRESTY_LOG_DIR}:/home/wwwlogs/openresty/:rw
restart: always
networks:
- frontend
depends_on:
- php
php:
image: ${PHP_IMAGE}
container_name: php
build:
context: ./service/php
volumes:
- ${SOURCE_DIR}:/home/wwwroot/:rw
- ${PHP_LOG_DIR}:/var/log/php
- ${DATA_DIR}/composer:/tmp/composer
#- ${PHP_CONF_FILE}:/usr/local/etc/php/php.ini
#- ${PHP_FPM_CONF_FILE}:/usr/local/etc/php-fpm.d/www.conf
#- ${SUPERVISOR_CONF_FILE}:/etc/supervisord.conf
#- ${SUPERVISOR_CONF_DIR}:/etc/supervisor/conf.d
restart: always
networks:
- frontend
- backend
depends_on:
- mysql
遇到的情况就是,不管是 Openresty 的 log , 还是程序打印出来的 $_SERVER['REMOTE_ADDR'] 都是 docker 网关的地址,拿不到真实的用户 IP,就很尴尬...
有没有大佬能提点一下,谢谢啦
1
oott123 2021-01-13 13:26:34 +08:00
你说的“docker 网关”是什么东西?
|
2
kenshin912 OP @oott123 #1 就是 docker inspect ${Container} 看到的 Gateway
就这里了 我这边看到的 Client IP 一直是这个 Gateway 的地址 ``` "NetworkID": "e9d63ecbe9c731c56c4279be95fe1f04501ec9adb0f7b42ffd91bfc5f157664b", "EndpointID": "491a05c9061f5464ec29f17bd28feda572ab3db5723f3f078a250747a8c1fe57", "Gateway": "172.19.0.1", "IPAddress": "172.19.0.3", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:13:00:03", "DriverOpts": null ``` |
3
SingeeKing 2021-01-13 13:30:23 +08:00
如果服务很简单可以直接 --net=host
如果不太方便,那么几乎没有办法;可以考虑在外层负载均衡 /CDN 将用户的 IP 写入 Headers 然后 openresty 配置信任这个自己写的 Headers |
4
kenshin912 OP @SingeeKing #3 感谢
--net=host 请问是加在哪里呢? 我之前 Google 到的解决方案中有建议将 openresty 的网络修改为 host 模式,我在 openresty 的 ports 那里定义过但是似乎不起作用,如果定义为 networks_mode: host,则 openresty 无法启动,因为 openresty 的配置文件中定义了 fastcgi_pass php:9000 , openresty 不在 frontend 网络中,找不到 php:9000 ... 网络上还有办法就是安装 traefik,然后手动追加一个 X-Real-IP 到 headers 里面,看了下挺麻烦心态也有点崩,所以只好跑 V2EX 来问问各位大佬们有没有什么办法了 |
5
SingeeKing 2021-01-13 13:42:44 +08:00
@kenshin912 #4 你这种情形就不适合配置 network host 了…… 只能是前置追加了
|
6
kenshin912 OP @SingeeKing #5 好吧,谢谢大佬,研究一下 traefik 的用法去...
|
7
shynome 2021-01-13 15:30:37 +08:00 via Android
#ports 写法改成下面这样就可以了
ports: - { mode: host, protocol: tcp, target: 80, published: 80 } #要注意的是只能有一个这样的服务不然会端口冲突 deploy: &deploy replicas: 1 |
8
shynome 2021-01-13 15:32:20 +08:00 via Android
deploy: &deploy
replicas: 1 #可能要加上下面这个才行 endpoint_mode: dnsrr |
9
also24 2021-01-13 15:36:54 +08:00
简单点直接套娃吧,外面套个 proxy_protocol
https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/ |
10
Symo 2021-01-13 15:38:33 +08:00
X-Real-IP 还是个比较常用的办法, 阿里云自己的 SLB 就是用这个 header 来识别 client ip 的, 不然 vps 拿到的全是 slb 的内网 ip
|
11
kenshin912 OP @shynome 好的,谢谢大佬我去试试看
|
12
kenshin912 OP @shynome 我没有启动 Swarm,直接 docker compose up -d 启动的,也需要设定副本数量嘛
|
13
shynome 2021-01-13 16:46:56 +08:00 via Android
@kenshin912 那不用设定副本数量
|
14
kenshin912 OP @shynome #13 大佬,我已经修改了 docker-compose.yaml
``` build: context: ./service/openresty ports: - { mode: host, protocol: tcp, target: 80, published: 80 } - { mode: host, protocol: tcp, target: 443, published: 443 } ``` 但是没有任何效果 endpoint_mode: dnsrr 这个我添加后是无法启动的,提示: Unsupported config option for services.openresty: 'endpoint_mode' |
15
shynome 2021-01-13 20:07:50 +08:00 1
@kenshin912 干, docker-compose 起的都是可以直接拿到客户端的地址的, openresty 这里就可以拿到客户端的地址, 转发到 php 这里的话就只能拿到 openresty 的地址, 如果用的是 docker swarm 才会拿不到客户端的地址, 所以你到底用的是啥? 前面还有层代理?
|
16
kenshin912 OP @shynome #15 我真的用的是 docker compose 起的服务啊
openresty 的 log 也都只能拿到 172.19.0.1 这种 Gateway 的地址,我一直很纳闷。 之前我在 Docker Swarm 里面遇到过这种问题,不过当时没来得及解决。 现在我只是拿 docker-compose 起了个测试环境而已,还是这种问题,我也好郁闷。 系统环境是 Cent OS 8.2,docker 版本是 Docker version 19.03.13, build 4484c46d9d |
17
muzuiget 2021-01-13 22:13:25 +08:00 1
我觉得根本不是 docker 的问题喔,一般来说 $_SERVER['REMOTE_ADDR'] 就是 TCP 层面发起端的 IP 。如果不是你想要的用户 IP,那么数据传递时中间肯定有个节点再起另一个新 TCP 链接到你的 web 服务器上,就像使用代理一样。除非代理以某种方式告知,否则你搞不到原始 IP 的。
我看你用的是 openresty,也就是 nginx, 在折腾其它方案之前,先看看是不是有 X-Forwarded-For 这个 HTTP header,或者先打印出所有 X- 开头的 headers 看看。 |
18
kenshin912 OP @muzuiget #17 打印过 $_SERVER , 整个看了一圈确实是来自于 Docker 的 Gateway 。
不过刚才 15 楼提醒了我,docker-compose 起的是可以直接拿到客户端 IP 的不应该有问题。 我登录了一台阿里云 HK 的节点,观察了这台服务器上同样是 docker-compose 起的 openresty,log 里面的 IP 是正常的没有问题。 结合之前我操作 firewall-cmd --zone=public --remove-masquerade 以后,log 里面的 IP 就正常了,但是 openresty 会找不到 php:9000 来看,应该还是服务器配置或者防火墙有问题。 所以,你开始的结论应该是正确的,不是 docker 的问题。 只是我现在还是不知道到底哪里出了问题,只好再去一个个排查一下。 |
19
privil 2021-01-13 23:26:20 +08:00 1
基于 iptables,会转发 nat 一次的,所以你拿到的地址都是 docker gateway 的
|
20
kenshin912 OP @privil 我关闭转发后 openresty 的 log 显示拿到了正确的 Client IP,但是访问站点却抛出了 502,看 error_log 显示 no route to host 172.19.0.2 之类的错误
但是还是无法解决我的问题,毕竟站点挂了。 |
21
kenshin912 OP @shynome #15
@muzuiget #17 @privil #19 感谢各位的帮助,问题已经解决。 参考链接: https://stackoverflow.com/questions/47537954/how-to-make-docker-container-see-real-user-ip https://stackoverflow.com/a/61263768 首先将 openresty 所在 network 对应的网卡接口调整到 internal 区域 执行完这个命令后重启防火墙, 访问 openresty 会报 no route to host 的错误 接着执行后三条命令即可。 firewall-cmd --zone=internal --change-interface=br-e9d63ecbe9c7 --permanent firewall-cmd --reload sysctl net.bridge.bridge-nf-call-iptables=0 sysctl net.bridge.bridge-nf-call-arptables=0 sysctl net.bridge.bridge-nf-call-ip6tables=0 |