V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kenshin912
V2EX  ›  Docker

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

  •  
  •   kenshin912 · 2021-01-13 13:24:08 +08:00 · 2994 次点击
    这是一个创建于 1416 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    21 条回复    2021-01-14 13:42:02 +08:00
    oott123
        1
    oott123  
       2021-01-13 13:26:34 +08:00
    你说的“docker 网关”是什么东西?
    kenshin912
        2
    kenshin912  
    OP
       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
        3
    SingeeKing  
       2021-01-13 13:30:23 +08:00
    如果服务很简单可以直接 --net=host

    如果不太方便,那么几乎没有办法;可以考虑在外层负载均衡 /CDN 将用户的 IP 写入 Headers 然后 openresty 配置信任这个自己写的 Headers
    kenshin912
        4
    kenshin912  
    OP
       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
        5
    SingeeKing  
       2021-01-13 13:42:44 +08:00
    @kenshin912 #4 你这种情形就不适合配置 network host 了…… 只能是前置追加了
    kenshin912
        6
    kenshin912  
    OP
       2021-01-13 13:57:21 +08:00
    @SingeeKing #5 好吧,谢谢大佬,研究一下 traefik 的用法去...
    shynome
        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
    shynome
        8
    shynome  
       2021-01-13 15:32:20 +08:00 via Android
    deploy: &deploy
    replicas: 1
    #可能要加上下面这个才行
    endpoint_mode: dnsrr
    also24
        9
    also24  
       2021-01-13 15:36:54 +08:00
    简单点直接套娃吧,外面套个 proxy_protocol

    https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/
    Symo
        10
    Symo  
       2021-01-13 15:38:33 +08:00
    X-Real-IP 还是个比较常用的办法, 阿里云自己的 SLB 就是用这个 header 来识别 client ip 的, 不然 vps 拿到的全是 slb 的内网 ip
    kenshin912
        11
    kenshin912  
    OP
       2021-01-13 16:17:37 +08:00 via iPhone
    @shynome 好的,谢谢大佬我去试试看
    kenshin912
        12
    kenshin912  
    OP
       2021-01-13 16:18:56 +08:00 via iPhone
    @shynome 我没有启动 Swarm,直接 docker compose up -d 启动的,也需要设定副本数量嘛
    shynome
        13
    shynome  
       2021-01-13 16:46:56 +08:00 via Android
    @kenshin912 那不用设定副本数量
    kenshin912
        14
    kenshin912  
    OP
       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
        15
    shynome  
       2021-01-13 20:07:50 +08:00   ❤️ 1
    @kenshin912 干, docker-compose 起的都是可以直接拿到客户端的地址的, openresty 这里就可以拿到客户端的地址, 转发到 php 这里的话就只能拿到 openresty 的地址, 如果用的是 docker swarm 才会拿不到客户端的地址, 所以你到底用的是啥? 前面还有层代理?
    kenshin912
        16
    kenshin912  
    OP
       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
        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 看看。
    kenshin912
        18
    kenshin912  
    OP
       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
        19
    privil  
       2021-01-13 23:26:20 +08:00   ❤️ 1
    基于 iptables,会转发 nat 一次的,所以你拿到的地址都是 docker gateway 的
    kenshin912
        20
    kenshin912  
    OP
       2021-01-13 23:52:17 +08:00
    @privil 我关闭转发后 openresty 的 log 显示拿到了正确的 Client IP,但是访问站点却抛出了 502,看 error_log 显示 no route to host 172.19.0.2 之类的错误
    但是还是无法解决我的问题,毕竟站点挂了。
    kenshin912
        21
    kenshin912  
    OP
       2021-01-14 13:42:02 +08:00
    @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
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2660 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 02:02 · PVG 10:02 · LAX 18:02 · JFK 21:02
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.