bigywater.tech
Kubernetes · 74

externalTrafficPolicy:源 IP 保留与跨节点跳转

externalTrafficPolicy 到底改变了什么?为什么设成 Local 能保留客户端 IP?代价是什么?健康检查端口是怎么回事?这篇用控制面节点当作集群外的客户端,逐个看后端看到的地址、被丢弃的包和健康检查的返回码。

10.244.1.1 / 172.18.0.4
Cluster 模式下后端看到的客户端地址(不是真实客户端 172.18.0.3)
rc=28
Local 模式下访问没有本地后端的节点,curl 3 秒超时(包被 DROP)
200 / 503
healthCheckNodePort 在有本地后端和没有本地后端的节点上的 HTTP 状态

Cluster:源地址被改写

实测 · 本机执行输出
  控制面 → worker:31575(有后端):client=10.244.1.1 rc=0(用时 0.1 秒)
  控制面 → worker2:31575(没有后端,要转发到别的节点):client=172.18.0.4 rc=0(用时 0.1 秒)
  对照:控制面节点的 IP 是 172.18.0.3;后端看到的不是它——kube-proxy 对集群外来源的 NodePort 流量做了 SNAT

后端 Pod 只在 worker(172.18.0.2)上,客户端是控制面节点 172.18.0.3。默认的 externalTrafficPolicy: Cluster 下,访问 worker:31575 后端看到 client=10.244.1.1(节点上的 Pod 网关地址),访问没有后端的 worker2:31575 看到 client=172.18.0.4(worker2 自己)——请求被转到别的节点。两种情况后端看到的都不是 172.18.0.3:kube-proxy 对集群外来源的 NodePort 流量做了 SNAT,保证回包原路返回。

Local:保留源 IP,不转发

实测 · 本机执行输出
  Service s-local:externalTrafficPolicy=Local;healthCheckNodePort=None(NodePort 类型不分配,LoadBalancer 才有)
  控制面 → worker(有后端):client=172.18.0.3 rc=0(用时 0.1 秒)——后端看到的是真实的客户端 172.18.0.3
  控制面 → worker2(没有本地后端):rc=28(用时 3.1 秒)——Local 不会转发给别的节点的后端
  worker2 的 filter 表里这个 Service 的规则:['-A KUBE-EXTERNAL-SERVICES -p tcp -m addrtype --dst-type LOCAL -m tcp --dport 32459 -j DROP']

改成 Local 后,访问有后端的 worker,后端看到真实的 client=172.18.0.3;访问没有本地后端的 worker2 则 3.1 秒后超时(curl rc=28)。worker2 的 filter 表里这个 NodePort 对应 -A KUBE-EXTERNAL-SERVICES -p tcp -m addrtype --dst-type LOCAL --dport … -j DROP:包直接被丢弃。代价就是流量必须落在有后端的节点上。

健康检查端口

实测 · 本机执行输出
  Service s-lb(LoadBalancer + Local):healthCheckNodePort=31273
  curl worker:31273/ → localEndpoints=1 HTTP=200
  curl worker2:31273/ → localEndpoints=0 HTTP=503
  curl control-plane:31273/ → localEndpoints=0 HTTP=503
  删掉后端 Pod 之后 worker:31273 → localEndpoints=0 HTTP=503(没有本地后端:返回 503,负载均衡器据此把这个节点摘掉)

LoadBalancer + Local 的 Service 会分配 healthCheckNodePort(NodePort 类型不会)。访问 worker 的这个端口返回 localEndpoints=1、HTTP 200;worker2 和 control-plane 返回 localEndpoints=0、HTTP 503;删掉后端 Pod 后 worker 也变成 503。云上的负载均衡器用它判断该把流量发到哪些节点,避免打到会 DROP 的节点上。

Cluster 的多一跳

实测 · 本机执行输出
  Cluster 模式下经过 worker2(没有后端)的连接在 worker2 的 conntrack 里:
    tcp 6 119 TIME_WAIT src=172.18.0.3 dst=172.18.0.4 sport=52092 dport=31575 src=10.244.1.178 dst=172.18.0.4 sport=80 dport=4458 [ASSURED] mark=0 use=1
    tcp 6 102 TIME_WAIT src=172.18.0.3 dst=172.18.0.4 sport=36830 dport=31575 src=10.244.1.177 dst=172.18.0.4 sport=80 dport=24444 [ASSURED] mark=0 use=1
  含义:CP→worker2:31575 先在 worker2 被 DNAT 成 Pod IP:80,再 SNAT 成 worker2 的地址,跨节点送到 worker 上的 Pod;回包原路返回——请求在集群内绕了一圈

Cluster 模式经过没有后端的 worker2 时,它的 conntrack 里原方向是 src=172.18.0.3 dst=172.18.0.4 dport=31575,回复方向是 src=10.244.1.178(Pod IP)dst=172.18.0.4:入口被 DNAT 成 Pod IP,回复被 SNAT 成 worker2 的地址,请求从 worker2 跨节点到 worker 上的 Pod 再原路返回——多绕了一跳,也丢了源 IP。

分步演示

externalTrafficPolicy
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0,节点 IP 172.18.0.2/.3/.4),后端是固定在 worker 上的 nginx:1.27-alpine(返回 $remote_addr);“集群外客户端”是控制面节点,用 docker exec 在其中运行 curl;iptables-save、conntrack 在 worker2 上读取;LoadBalancer 在 kind 里没有外部 IP,这里只用它的 healthCheckNodePort;每种情形各一次
    • 没有验证:真实云负载均衡器的行为(保留源 IP 的实现、健康检查间隔);Local 模式下 Pod 在多个节点时的权重不均(按节点平均);proxy protocol;internalTrafficPolicy;MetalLB 等裸机方案;IPVS / nftables 模式下的同一行为
    京ICP备2026067502号-1京公网安备11010502063487号