内容简介:好久没更新了,正好上周遇到一个获取不到客户端IP的BUG,开发环境用nginx做反代都是work的。上到生产环境就获取不到。思来想去就是生产上多了一个SLB负载均衡。但这是一个老的功能,之前也都是好的,突然就拿的不对了,非常之诡异。为了确认不是代码的问题,我们使用tcpdump在服务结点上抓包。发现报文头的X-Real-IP是一个VPC的内网地址,说明在我们的nginx中获取的
好久没更新了,正好上周遇到一个获取不到客户端IP的BUG,开发环境用nginx做反代都是work的。上到生产环境就获取不到。思来想去就是生产上多了一个SLB负载均衡。但这是一个老的功能,之前也都是好的,突然就拿的不对了,非常之诡异。
故障重现
为了确认不是代码的问题,我们使用tcpdump在服务结点上抓包。
GET /api/foo/bar HTTP/1.1 remoteip: 122.xx.xx.xx x-forwarded-for: 122.xx.xx.xx, 10.130.0.1 accept: application/json, text/plain, */* dnt: 1 user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 referer: https://app.example.com/login accept-language: zh-CN,zh;q=0.9 x-forwarded-host: app.example.com,app.example.com x-forwarded-port: 80,80 x-forwarded-proto: http,http x-request-id: d74323d45afd4609977eb233d59f9a9e x-trace-id: d74323d45afd4609977eb233d59f9a9e x-real-ip: 10.130.0.1 x-locale: zh_CN host: app.example.com Accept-Encoding: gzip Content-Length: 0 Connection: Keep-Alive
发现报文头的X-Real-IP是一个VPC的内网地址,说明在我们的nginx中获取的 $remote_addr
就是 10.130.0.1
,是SLB的地址。
nginx配置如下:
http { server { listen 80; server_name 192.168.50.88; root /usr/local/var/www/html; location /api { proxy_set_header X-Real-IP $remote_addr; #将remode_addr写入Http Header proxy_pass http://backend_hosts; } } upstream backend_hosts { server 127.0.0.1:8080; } }
配置很简单,没有使用realip模块,直接将 remote_addr
认定为客户端ip。大家知道 remote_addr
不是http头,不容易伪造。它是服务端与客户端建立socket连接时,从客户端直接获取的。但是为什么这里获取的ip却是SLB自身的IP呢?
故障分析
再仔细分析抓包内容,发现其实报文中是包含客户端原始IP的,分别在 x-forwarded-for
和 remoteip
上。这里 x-forwarded-for
的值引起了我们的注意,如果是用nginx原始的 $proxy_add_x_forwarded_for
参数,客户端IP应该会放在最后,但是这里在第一位,说明SLB对这个头做过处理。
找到devops询问是否更改过SLB的配置,发现确实做过调整。为了直接在SLB实现http到https的重定向,将原本的4层负载均衡(tcp)换成了7层负载均衡(http)。试着将SLB恢复原有配置,可以获取客户端IP。最终问题定位到SLB的配置上。
再次阅读 SLB手册 ,发现以下描述:
负载均衡提供获取客户端真实IP地址的功能,该功能默认是开启的。
四层负载均衡(TCP协议)服务可以直接在后端ECS上获取客户端的真实IP地址,无需进行额外的配置。
七层负载均衡(HTTP/HTTPS协议)服务需要对应用服务器进行配置,然后使用X-Forwarded-For的方式获取客户端的真实IP地址。
真实的客户端IP会被负载均衡放在HTTP头部的X-Forwarded-For字段,格式如下:
X-Forwarded-For: 用户真实IP, 代理服务器1-IP, 代理服务器2-IP,…
当使用此方式获取客户端真实IP时,获取的第一个地址就是客户端真实IP。
查看SLB配置页面确实也如文档所说
至于 remote_addr
获取到SLB的IP也就很容易理解了,当没有上级代理没有透传tcp连接时, remote_addr
获取的就是上一层代理的ip地址。
故障恢复
既然定位到问题了,那么需要着手解决,改回4层LB是不现实的。
阿里云其实提供了两个方案:
- 按照文档上说的,获取X-Forwarded-For的第一段IP即为客户真实IP
- 通过抓包发现SLB会添加一个remoteip的头,直接使用就行
我们偷个懒,直接用第二种,在nginx将remoteip塞到X-Real-IP上,这样不用打hotfix即可修复问题。
花絮
其实在故障恢复的过程中,本想在本地复现的。过程就是用nginx搭建一个4层负载代理到7层负载最终到服务。如下图所示
+------------------+ +----------------+ +---------------+ +----------------+ | | | | | | | | | | | | | | | | | Client +------> TCP LB +----->+ HTTP LB +----> SERVER | | | | | | | | | | | | | | | | | +------------------+ +----------------+ +---------------+ +----------------+
tcp负载的配置如下:
stream { upstream tcp_proxy { server 127.0.0.1:80; } server { listen 88; proxy_connect_timeout 1s; proxy_timeout 300s; proxy_pass tcp_proxy; } }
最终发现nginx的tcp代理有个巨大的坑,就是无法透传 remote_addr
,如果tcp代理跳过http直连服务,获取到的remote_addr就是127.0.0.1这个本机地址。
翻了翻文档,发现还真有 官方说明
简而言之,就是要买nginx-plus,里面有个 proxy_bind $remote_addr transparent;
可以实现透传功能,满满的套路。
总结
DevOps有的时候真的会影响到业务,不同环境不同配置造成难以预料的影响。虽然我们的服务都已经实现了容器化。但是对于这些PaaS组件如何统一配置并且将配置代码化,让多个环境(包括开发,测试,staging)保持一致还是挺值得研究的话题。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 客户端ip获取蹲坑启示: 不要侥幸
- Kubernets中获取客户端真实IP总结
- 服务器如何获取真实客户端 IP
- 【问题排查】tomcat无法获取客户端IP
- nginx获取客户端请求的真实IP
- 在 Kubernetes Pod 中如何获取客户端的真实 IP
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Speed Up Your Site
Andrew B. King / New Riders Press / 2003-01-14 / USD 39.99
There's a time bomb on the web: user patience. It starts ticking each time someone opens one of your pages. You only have a few seconds to get compelling content onto the screen. Fail, and you can kis......一起来看看 《Speed Up Your Site》 这本书的介绍吧!