Docker compose 搭建的测试环境下,没办法拿到真实的 Client IP 吗?

2021-01-13 13:24:08 +08:00
 kenshin912

有一台阿里云的服务器,我用 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,就很尴尬...

有没有大佬能提点一下,谢谢啦

3015 次点击
所在节点    Docker
21 条回复
oott123
2021-01-13 13:26:34 +08:00
你说的“docker 网关”是什么东西?
kenshin912
2021-01-13 13:29:42 +08:00
@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
```
SingeeKing
2021-01-13 13:30:23 +08:00
如果服务很简单可以直接 --net=host

如果不太方便,那么几乎没有办法;可以考虑在外层负载均衡 /CDN 将用户的 IP 写入 Headers 然后 openresty 配置信任这个自己写的 Headers
kenshin912
2021-01-13 13:38:14 +08:00
@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 来问问各位大佬们有没有什么办法了
SingeeKing
2021-01-13 13:42:44 +08:00
@kenshin912 #4 你这种情形就不适合配置 network host 了…… 只能是前置追加了
kenshin912
2021-01-13 13:57:21 +08:00
@SingeeKing #5 好吧,谢谢大佬,研究一下 traefik 的用法去...
shynome
2021-01-13 15:30:37 +08:00
#ports 写法改成下面这样就可以了
ports:
- { mode: host, protocol: tcp, target: 80, published: 80 }
#要注意的是只能有一个这样的服务不然会端口冲突
deploy: &deploy
replicas: 1
shynome
2021-01-13 15:32:20 +08:00
deploy: &deploy
replicas: 1
#可能要加上下面这个才行
endpoint_mode: dnsrr
also24
2021-01-13 15:36:54 +08:00
简单点直接套娃吧,外面套个 proxy_protocol

https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/
Symo
2021-01-13 15:38:33 +08:00
X-Real-IP 还是个比较常用的办法, 阿里云自己的 SLB 就是用这个 header 来识别 client ip 的, 不然 vps 拿到的全是 slb 的内网 ip
kenshin912
2021-01-13 16:17:37 +08:00
@shynome 好的,谢谢大佬我去试试看
kenshin912
2021-01-13 16:18:56 +08:00
@shynome 我没有启动 Swarm,直接 docker compose up -d 启动的,也需要设定副本数量嘛
shynome
2021-01-13 16:46:56 +08:00
@kenshin912 那不用设定副本数量
kenshin912
2021-01-13 17:18:30 +08:00
@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'
shynome
2021-01-13 20:07:50 +08:00
@kenshin912 干, docker-compose 起的都是可以直接拿到客户端的地址的, openresty 这里就可以拿到客户端的地址, 转发到 php 这里的话就只能拿到 openresty 的地址, 如果用的是 docker swarm 才会拿不到客户端的地址, 所以你到底用的是啥? 前面还有层代理?
kenshin912
2021-01-13 21:19:05 +08:00
@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
muzuiget
2021-01-13 22:13:25 +08:00
我觉得根本不是 docker 的问题喔,一般来说 $_SERVER['REMOTE_ADDR'] 就是 TCP 层面发起端的 IP 。如果不是你想要的用户 IP,那么数据传递时中间肯定有个节点再起另一个新 TCP 链接到你的 web 服务器上,就像使用代理一样。除非代理以某种方式告知,否则你搞不到原始 IP 的。

我看你用的是 openresty,也就是 nginx, 在折腾其它方案之前,先看看是不是有 X-Forwarded-For 这个 HTTP header,或者先打印出所有 X- 开头的 headers 看看。
kenshin912
2021-01-13 23:02:38 +08:00
@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 的问题。
只是我现在还是不知道到底哪里出了问题,只好再去一个个排查一下。
privil
2021-01-13 23:26:20 +08:00
基于 iptables,会转发 nat 一次的,所以你拿到的地址都是 docker gateway 的
kenshin912
2021-01-13 23:52:17 +08:00
@privil 我关闭转发后 openresty 的 log 显示拿到了正确的 Client IP,但是访问站点却抛出了 502,看 error_log 显示 no route to host 172.19.0.2 之类的错误
但是还是无法解决我的问题,毕竟站点挂了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/744533

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX