当业务系统跑在容器集群里,流量从外部负载均衡进入后,后端应用往往只能拿到网关或者 Node 的 IP,真正的客户端地址在 NAT 环节被改写。这种现象让风控、访问日志和按 IP 限流变得不准确。为了解决这类问题,社区和厂商给出了两类思路:一类是在集群内部保留源地址,另一类是直接拿客户端 IP 去连后端 Pod。这两种做法在实现机制与适用面上完全不同。

源地址保留的技术原理与实现方式
集群源地址保留的核心目标是让后端 Pod 收到的数据包里,源 IP 字段依然是客户端的真实地址。最常见的做法是借助 DSR(Direct Server Return)模式,负载均衡器把请求转发给后端时不做源 NAT,仅修改目的 MAC 或目的 IP,后端响应直接从 Pod 返回给客户端,从而跳过二次 NAT 造成的地址丢失。另一种做法是使用 TOA(TCP Option Address)内核模块,它在 TCP 协议头的可选字段里塞入真实客户端 IP,应用通过特定系统调用提取,而不依赖标准 socket 源地址。
在 Kubernetes 环境中,如果使用了 kube-proxy 的 iptables 或 IPVS 模式,默认情况下外部流量经过 NodePort 或 LoadBalancer 时会做 SNAT,导致 Pod 内看到的是 Node 的 IP。要避免这一点,可以给 Service 设置 externalTrafficPolicy: Local,让 kube-proxy 只把流量转给本机上存在后端 Pod 的节点,并且不做 SNAT。不过这种方式要求负载均衡器能感知后端节点健康,否则容易把请求发到没有 Pod 的节点上被丢弃。
从运维角度看,源地址保留方案对内核版本和底层网络插件有一定要求。比如 DSR 通常需要负载均衡设备支持,TOA 则要所有后端节点加载对应内核模块。如果集群规模大、节点异构,模块统一分发和升级会带来额外成本。但它的好处是应用代码几乎不用改,依旧通过标准 getRemoteAddr 之类接口拿地址,迁移负担小。
IP 直连方案的工作机制与落地路径
IP 直连指的是客户端或者边缘代理不通过 Cluster IP、NodePort 这类集群抽象,而是直接连接后端 Pod 的真实 IP 和端口。实现上往往依赖于把 Endpoint 信息暴露给调度层,例如使用 Headless Service 配合 DNS 解析出 Pod IP,或者从 Informer 中监听 Endpoints 资源,在七层代理里维护一份直连池。这样请求从入口就绕开了 kube-proxy 的转发链,源地址自然也不会被中间层改写。
下面是一段基于 Go 客户端获取 Endpoint 并直连的简化示例,展示如何从集群内拿到 Pod IP 后发起 HTTP 调用:
package main
import (
"context"
"fmt"
"net/http"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func main() {
cfg, _ := rest.InClusterConfig()
clientset, _ := kubernetes.NewForConfig(cfg)
// 获取 headless service 对应的 endpoint 切片
eps, _ := clientset.DiscoveryV1().EndpointSlices("default").Get(context.Background(), "my-svc", metav1.GetOptions{})
for _, ep := range eps.Endpoints {
for _, addr := range ep.Addresses {
// 直接使用 Pod IP 发起请求,源地址即客户端真实 IP
resp, err := http.Get("http://" + addr + ":8080/health")
if err == nil {
fmt.Println("connect to", addr, "status", resp.StatusCode)
resp.Body.Close()
}
}
}
time.Sleep(time.Second)
}
IP 直连的优势在于路径极短,没有 SNAT 也没有额外的 iptables 规则匹配,性能损耗低,并且天然保留源 IP。但它把服务发现职责上推到了应用或边缘层,一旦 Pod 发生漂移,直连池必须秒级更新,否则会出现大量连接失败。此外,许多网络安全策略是基于 Service 粒度配置的,直连会绕过这些策略,需要在 NetworkPolicy 或者 CNI 层面补齐隔离规则。
两种方案在性能与故障域上的对比分析
从数据面性能来说,源地址保留如果采用 externalTrafficPolicy: Local 加 IPVS,转发表命中很快,但 SNAT 关闭后,如果负载均衡器把流量发到无 Pod 节点,就会出现丢包,因此故障域和负载均衡健康探测强绑定。TOA 方案由于在内核态插入选项,CPU 开销略高,但在大并发下比应用层直连更稳定,因为它仍走内核协议栈标准路径。
IP 直连把故障域缩小到单个 Pod,某个 Pod 挂掉只影响指向它的连接,不会波及整个 Node。可是它要求调用方维护最新的 Endpoint 状态,在大规模集群里 Endpoint 变更频繁,Watch 机制若处理不当会产生惊群效应。我们可以用一张简表概括差异:
| 维度 | 源地址保留 | IP 直连 |
|---|---|---|
| 源 IP 获取 | 内核/负载均衡透传 | 天然直连无改写 |
| 改造量 | 应用无感,集群配置改动 | 应用或代理需接服务发现 |
| 故障扩散 | 依赖 LB 健康检测 | 仅影响单 Pod 连接 |
| 策略兼容 | 兼容 NetworkPolicy | 易绕过 Service 级策略 |
实际选型时,如果业务强烈需要真实 IP 且不想改代码,比如传统 Java 应用容器化,源地址保留更合适;如果是自建网关或者 Service Mesh 控制面,本身就在做服务发现,那么 IP 直连能顺带解决地址与性能两个问题。理解这两类方案背后的数据面差异,才能避免在集群网络规划中踩坑。
source_ip_preservationIP_direct_connectioncluster_network修改时间:2026-08-15 06:24:30