WEBKT

Kubernetes Ingress 配置 Proxy Protocol 获取真实客户端 IP 完全指南

3 0 0 0

前言

在 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-ForX-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 参数来缓解。
运维老王 kubernetesingress真实IP

评论点评