如何从 Kubernetes 服务背后的 HTTP 请求中读取客户端 IP 地址?

How to read client IP addresses from HTTP requests behind Kubernetes services?

我的 Web 应用程序 运行 作为 SSL 的 nginx 反向代理后面的 Kubernetes pod。代理和我的应用程序都使用 Kubernetes 服务进行负载平衡(如所述here)。

问题是我所有的 HTTP 请求日志只显示内部集群 IP 地址,而不是实际 HTTP 客户端的地址。有没有办法让 Kubernetes 服务将此信息传递到我的应用程序服务器?

现在,没有。

服务使用 kube_proxy 将流量分配到它们的后端。 Kube-proxy 使用 iptables 将服务 IP 路由到它正在侦听的本地端口,然后打开一个到后端之一的新连接。您看到的内部 IP 是您的一个节点上 kube-proxy 运行 的 IP:port。

一个只有 iptables 的 kube-proxy 是 in the works。这将保留原始源 IP。

从 Kubernetes 1.1 开始,有一个基于 iptables 的 kube-proxy 在某些情况下修复了这个问题。默认情况下它是禁用的;有关如何启用它的说明,请参阅 this post。总之,做:

for node in $(kubectl get nodes -o name); do kubectl annotate $node net.beta.kubernetes.io/proxy-mode=iptables; done

在 Pod 到 Pod 流量的情况下,使用 iptables kube-proxy,您现在将在目标 pod 上看到真正的源 IP。

但是,如果您的服务正在从集群外部转发流量(例如 NodePort、LoadBalancer 服务),那么我们仍然需要替换 (SNAT) 源 IP。这是因为我们正在对传入流量进行 DNAT 以将其路由到服务 Pod(可能在另一个节点上),因此 DNATing 节点需要将自身插入 return 路径才能取消 DNAT响应。

你可以通过两种方式让 kube-proxy 完全脱离循环:

  1. 使用 Ingress 将您的 nginx 配置为根据源 ip 进行平衡,并将流量直接发送到您的端点 (https://github.com/kubernetes/contrib/tree/master/ingress/controllers#ingress-controllers)

  2. 部署 haproxy serviceloadbalancer(https://github.com/kubernetes/contrib/blob/master/service-loadbalancer/service_loadbalancer.go#L51) 并在服务上设置平衡注释,以便它使用 "source"。

对于非 HTTP 请求(HTTPS、gRPC 等),这计划在 Kubernetes 1.4 中得到支持。参见:https://github.com/kubernetes/features/issues/27

从 1.5 开始,如果您 运行 在 GCE(通过扩展名 GKE)或 AWS 中,您只需向您的服务添加注释即可使 HTTP 源保留工作。

...
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/external-traffic: OnlyLocal
...

它基本上直接通过节点端口公开服务,而不是提供代理——通过在每个节点上公开健康探测器,负载均衡器可以确定将流量路由到哪些节点。

在 1.7 中,此配置已正式发布,因此您可以在您的服务规范中设置 "externalTrafficPolicy": "Local"

Click here to learn more

对于 kubernetes 1.7+ 设置 service.spec.externalTrafficPolicyLocal 将解决它。 更多信息在这里:Kubernetes Docs

externalTrafficPolicy: Local

是您可以在负载均衡器类型或 NodePort 类型的 Kubernetes 服务的 yaml 中指定的设置。 (Ingress Controller 通常包含 yaml 来提供 LB 服务。)

externalTrafficPolicy: Local

做 3 件事:
1. 禁用 SNAT 以便 而不是入口控制器 pod 将源 IP 视为 Kubernetes 节点的 IP 它应该看到真正的源 IP
2。通过添加 2 条规则摆脱额外的网络跃点
- 如果流量落在没有入口的节点的节点端口上 pods 它被丢弃。
-如果流量到达具有入口 pods 的节点的节点端口,它将转发到同一节点上的 pod。
3。使用 /healthz 端点 更新 Cloud Load Balancer 的 HealthCheck,这应该使它成为可能,这样 LB 就不会转发到节点,如果它已经被删除,并且只转发到具有入口 pods 的节点。
(为清楚起见而改写:默认情况下又名 "externalTrafficPolicy: Cluster",流量在每个工作节点的节点端口之间进行负载平衡。"externalTrafficPolicy: Local" 允许流量仅发送到具有 Ingress 的节点子集Controller Pods 运行 on them. 所以如果你有一个 100 节点的集群,而不是云负载均衡器将流量发送到 97 个节点,它只会将它发送到 ~3-5 个节点运行 入口控制器 Pods)


重要提示!: AWS 不支持
"externalTrafficPolicy: Local"。
(据说它在 GCP 和 Azure 上运行良好,据说我还记得读过在 Kubernetes 1.14 的次要版本中有一个回归破坏了它+有一些 Cilium CNI 版本也被破坏了, 所以请注意默认的 externalTrafficPolicy: Cluster 是坚如磐石稳定的,如果您不需要该功能,通常应该是首选。还要注意,如果您在它前面有一个 WAF 即服务,那么您可能能够利用它来查看客户端流量的来源。)

(它会导致 kops 和 EKS 出现问题,AWS 上的其他发行版 运行 实际上可能不受影响,更多内容见下文。)

"externalTrafficPolicy: Local" 在 AWS 上不受支持是 Kubernetes 维护人员已知的一个问题,但没有很好的记录。此外,令人讨厌的是,如果您尝试它,您会很幸运,因为 it/it 看起来会起作用,这会诱使足够多的人认为它起作用。

externalTrafficPolicy:本地在 AWS 上以两种方式被破坏,两种破坏都有强制其工作的解决方法:
第一次中断 + 解决方法: /healthz 端点初始创建不稳定 + 协调循环逻辑被破坏。
在初始应用时,它将适用于某些节点而不适用于其他节点,然后永远不会更新。
https://github.com/kubernetes/kubernetes/issues/80579
^更详细地描述了问题。
https://github.com/kubernetes/kubernetes/issues/61486
^描述了一种使用 kops 钩子强制它工作的变通方法
(当您解决 /healthz 端点协调循环逻辑时,您解锁了好处 #2 和 #3 更少的跃点 + LB 仅将流量发送到工作节点的子集。但是,好处 #1 源 IP 仍然不正确.)

第二次中断 + 2 个解决方法选项:
期望的最终结果是入口 pod 看到真实客户端的 IP。
但真正发生的是入口 pod 从查看 k8s 节点的源 IP 转移到查看经典 ELB 的源 IP。

解决方法选项 1。)

切换到更像 Azure LB 的网络 LB (L4 LB)。这是以无法使用 ACM(AWS 证书管理器)在 AWS LB 终止 TLS / 为您处理 TLS 证书配置和轮换为代价的。

解决方法选项 2。)
继续使用 AWS 经典 ELB,(并且你可以继续使用 ACM),你只需要为经典 ELB 添加配置(以 LB 服务的注释形式)+ 添加配置到入口控制器。所以两者都使用代理协议或 x 转发 headers,我记得另一个 Stack Overflow post 涵盖了这一点,所以我不会在这里重复。