Kubernetes Ingress 配置 Proxy Protocol 获取真实客户端 IP 完全指南
前言
在 Kubernetes 集群中,当通过 LoadBalancer 或 NodePort 类型的服务暴露 Ingress Controller 时,由于流量经过多层代理,原始客户端 IP 信息往往会丢失。本文详细介绍如何在主流 Ingress Controller 中开启和配置 proxy_protocol,确保后端应用能够获取到真实的客户端 IP 地址。
一、为什么需要 Proxy Protocol
1.1 问题背景
当外部请求进入 Kubernetes 集群时,通常会经过以下路径:
Client → Cloud LB/MetalLB → NodePort Service → Ingress Controller → Backend Pod
在这个链路中,如果使用 L4 层负载均衡或 TCP 代理,原始的 HTTP X-Forwarded-For 和 X-Real-IP header 将无法正确传递,导致后端服务记录的都是负载均衡器的 IP 地址。
1.2 Proxy Protocol 工作原理
Proxy Protocol(RFC 6928)是一种协议,在 TCP 连接开始时,在实际请求数据之前插入一个包含原始连接信息的头部。其典型格式如下:
PROXY TCP4 <源IP> <目标IP> <源端口> <目标端口>\r\n
例如:PROXY TCP4 203.0.113.50 10.244.0.100 52341 443\r\n
这样,后端服务即使运行在四层代理之后,也能通过解析这个头部获知真实的客户端信息。
二、Nginx Ingress Controller 配置 Proxy Protocol
Nginx IC 是目前最流行的开源 Ingress Controller,下面分别介绍 Deployment 和 DaemonSet 安装方式的配置方法。
2.1 修改 ConfigMap 全局启用
创建或更新 nginx-configuration ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
use-forwarded-headers: "true"
这只是基础配置,要真正支持 Proxy Protocol,还需要让 upstream 服务识别该协议。
2.2 通过注解启用(针对单个 Ingress)
对于特定服务,可以直接在Ingress资源上添加注解:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.org/proxy-connect-timeout: "30"
nginx.org/proxy-read-timeout: "20"
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
但注意,这些是 NGINX 原生注解,完整的 Proxy Protocol 支持需要配合 upstream 配置。
2.3 修改 Deployment 添加启动参数(推荐方式)
编辑 ingress-controller 的 Deployment,在 args 中添加必要参数:
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --enable-prometheus-metrics=true
# 以下两个参数确保使用外部提供的 headers
- --use-forwarded-headers=true
# 对于某些云环境,还需要指定 external-status-address
- --election-id=ingress-controller-leader
然后在 downstream 服务(如 ClusterIP Service)中添加 externalTrafficPolicy: Local 以保留源 IP。但这只对 L3 有帮助,对于纯 L4 TCP 流量的完整方案,需要在后端应用层面也支持解析 PROXY protocol。
更完整的方案是部署一个支持 PROXY protocol 的 sidecar 或者修改应用容器网络。对于 Nginx 作为 upstream 的场景,可以参考官方文档中的 preserve-data-loss 说明。
三、云厂商 LoadBalancer + Proxy Protocol 配置要点
不同的云平台对 Proxy Protocol 有不同的支持方式和配置入口,理解这些差异非常重要。
AWS ALB (Application Load Balancer)
AWS ALB 原生不支持发送 PROXY protocol,但可以通过 NLB (Network Load Balancer) 实现。在 EKS 中配合 NL B使用时,需要:
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-lb
namespace: ingress-nginx
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol "*"
spec:
type: LoadBalancer
externalTrafficPolicy: Cluster # 或 Local
selector:
app.kubernetes.io/name: ingress-nginx
ports:
- name:http
port:80
targetPort:http
- name:https
port:443
targetPort:https
此时需要在 Nginx IC 上启用对应接收逻辑。由于 Nginx 默认不解析 PROXY protocol header,需要重新编译带 --with-stream_realip_module 模块,或者使用其他支持该协议的 ingress controller 如 Traefik、Gloo等。对于生产环境,建议评估是否真的需要四层透传,还是可以接受七层方案(如 ALB + XFF header)。
Azure Azure Load Balancer
Azure Standard LB 对 Proxy Protocol 支持有限。如果必须使用,可以在 AKS 中通过 Kured 或者 Kubeadm 配置自定义的 HAProxy 作为中间层来解包 PROXY protocol,再转发给标准后端服务。实际项目中,更常见的做法是依赖七层 Header:在 Application Gateway 或 AGIC (App Gateway Ingress Controller) 环境里,直接利用 XFF header 已经能获取足够准确的客户端信息了。只有在特殊场景(如非 HTTP/S TCP 应用)才考虑 PROXY protocol。
四、使用 Gloo Enterprise / Solo.io 作为替代方案
Gloo Edge 和 Gloo Mesh 内置了完整的 PROXY protocol 支持,适合企业级场景。在 Gateway 定义中可以这样启用:
apiVersion:gateway.solo.io/v1
kind:Gateway
metadata:
name:httpwan-gateway
namespace:gloo-system
spec:
bindAddress:::8080
bindPort:8080
options:
httpRequestGatewayConfig:
mode:USE_X_FORWARDED_headers
tcpForwardingOnlyModeSettings:
enableProxyProtocol:true
listenerType:http
soloio标注:该功能为Gloo企业版特性,开源版本可能有所限制。
然后部署对应的 Upstream 并将 EnableProxyProtocol 设置为 true,Gloo 会自动处理协议解包并注入正确的 remote address 到转发上下文里,后端应用无需任何改动即可获得真实 IP。此方案的优势在于提供了统一的控制平面,无需手动编译模块或维护复杂的 sidecar 网络拓扑,并且与 servicemesh 的集成也更顺滑——Istio + Gloo 结合的场景下,数据面可以直接消费由 Gloo 解包后的 metadata,无需二次处理元数据传播链路上的信息丢失问题。对性能有极致要求的团队来说,这是值得投入的方向,因为 Gloo 基于 Envoy,性能损耗可控,且管理成本低很多。建议先从开源版测试其基本功能,再评估升级到企业版的 ROI。需要进一步了解 gloo 与 istio 在多租户网络隔离方面的具体实现细节,可以参考官方文档中关于 Traffic Management 与 North-South Gateway 的章节,里面有 step-by-step 的最佳实践说明以及故障排查流程图,特别是关于 RDS 更新延迟导致偶发 connection reset 的问题,那部分通常容易被忽视。另外,如果你的集群同时运行多个命名空间下的微服务,确保 upstream discovery 能够跨命名空间正确识别 endpoints,否则会出现明明启用了协议却仍然拿不到数据的奇怪现象。这种情况往往是因为没有为跨命名空间的 upstreams 设置适当的 RBAC 或 federation 配置。最后,关于证书管理和 mTLS,如果你在网关层启用了 TLS termination 但又想保留 clientcert 信息用于审计日志,Gloo 可以通过 PeekCliencert 选项提取 client certificate DN 并注入到访问日志字段中,这对合规要求高的金融或医疗行业很有用。监控方面,配合 Prometheus 和 Grafana 可以可视化每一个 upstream 的连接数、错误率和延迟 P99 等指标,便于快速定位瓶颈所在。
如果必须通过 L4 层且不能改动业务代码,还有一个取巧的办法:在 Pod 里加一个 network prefix 处理程序,例如使用 iptables MARK + policy routing 把入站流量导向一个本地监听的 haproxy,由 haproxy 解开 PROXY protocol 再转给真正的业务进程。这样做的代价是需要 privileged container权限,对安全策略严格的环境不太友好,但如果业务优先级更高,这确实是可落地的折衷方案。记得同时把对应的 metrics endpoint暴露出来,方便后续排查。另一个角度是从基础设施层面优化,比如确认 kube-proxy 没有因为 conntrack tracking 导致 NAT后的地址被覆盖,这在高并发短连接场景下容易出现源端口耗尽的情况,此时可以在 node级别调大 net.netfilter.nf_conntrack_tcp_timeout_time_wait 参数来缓解。