• Post author:
  • Post category:Kubernetes
  • Post comments:1评论
11

使用 Ingress 转发客户端请求到 Service 情况下,我们会发现日志中的客户端真实 IP 都是本地 IP。

# ingress-nginx-controller 日志
10.244.0.1 - - [17/Jan/2022:07:40:18 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 49 "https://www.cpweb.top/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36" 1136 0.187 [blog-nginx-php-svc-80] [] 10.244.0.172:80 49 0.187 200 2192f7b39892a7d1a2e56686b7d5e954

# 后端 nginx 日志
10.244.0.1 - - [17/Jan/2022:07:43:04 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 49 "https://www.cpweb.top/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36" "10.244.0.1"

  原因是 NodePort 类型的 Service 默认执行 SNAT。这意味着从 Nginx 的角度来看,HTTP 请求的源 IP 始终是接收请求的 Kubernetes 节点的 IP 地址。
  那么我们如何保留客户端源 IP?首先我们要保证 Ingress 获取到了客户端真实 IP,然后再去配置后端 Nginx 获取真实 IP。
  以下环境:客户端 → CDN → Ingress → Nginx
  注意,以下设置适合于客户端到 Kubernets 集群之间有代理。

1、基本概念

先来了解几个概念方便理解:

• remote_addr:记录了客户端 IP 地址,与之直连的上一个客户端的 IP 地址。
• X-Forwarded-For:是一个自定义 HTTP 扩展头部字段,记录了客户端真实 IP 地址。

  如果一个 HTTP 请求到达服务器之前,经过了2个代理 Proxy1、Proxy2,IP 分别为 IP1、IP2,用户真实 IP 为 IP0。那么按照 XFF 标准,服务端最终会收到以下信息 X-Forwarded-For: IP0, IP1,此时 remote_addr 值为 IP2。

2、Ingress 获取客户端真实IP

  默认情况下,Ingress 是没有开启 X-Forwarded-For,所以我们需要修改 Ingress Nginx Controller 的 Configmap。
  官方文档:https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers

这里涉及到两个参数:use-forwarded-headers 和 compute-full-forwarded-for。

  • use-forwarded-headers:如果为True,Nginx 将传入的 X-Forwarded- 标头传递给上游。当 Nginx 在设置这些标头的另一个 L7 代理/负载均衡器之后使用此选项。如果为 False,Nginx 会忽略传入 X-Forwarded- 的标头,并用它看到的请求信息填充它们。如果 NGINX 直接暴露在互联网上,或者它位于基于 L3/数据包的负载均衡器后面,并且不会更改数据包中的源 IP,请使用此选项。
  • compute-full-forwarded-for:将远程地址附加到 X-Forwarded-For 标头而不是替换它,它会将客户端访问所经过的代理 IP 按逗号连接的列表形式记录到X-Forwarded-For 标头中。

在 ConfigMap 的 data 块下添加以下两个参数:

apiVersion: v1
kind: ConfigMap
......
data:
  compute-full-forwarded-for: "true"
  use-forwarded-headers: "true"

此时我们来看下日志:

# ingress-nginx-controller 日志
220.202.245.71 - - [17/Jan/2022:07:54:24 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 49 "https://www.cpweb.top/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36" 1135 0.197 [blog-nginx-php-svc-80] [] 10.244.0.172:80 49 0.197 200 f9b0083b54db151a23d7b0b966636410

# 后端 nginx 日志
10.244.0.163 - - [17/Jan/2022:07:57:58 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 49 "https://www.cpweb.top/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36" "220.202.245.71, 10.244.0.1"

  此时 Ingress 已经获取到了客户端真实 IP 了,并且已经传递给后端 Nginx,但是 remote_addr 不是客户端真实 IP,为 10.244.0.163 这是 ingress-nginx-controller Pod 的 IP。
  所以我们要修改后端 Nginx 配置让其获取到客户端真实 IP。

3、Nginx 获取真实 IP

修改后端 Nginx 配置,在 HTTP 层中添加以下参数:

set_real_ip_from 10.244.0.0/16;   # 定义信任地址
real_ip_header X-Forwarded-For;   # 将 X-Forwarded-For 字段包含的 IP 替换原 $remote_addr 值 

# 将 $remote_addr 值替换为由 real_ip_header 指令定义的请求标头字段包含地址中最后一个不受信任的地址,即排除配置中定义的信任地址。
real_ip_recursive on;             

此时我们再看后端 nginx 日志:

220.202.245.71 - - [17/Jan/2022:08:14:14 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 49 "https://www.cpweb.top/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36" "220.202.245.71, 10.244.0.1"

成功获取到客户端真实IP。

4、问题

  结果看着可以,但是问题在此网站是用了 CDN 的,在真实 IP 前面还应该有一个代理 IP,但是 XFF 中却没有。将网站取消 CDN 后,发现日志中的客户端 IP 又都是本地 IP 了。
  我理解就是,CDN 添加了 XFF 标头传递给了 Ingress,但是由于 CDN 是最后直连者,所以传递的 XFF 中是并不会有它的 IP 的。请求到达 Ingress,由于做了 SNAT 转换,Ingress 发现直连者变成本地 IP,然后将本地 IP 记录到 XFF 中传递到后端 Nginx,所以 CDN IP 成功消失了。
  取消 CDN 后,客户端直连到服务器,不会带 XFF 标头,然后服务器内部又做了 SNAT 转换,所以日志中就都是本地 IP。
  所以以上设置适合于客户端到 Kubernets 集群之间有代理。

参考文章:

https://www.cnblogs.com/lizexiong/p/15204071.html
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers

11

这篇文章有一个评论

  1. Avatar photo
    f

    写的太优秀了吧,厉害厉害

发表回复

验证码: + 49 = 50