概述

通常web应用获取用户客户端的真实ip一个很常见的需求,例如将用户真实ip取到之后对用户做白名单访问限制、将用户ip记录到数据库日志中对用户的操作做审计等等...k8s中运行的应用通过Service抽象来互相查找、通信和与外部世界沟通,在k8s中是kube-proxy组件实现了Service的通信与负载均衡,流量在传递的过程中经过了源地址转换SNAT,因此在默认的情况下,常常是拿不到用户真实的ip的...

这个问题在k8s官方文档(https://kubernetes.io/zh/docs/tutorials/services/source-ip/)中基于Cluster IPNodePortLoadBalancer三种不同的Service类型进行了一定的说明.

简单来说,就是原先 k8s 能在一个 Pod 的情况在多个 Node 进行访问,因为其做了转发做了 SNAT将源 IP

  • 客户端发送数据包到 node2:nodePort

  • node2 使用它自己的 IP 地址替换数据包的源 IP 地址(SNAT)

  • node2 将数据包上的目标 IP 替换为 Pod IP

  • 数据包被路由到 node1,然后到端点

  • Pod 的回复被路由回 node2

  • Pod 的回复被发送回给客户端

tutor-service-nodePort-fig01.svg

当然 Kubernetes 也提供了一个特性保留原始客户端 IP 但是这样就无法在多个 Node 中进行访问.

tutor-service-nodePort-fig02.svg

还有一种方案,就是在 LoadBalancer 添加新一个新的消息头.例如 X-Forwarded-For.但是 Ingress 会将真实 IP 放入至 x-original-forwarded-for前提就是需要前置服务将源 IP 放置 X-Forwarded-For.

环境介绍

组件名

型号或版本

硬件负载设备

F5

k8s 集群

KubeSphere v3.5.0

k8s Nginx Ingress

nginx-ingress-controller:v1.3.1

image-20240905152823469.png

相关说明

真实生产场景下,一般提供给用户的都是七层https服务

首先域名解析在外部负载设备绑定的公网ip上,负载周边可能还会有一些安全设备例如WAF等,这里不多介绍

流量经过负载后进入到k8s集群中,其中Ingress ControllerDaemonSet方式部署并使用hostNetwork模式接收并处理到达宿主机的80443端口流量

关于https证书的配置,一般有以下两种可选方式:

  • 配置在负载设备(负载类型如果只考虑七层负载),由负载负责将数据包封包解包,并转发到后端,如果用户通过https形式访问,流量经过的流程是:用户端->负载80端口->负载443端口->服务端(k8s node)的80端口

  • 配置在后端,例如Ingress资源上,如果用户通过https形式访问,流量经过的流程是:用户端->负载80端口->服务端(k8s node)的80端口->服务端(k8s node)的443端口

但是为了获取用户的真实ip,只能选择方式一,因为如果证书配置在后端服务,流量经过负载时是加密的,负载一般在没有证书的情况下,是无法对数据包进行解包操作透传用户ip的.

image-20240905153755233.png

image-20240905153605609.png

image-20240905153617584.png

第一种情况 我们可以在 F5 开启 XFF 将 x-original-forwarded-for 放入 HTTP 协议

第二种情况 我们在 Ingress 进行 Https 的校验,这就会有一个问题 因为 F5 没有证书就无法对包进行篡改添加 XFF 所以我们获取不到真实的内容

第三钟情况 我们将 HTTPS 的解析放置在 F5 中这样 F5 就可以进行拆包,给包中的内容添加一个 XFF这样就可以获取到真实的 IP.

环境准备

  1. 一个能获取到当前 IP 或者消息头的软件 这里推荐 whoami https://hub.docker.com/r/containous/whoami

  2. 一个k8s

  3. 负载均衡器

F5 配置

应为我不是一个网络管理员所以这块内容不会.我让同事帮我开启了 F5 的 XFF 的透传

IngressController

Nginx 配置资料

吐槽下 我们购买了青云商业版本,提交了工单,青云工程师先直接扔给我了一个文档... 我会我就不提工单了兄弟...

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/

data:
  use-forwarded-headers: "true"
  compute-full-forwarded-for: "true"
  forwarded-for-header: "X-Forwarded-For"
  • use-forwarded-headers

    如果为true,会将传入的X-Forwarded-*头传递给upstreams

    如果为false,会忽略传入的X-Forwarded-*头,用看到的请求信息填充它们。如果直接暴露在互联网上,或者它在基于L3/packet-based load balancer后面,并且不改变数据包中的源IP时使用此选项

  • forwarded-for-header

    设置标头字段以标识客户端的原始IP地址。 默认: X-Forwarded-For

  • compute-full-forwarded-for

    将远程地址附加到 X-Forwarded-For标头,而不是替换它。 启用此选项后,upstreams应用程序将根据其自己的受信任代理列表提取客户端IP

未配置之前

Hostname: whoami-v1-778694b4f-jfz4h
IP: 127.0.0.1
IP: ::1
IP: 10.240.129.149
IP: fe80::9cfb:1aff:fe47:680a
RemoteAddr: 10.240.10.48:55950
GET / HTTP/1.1
Host: w.eternalcloud.cn
User-Agent: curl/8.7.1
Accept: */*
X-Forwarded-For: 10.240.156.0
X-Forwarded-Host: w.eternalcloud.cn
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Scheme: http
X-Real-Ip: 10.3.35.53
X-Request-Id: 5585aceba8bbf4dfc8d1c4707a9ec073
X-Scheme: http

配置完成后

Hostname: whoami-v1-778694b4f-jfz4h
IP: 127.0.0.1
IP: ::1
IP: 10.240.129.149
IP: fe80::9cfb:1aff:fe47:680a
RemoteAddr: 10.240.10.48:55950
GET / HTTP/1.1
Host: w.eternalcloud.cn
User-Agent: curl/8.7.1
Accept: */*
X-Forwarded-For: 10.3.35.53, 10.240.156.0
X-Forwarded-Host: w.eternalcloud.cn
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Scheme: http
X-Original-Forwarded-For: 10.3.35.53
X-Real-Ip: 10.3.35.53
X-Request-Id: 5585aceba8bbf4dfc8d1c4707a9ec073
X-Scheme: http

image-20240905163631466.png

参考资料

  1. k8s生产实践之获取客户端真实 IP: https://www.cnblogs.com/ssgeek/p/14783475.html

  2. Kubernetes官方使用源IP : https://kubernetes.io/zh-cn/docs/tutorials/services/source-ip/

  3. 在 Pod 中如何获取客户端的真实 IP : https://kubesphere.io/zh/blogs/how-to-get-real-ip-in-pod/