Kubernetes 基础体系 · 第 27/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
kube-proxy 与服务转发:iptables、IPVS、nftables、Session 和诊断
1. 先建立正确的模型:Service 不是一个监听进程
Kubernetes Service 是一个稳定的访问抽象,通常提供一个虚拟 IP、一个端口和一组后端 Pod。以典型的 ClusterIP 为例:
客户端 Pod
|
| 访问 10.96.10.20:80
v
节点内核网络栈
|
| kube-proxy 写入的规则或虚拟服务
v
选择一个后端 Pod
|
| 可能发生 DNAT、SNAT、路由
v
Pod IP:8080
这里容易产生一个错误认识:kube-proxy 不是一个一直监听 ClusterIP:80 的普通 TCP 代理。它通常不在用户态接收每个连接,而是监听 Service、EndpointSlice 等 Kubernetes 对象的变化,然后把转发规则写入 Linux 内核的 iptables、IPVS 或 nftables。真正处理数据包的是内核网络栈。
因此需要区分两条路径:
- 控制路径:kube-proxy 从 API Server 获取 Service 和 EndpointSlice,计算规则并写入节点。
- 数据路径:数据包进入节点后,由内核中的 netfilter、IPVS、路由和 conntrack 完成转发。
kube-proxy 的故障通常首先表现为控制路径没有正确收敛;但即使规则已经存在,数据路径仍可能被 CNI、路由、MTU、防火墙、conntrack 或 Pod 本身的问题阻断。
2. Service、EndpointSlice 与 kube-proxy 的关系
2.1 Service 只描述“如何访问”,EndpointSlice 描述“转发到哪里”
一个 Service 可能类似于:
apiVersion: v1
kind: Service
metadata:
name: web
namespace: default
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
sessionAffinity: None
各字段的含义是:
port: 80:客户端访问 Service 时使用的端口。targetPort: 8080:后端 Pod 接收流量的端口。selector:Service 控制器根据标签选择 Pod。ClusterIP:Service 的虚拟地址,通常由控制面分配。sessionAffinity:是否尝试让同一个客户端 IP 持续使用同一个后端。
Service 本身不保存完整的后端地址列表。现代 Kubernetes 主要通过 EndpointSlice 表示后端:
kubectl get endpointslice \
-l kubernetes.io/service-name=web \
-o wide
典型输出会包含:
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
web-abc12 IPv4 8080 10.244.1.12,10.244.2.7 5m
实际输出格式会因 Kubernetes 版本和 kubectl 版本略有差异,但需要关注以下状态:
addresses:后端地址。conditions.ready:是否可接收正常流量。conditions.serving:终止过程中的 Pod 是否仍在提供服务。conditions.terminating:后端是否正在终止。nodeName:Endpoint 所在节点。hints:拓扑感知路由可能使用的提示。
Service 控制器负责维护 EndpointSlice,kube-proxy 再根据 EndpointSlice 生成本节点上的转发状态。也就是说:
Pod 标签变化
-> Service 控制器更新 EndpointSlice
-> kube-proxy 观察到对象变化
-> kube-proxy 重新计算节点规则
-> 新连接使用新的后端集合
这条链路中任何一环延迟或失败,都可能造成“Service 对象看起来正确,但节点实际仍转发到旧 Pod”的现象。
2.2 端口不等于监听成功
Service 中写了 targetPort: 8080,并不代表 Pod 内一定有进程监听 8080。例如:
kubectl exec deploy/web -- ss -lntp
如果容器没有监听目标端口,Service 规则仍然可能正确,数据包也可能成功 DNAT 到 Pod IP,但最终收到 connection refused 或超时。
因此应分别验证:
kubectl get svc web -o yaml
kubectl get endpointslice \
-l kubernetes.io/service-name=web \
-o yaml
kubectl exec deploy/web -- ss -lntp
这三步分别验证 Service 配置、后端集合和应用监听状态,不能用其中一项替代另外两项。
3. 一次 ClusterIP 请求究竟发生了什么
假设:
- 客户端 Pod:
10.244.1.10 - Service ClusterIP:
10.96.10.20 - Service 端口:
80 - 后端 Pod:
10.244.2.7:8080 - 客户端访问:
10.96.10.20:80
一个常见的数据路径如下:
10.244.1.10:45000
|
| 目的地址 10.96.10.20:80
v
节点 netfilter / kube-proxy 规则
|
| DNAT
v
10.244.2.7:8080
|
| 路由到后端节点或本机 Pod
v
后端进程
3.1 DNAT 改变目的地址
Destination NAT,即 DNAT,会把报文目的地址从:
10.96.10.20:80
改为:
10.244.2.7:8080
内核必须记录这个连接的 NAT 状态,否则返回包无法恢复为客户端眼中的:
源地址:10.96.10.20:80
这正是 conntrack 的作用之一。对于 TCP 连接,后续数据包通常不会每次都重新执行一次“选择后端”的逻辑;已建立连接由连接跟踪状态关联到之前选定的 NAT 映射。
因此,“修改 Service 后新连接已经使用新 Pod,但旧连接仍然进入旧 Pod”并不一定是 kube-proxy 失效,而可能是已有连接和 conntrack 状态仍然有效。
3.2 是否需要 SNAT 取决于返回路径
如果客户端和后端位于不同节点,后端 Pod 返回客户端时必须有一条可达且对称的路径。CNI 插件可能通过 Pod 路由、隧道或其他方式实现这一点。
在某些场景中,kube-proxy 会对源地址做 SNAT,将客户端地址改为节点地址。这样后端只需把响应返回给节点,节点再通过 conntrack 还原给客户端。SNAT 提高了路径成功率,但代价是后端看不到原始客户端 IP。
这解释了两个常见现象:
- 后端日志中看到的是节点 IP,而不是客户端 Pod IP。
- 禁用 SNAT 或设置
externalTrafficPolicy: Local后,源 IP 得以保留,但跨节点转发和负载均衡约束增加。
4. kube-proxy 的三种主要实现模式
kube-proxy 的 --proxy-mode 常见取值包括:
iptablesipvsnftables
另有历史上的 userspace 模式,但它已经不是现代生产部署的选择。具体默认值和可用模式取决于 Kubernetes 版本、节点内核、发行版和 kube-proxy 配置。
需要特别区分两件事:
- kube-proxy 模式:决定它如何把 Service 转发逻辑写入内核。
- iptables 后端实现:系统中的
iptables命令可能通过传统 xtables 或 nftables 兼容层实现。
因此,iptables 模式不一定意味着内核底层完全没有 nftables;反过来,nftables kube-proxy 模式也不是简单地调用 iptables 命令。
5. iptables 模式:链式规则与 conntrack
5.1 基本结构
在 iptables 模式下,kube-proxy 通常在 nat 表中建立若干自定义链。不同版本的链细节可能变化,但常见结构包含:
KUBE-SERVICES:根据 Service 的 ClusterIP、端口和协议匹配。KUBE-NODEPORTS:处理 NodePort。KUBE-SVC-*:对应某个 Service 的后端选择逻辑。KUBE-SEP-*:对应某个具体 Endpoint。KUBE-MARK-MASQ:标记需要 SNAT 的流量。KUBE-POSTROUTING:在 POSTROUTING 阶段执行伪装。
可以把一次选择抽象为:
KUBE-SERVICES
|
+-- Service A -> KUBE-SVC-A
|
+-- KUBE-SEP-1 -> DNAT Pod 1
+-- KUBE-SEP-2 -> DNAT Pod 2
+-- KUBE-SEP-3 -> DNAT Pod 3
较早版本常用一组概率规则近似实现随机选择。例如有三个后端时,逻辑上可以表示为:
第一次规则:以 1/3 概率选择 Endpoint 1
否则进入下一条
第二次规则:以 1/2 概率选择 Endpoint 2
否则选择 Endpoint 3
完整概率为:
P(Endpoint 1) = 1/3
P(Endpoint 2) = (2/3) × (1/2) = 1/3
P(Endpoint 3) = (2/3) × (1/2) = 1/3
这不是按请求数精确轮询,而是对符合条件的新连接执行概率选择。短时间内的连接数可能明显不均匀,尤其是在连接数很少、连接持续时间差异很大或部分客户端反复复用连接时。
现代 Kubernetes 版本会持续优化规则结构,不能依赖某个具体链名或规则顺序作为稳定 API。
5.2 iptables 的优点与代价
iptables 模式的优点是:
- 成熟、普遍可用。
- 依赖的内核能力广泛存在。
- 规则可以直接通过
iptables-save检查。 - 与大量 Linux 网络工具和排障经验兼容。
代价是:
- Service 和 Endpoint 数量很大时,规则数量可能增长明显。
- 规则更新可能需要较多内核规则操作。
- 排查时链路较长,容易混淆 Service 链、Endpoint 链和 SNAT 链。
- 规则中的“选择后端”不等于应用层负载均衡。
5.3 查看 iptables 规则
在节点上执行:
sudo iptables-save -t nat | less
可以筛选某个 ClusterIP:
sudo iptables-save -t nat | grep '10\.96\.10\.20'
也可以看计数器:
sudo iptables -t nat -L KUBE-SERVICES -n -v --line-numbers
输出中的 pkts 和 bytes 是规则命中计数。它们只能证明数据包经过某条规则,不能单独证明:
- 后端应用成功处理了请求;
- 返回路径正常;
- TCP 三次握手完成;
- 规则一定属于当前生效的 kube-proxy 状态。
此外,某些发行版的 iptables 命令是 nftables 兼容层。诊断时应确认后端:
iptables --version
nft --version
不要在生产节点直接执行:
iptables -F
iptables -t nat -F
这会破坏 kube-proxy、容器网络和主机防火墙的现有状态,可能造成整台节点的大范围中断。
6. IPVS 模式:虚拟服务、连接调度与现实边界
6.1 IPVS 如何表示一个 Service
IPVS,即 IP Virtual Server,是 Linux 内核中的四层负载均衡机制。kube-proxy 在 IPVS 模式下,会为 Service 创建一个虚拟服务,并为其挂载多个真实服务器。
抽象表示为:
虚拟服务:10.96.10.20:80/TCP
|
+-- 10.244.2.7:8080
+-- 10.244.3.9:8080
+-- 10.244.4.5:8080
IPVS 支持多种调度算法,例如:
rr:轮询。lc:最少连接。dh:目标地址哈希。sh:源地址哈希。
但需要注意:IPVS 选择的是连接或流的后端,而不是 HTTP 请求。一个 HTTP/1.1 keep-alive 连接上的多个请求通常都会继续到同一个 Pod;一个 HTTP/2 连接也不会因为每个请求不同而重新经过 Service 层调度。
6.2 IPVS 的检查方式
sudo ipvsadm -Ln
典型结果类似:
TCP 10.96.10.20:80 rr
-> 10.244.2.7:8080 Masq 3 12
-> 10.244.3.9:8080 Masq 2 8
字段通常表示:
- 虚拟地址和端口。
- 调度算法。
- 真实服务器地址和端口。
- 转发方式。
- 当前连接等计数。
实际列名和计数含义由 ipvsadm 版本决定。这里的 3、12 不能直接解释为请求数;它们通常与连接或报文统计相关。
IPVS 状态也可能通过 /proc 观察:
sudo cat /proc/net/ip_vs
sudo cat /proc/net/ip_vs_conn
6.3 IPVS 不会自动解决所有问题
IPVS 只负责其参与的四层转发逻辑,以下部分仍可能由 iptables、路由和 conntrack 完成:
- NodePort 入口规则。
- SNAT 和 Masquerade。
- 某些防火墙和流量标记逻辑。
- 本地或外部流量的特殊处理。
- 容器网络到 Pod 网络的路由。
因此,看到 ipvsadm -Ln 中存在虚拟服务,并不能证明请求一定能到达 Pod。仍需检查:
sudo ipvsadm -Ln --stats
ip route
sudo conntrack -L
还要检查节点到 Pod IP 的实际可达性,以及 Pod 是否监听目标端口。
6.4 IPVS 的版本和维护风险
IPVS 曾经被广泛用于大规模 Service 场景,但它依赖内核 IPVS 模块和额外的状态管理。Kubernetes 社区近年来更重视 nftables 模式,并在较新的版本中对 IPVS 的维护状态和弃用计划作出调整。具体弃用阶段必须以所使用 Kubernetes 版本的官方发布说明和 kube-proxy 配置文档为准,不能因为某个集群仍能启动 IPVS 就认为它是长期推荐方案。
生产环境迁移模式时,需要验证:
- 节点内核是否具备所需模块;
- CNI 与 kube-proxy 的兼容性;
- NodePort、LoadBalancer、双栈和本地流量策略;
- 现有监控是否依赖
ipvsadm; - 连接迁移期间的中断和回滚方式。
7. nftables 模式:集合、映射和更适合扩展的规则表达
7.1 nftables 与 iptables 的关系
nftables 是 Linux 新一代数据包过滤和分类框架。它提供集合、映射、原子规则更新等能力。kube-proxy 的 nftables 模式直接使用 nftables 规则表达 Service 转发逻辑,而不是通过传统 iptables 规则模型间接完成。
这与“iptables 命令使用 nftables 后端”不同:
iptables 模式
kube-proxy -> iptables 规则接口 -> 可能由 xtables-nft 兼容层实现
nftables 模式
kube-proxy -> nftables 规则模型
两者不能仅凭 nft list ruleset 是否有输出就判断为同一种模式。
7.2 nftables 的数据结构优势
Service 转发经常需要表达这样的关系:
(Service IP, port, protocol)
-> Service 后端集合或映射
nftables 的集合和映射可以更自然地承载这类关系,规则更新也可以采用更适合批量变更的方式。与大量线性 iptables 规则相比,在 Service 和 Endpoint 数量较多的节点上,nftables 模式通常具有更好的扩展潜力。
但“扩展潜力更好”不是任何工作负载下的固定性能保证。实际结果仍受到以下因素影响:
- 内核版本;
- nftables 实现;
- CNI 数据路径;
- Service 和 Endpoint 数量;
- 连接建立速率;
- conntrack 表压力;
- 是否启用双栈;
- 节点 CPU 和规则同步频率。
7.3 检查 nftables 模式
sudo nft list ruleset
kube-proxy 的规则通常位于带有 Kubernetes 命名特征的 table 或 chain 中,但具体名称不能作为跨版本 API。更可靠的判断方式是检查 kube-proxy 配置:
kubectl -n kube-system get configmap kube-proxy -o yaml
如果集群使用 KubeProxyConfiguration,重点查看:
mode: nftables
也可以查看 kube-proxy 日志:
kubectl -n kube-system logs ds/kube-proxy --tail=200
不同发行版可能把 kube-proxy 配置放在本地文件或启动参数中,不能假设所有集群都使用同一个 ConfigMap。
7.4 nftables 模式的版本边界
nftables kube-proxy 模式是较新的能力,支持级别会随 Kubernetes 版本变化。某个版本可能处于 alpha、beta 或 stable,默认模式也可能不同。部署前必须同时核对:
- Kubernetes 版本对应的官方 kube-proxy 文档;
- 特性门控状态;
- 节点内核版本;
- 集群安装器是否覆盖了 kube-proxy 配置;
- 云厂商托管控制面是否允许自定义 kube-proxy 模式。
不能把较新版本文档中的 nftables 配置直接复制到旧集群,也不能把某个发行版的默认值当作 Kubernetes API 的规范保证。
8. 三种模式的共同边界:调度的是连接,不是业务请求
无论使用 iptables、IPVS 还是 nftables,kube-proxy 通常工作在 L3/L4 层。它能识别的主要是:
- 源和目的 IP;
- TCP、UDP、SCTP 等协议;
- 端口;
- 连接跟踪状态。
它不知道以下应用层信息:
- HTTP URL;
- HTTP Host;
- 用户身份;
- 请求大小;
- 请求是否幂等;
- 一个连接中每个请求的业务负载。
因此下面这个算例很重要。
假设 Service 有两个后端:
Pod A:10.244.2.7:8080
Pod B:10.244.3.9:8080
客户端建立一条 TCP 连接:
10.244.1.10:45000 -> 10.96.10.20:80
第一次选择得到 Pod A。之后该连接上的 100 个 HTTP 请求通常都进入 Pod A,而不会变成 50 个请求到 A、50 个请求到 B。
如果希望按 HTTP 请求做负载均衡,应使用 Ingress、Gateway、服务网格或应用层代理等组件。Service 本身不提供 L7 请求级调度。
9. Session:SessionAffinity 的定义、算法边界与反例
9.1 ClientIP 是什么
Service 的 sessionAffinity 有:
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
含义是:对于同一个源 IP,尽量在指定时间内继续使用同一个后端 Endpoint。
默认通常是:
sessionAffinity: None
ClientIP 并不等价于:
- 按用户登录状态绑定;
- 按 Cookie 绑定;
- 按 TCP 连接绑定;
- 永久固定到某个 Pod;
- 跨 Service 共享会话;
- 在所有代理拓扑中识别真实终端 IP。
它依赖内核转发实现和客户端源地址。多个用户如果经过同一个 NAT 网关,在 Service 看来可能具有同一个源 IP,于是会被归为同一个客户端。
9.2 一个具体例子
有三个后端:
A、B、C
启用 ClientIP 后,来自:
源 IP 10.244.1.10
的连接可能被绑定到 B。只要相关会话状态仍在且未超过超时,后续新连接会尽量继续到 B。
但下面情况可能导致重新选择:
- SessionAffinity 超时。
- B 从 EndpointSlice 中移除。
- B 不再 Ready。
- kube-proxy 规则重新同步。
- conntrack 或 IPVS 会话状态丢失。
- 客户端源 IP 发生改变。
- 中间 NAT 设备改变了可见源地址。
所以它提供的是“粘性尝试”,不是强一致性会话保证。
9.3 SessionAffinity 与连接复用的叠加
如果客户端使用长连接,实际观察到的粘性可能来自两层机制:
TCP 连接保持
+ ClientIP 会话亲和
+ 后端集合未变化
即使关闭 ClientIP,已有 TCP 连接通常也不会自动迁移;即使启用 ClientIP,连接断开后也可能因为状态失效而选择新后端。
如果应用依赖登录会话,单纯启用 ClientIP 通常不够。更稳妥的设计是把状态放入共享存储,或者使用有明确语义的 Cookie/令牌策略。
10. Service 类型如何改变 kube-proxy 的路径
10.1 ClusterIP
ClusterIP 面向集群内部客户端:
Pod -> ClusterIP -> Endpoint
客户端访问的是虚拟 IP,kube-proxy 负责把它转换为后端地址。ClusterIP 通常只在集群内部有意义,外部主机不能仅凭路由就访问它。
10.2 NodePort
NodePort 在每个节点上暴露一个端口,例如:
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 30080
外部客户端可以访问:
任意节点 IP:30080
路径通常为:
外部客户端
-> 节点 IP:30080
-> kube-proxy NodePort 规则
-> Pod IP:8080
NodePort 是否能从节点本机访问、是否监听所有节点地址、是否受 --nodeport-addresses 限制,都取决于 kube-proxy 配置、内核行为和云环境。
10.3 LoadBalancer
type: LoadBalancer 主要是 Kubernetes API 对外部负载均衡器的抽象。云控制器或其他实现负责创建外部负载均衡器,kube-proxy 通常仍负责节点接收到流量后的 Service 到 Endpoint 转发。
因此:
LoadBalancer 创建成功
不等于:
节点上的 Service 转发正常
必须把外部负载均衡器、节点安全组、健康检查、NodePort 或直接 Pod 转发路径分别验证。
10.4 externalTrafficPolicy
常见配置:
spec:
externalTrafficPolicy: Local
与默认的 Cluster 相比:
Cluster:节点可以把外部流量转发到其他节点的 Endpoint,通常更容易均匀利用后端,但可能需要 SNAT。Local:节点只使用本节点上的 Endpoint。这样有机会保留外部客户端源 IP,但如果节点没有本地 Endpoint,流量可能失败或被健康检查排除。
Local 的关键不是“更快”,而是改变了可用后端集合:
Cluster:节点 N 可选择全集群 Endpoint
Local:节点 N 只能选择节点 N 上的 Endpoint
如果外部负载均衡器仍把流量发送到没有本地 Pod 的节点,而该节点没有被正确健康检查排除,就会出现连接失败或黑洞。
与之相近但不同的是:
spec:
internalTrafficPolicy: Local
它控制集群内部流量是否只选择本节点 Endpoint。不要把 internalTrafficPolicy 和 externalTrafficPolicy 混为一谈。
11. SNAT、源地址保留与 Hairpin
11.1 SNAT 的因果关系
假设客户端和后端在不同网络,后端返回路径不能直接到达客户端。kube-proxy 或 CNI 可能执行 SNAT:
原始请求:
10.244.1.10:45000 -> 10.96.10.20:80
DNAT 后:
10.244.1.10:45000 -> 10.244.2.7:8080
SNAT 后:
节点IP:45000 -> 10.244.2.7:8080
后端看到的源地址变为节点 IP。conntrack 保存这组转换关系,使响应能够还原为客户端访问的 Service 地址。
如果业务需要真实客户端 IP,应确认:
- Service 的
externalTrafficPolicy; - 云负载均衡器是否使用代理协议;
- Ingress 是否写入
X-Forwarded-For; - CNI 是否改变源地址;
- 后端日志记录的是 TCP 对端地址还是 HTTP 头。
不能仅凭“Service 使用了 Local”就断言业务一定获得真实终端 IP。
11.2 Hairpin 流量
Hairpin,也叫 hairpin NAT 或 hairpin traffic,指后端 Pod 通过 Service 访问自己,或者访问同一 Service 后又被选回同一个节点/Pod 的情况。
例如:
Pod A -> ClusterIP -> Pod A
某些网络路径中,如果源地址不做适当转换,Pod A 发出的请求到达 Pod A 后,响应可能绕过预期的 conntrack 路径,导致连接失败。kube-proxy 和 CNI 会针对常见场景处理 hairpin,但实现方式与容器运行时、桥接配置和 CNI 有关。
诊断时可以测试:
kubectl exec deploy/web -- \
curl -v --connect-timeout 3 http://web.default.svc.cluster.local/
如果 Pod 通过 Service 访问自己失败,但其他 Pod 访问成功,应检查:
- kube-proxy 的 Service 规则;
- CNI bridge 的 hairpin 模式;
- Pod 网卡和宿主机 veth;
- conntrack;
- 是否存在 NetworkPolicy 或主机防火墙阻断。
12. kube-proxy 的状态同步与并发行为
kube-proxy 并不是每收到一个数据包就向 API Server 查询后端。它通常维护本地缓存,监听对象事件,再异步同步内核状态。
因此从 Pod 删除到规则消失之间可能存在短暂窗口:
Pod 被删除
-> EndpointSlice 更新
-> kube-proxy 收到事件
-> 计算新规则
-> 写入内核
-> 新连接使用新集合
这个过程受以下因素影响:
- API Server 延迟;
- kube-proxy 事件处理;
- 规则计算耗时;
- 内核规则更新;
- EndpointSlice 数量;
- 节点 CPU 和 I/O;
- kube-proxy 与 API Server 的连接状态。
旧连接不一定会被主动迁移。即使后端已经从 EndpointSlice 删除,已有 conntrack、IPVS 连接或应用长连接仍可能继续存在,具体行为取决于协议状态和实现。
终止中的 Pod 还涉及连接排空。EndpointSlice 的 ready、serving、terminating 状态用于表达后端生命周期,但应用是否能正确处理 SIGTERM、停止接收新请求并等待旧请求完成,仍是应用自身责任。
13. 一个可运行的端到端实验
下面创建两个后端 Pod 和一个 ClusterIP Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo
spec:
replicas: 2
selector:
matchLabels:
app: echo
template:
metadata:
labels:
app: echo
spec:
containers:
- name: echo
image: hashicorp/http-echo:1.0
args:
- "-text=$(HOSTNAME)"
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: echo
spec:
selector:
app: echo
ports:
- name: http
port: 80
targetPort: 5678
type: ClusterIP
应用:
kubectl apply -f echo.yaml
kubectl rollout status deployment/echo
kubectl get pod -l app=echo -o wide
kubectl get svc echo
kubectl get endpointslice \
-l kubernetes.io/service-name=echo \
-o wide
创建临时客户端:
kubectl run curl \
--image=curlimages/curl \
--restart=Never \
-it --rm -- sh
在容器内执行:
for i in $(seq 1 10); do
curl -s http://echo.default.svc.cluster.local/
echo
done
预期可能看到两个不同的 Pod 名称,但不保证严格一半一半。原因包括:
- 每个
curl可能建立一个新连接; - kube-proxy 的选择是连接级别;
- 调度算法可能是概率或实现相关;
- 连接复用会减少重新选择次数;
- Endpoint 变化会改变可选集合。
查看 DNS 解析:
getent hosts echo.default.svc.cluster.local
通常会得到 Service 的 ClusterIP。DNS 只负责把名称解析为 Service 地址,并不负责选择后端 Pod。后端选择发生在节点上的 Service 转发路径。
清理:
kubectl delete -f echo.yaml
这个实验验证了三层关系:
DNS 名称 -> ClusterIP
ClusterIP -> Endpoint
Endpoint -> Pod 进程端口
任何一层出错,表象都可能是“访问 Service 失败”,但修复方式完全不同。
14. 系统化诊断:从对象到数据包
诊断 Service 转发时,建议沿着实际数据路径逐层缩小范围,而不是直接修改 kube-proxy 规则。
14.1 第一步:确认 Service 对象
kubectl get svc echo -o yaml
重点检查:
clusterIP是否为None;ports.port与客户端访问端口是否一致;targetPort是否为正确端口名或数字;selector是否匹配预期 Pod;internalTrafficPolicy;externalTrafficPolicy;sessionAffinity。
如果 clusterIP: None,这是 Headless Service。它通常不依赖 kube-proxy 为一个虚拟 ClusterIP 做传统转发,而是由 DNS 返回 Endpoint 地址。把 Headless Service 当作普通 ClusterIP 排查,会走错方向。
14.2 第二步:确认 EndpointSlice
kubectl get endpointslice \
-l kubernetes.io/service-name=echo \
-o yaml
检查:
- 是否存在 EndpointSlice;
- 地址是否属于预期 Pod;
- 端口是否为实际监听端口;
ready是否为true;- 是否因为 selector 不匹配而没有后端;
- 是否存在多个地址族导致 IPv4/IPv6 访问不一致。
快速查看 Pod:
kubectl get pod -l app=echo -o wide
kubectl describe pod -l app=echo
如果 EndpointSlice 为空,先不要检查 iptables。kube-proxy 没有正确后端可编程,规则缺失通常只是结果。
14.3 第三步:区分 DNS 问题与 Service 转发问题
从客户端 Pod 中执行:
nslookup echo.default.svc.cluster.local
curl -v --connect-timeout 3 http://echo.default.svc.cluster.local/
再直接访问 ClusterIP:
kubectl get svc echo -o wide
curl -v --connect-timeout 3 http://<ClusterIP>/
解释方式:
- DNS 名称失败、ClusterIP 访问成功:优先检查 CoreDNS、搜索域和 DNS Policy。
- DNS 名称成功、ClusterIP 连接超时:优先检查 kube-proxy、CNI、路由、防火墙和 conntrack。
- ClusterIP 连接被拒绝:可能已到达 Pod,但应用未监听目标端口。
- Service 访问失败、直接 Pod IP 成功:Service 规则、端口、Endpoint 或转发路径可疑。
- 直接 Pod IP 也失败:问题可能在应用、Pod 网络、NetworkPolicy 或 CNI。
直接测试 Endpoint:
kubectl get endpointslice \
-l kubernetes.io/service-name=echo \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"\n"}{end}'
然后从客户端访问:
curl -v http://10.244.2.7:5678/
不要把“Pod IP 可访问”简单推导为“Service 必然正常”。Service 还涉及虚拟地址匹配、端口转换、后端选择和可能的 SNAT。
14.4 第四步:确认 kube-proxy 模式和日志
kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide
kubectl -n kube-system logs ds/kube-proxy --tail=200
kubectl -n kube-system get configmap kube-proxy -o yaml
注意:
- kube-proxy DaemonSet 是否在故障节点运行;
- 容器是否反复重启;
- 是否无法连接 API Server;
- 是否报告内核模块、iptables、IPVS 或 nftables 错误;
- 配置中的
mode是什么; - 实际节点是否使用了不同配置。
一个常见误区是只查看控制面上的 kube-proxy 日志。Service 转发发生在客户端所在节点或 NodePort 接收流量的节点,因此应查看对应节点上的 kube-proxy。
14.5 第五步:按模式查看内核状态
iptables:
sudo iptables-save -t nat | grep -E 'KUBE|10\.96\.10\.20'
sudo iptables -t nat -L -n -v
IPVS:
sudo ipvsadm -Ln
sudo ipvsadm -Ln --stats
nftables:
sudo nft list ruleset
检查某个 ClusterIP 是否出现时,要同时核对协议和端口。下面两个 Service 即使 ClusterIP 不同,也可能因为端口错误导致完全不同的结果:
10.96.10.20:80/TCP
10.96.10.20:8080/TCP
此外,双栈环境必须分别检查 IPv4 和 IPv6。一个地址族工作而另一个失败,不能用“Service 正常”概括。
14.6 第六步:观察 conntrack 和抓包
查看连接跟踪表:
sudo conntrack -L | grep '10.96.10.20'
高连接数场景检查计数:
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
如果 nf_conntrack_count 接近 nf_conntrack_max,新连接可能出现丢包、超时或 NAT 失败。扩大表容量不能替代分析连接泄漏和异常长连接,否则只是延后故障。
抓包时应选择正确接口。Pod 流量可能经过:
- Pod veth;
- Linux bridge;
- CNI 隧道;
- 节点物理网卡;
- 云厂商虚拟网卡。
例如:
sudo tcpdump -ni any \
'host 10.96.10.20 or host 10.244.2.7'
观察顺序:
- 客户端是否发出 SYN。
- Service 虚拟 IP 的 SYN 是否出现在节点。
- 是否看到目的地址改为 Endpoint IP。
- 后端是否返回 SYN-ACK。
- 返回包是否被正确还原并到达客户端。
- 是否发生重传、RST 或 ICMP 错误。
抓包显示的地址可能受抓包位置影响。不同 netfilter hook、veth 两端和隧道接口看到的 NAT 前后地址不一样,不能把某一个接口上的现象直接当成全链路事实。
15. 常见失败表现与对应推理
15.1 Service 没有 Endpoint
表现:
kubectl get endpointslice \
-l kubernetes.io/service-name=echo
没有对象,或对象中没有地址。
优先推理:
- Service selector 写错;
- Pod 标签不匹配;
- Pod 未 Ready;
- 端口定义错误;
- EndpointSlice 控制器异常;
- 命名空间不一致。
这不是先切换 IPVS 或 nftables 就能解决的问题。
15.2 Endpoint 存在,但访问超时
可能路径:
ClusterIP
-> kube-proxy 规则
-> DNAT
-> CNI 路由
-> Pod
-> 返回路径
应依次验证:
curl http://<PodIP>:<targetPort>
curl http://<ClusterIP>:<port>
ip route
conntrack -L
tcpdump
如果 Pod IP 直连成功而 ClusterIP 超时,重点看 Service 规则和 conntrack;如果两者都失败,重点看 Pod 网络和应用。
15.3 访问被拒绝
connection refused 往往说明某个网络实体主动返回了 RST,常见原因是:
- Pod 内没有监听目标端口;
targetPort错误;- NodePort 的节点地址上没有对应监听路径;
- 主机或中间设备主动拒绝。
但不能仅凭错误字符串定位,需要结合抓包确定 RST 来源。
15.4 修改 Endpoint 后仍访问旧 Pod
可能原因:
- 客户端复用了已有 TCP 连接;
- conntrack 仍保存旧 NAT;
- IPVS 仍有连接状态;
- kube-proxy 尚未完成同步;
- 应用或代理缓存了旧地址。
可比较:
kubectl get endpointslice \
-l kubernetes.io/service-name=echo -o wide
sudo iptables-save -t nat
sudo ipvsadm -Ln
sudo conntrack -L
不要为了验证而随意清空全机 conntrack。清理连接跟踪会中断大量业务连接,必须限定范围、评估影响并准备恢复方案。
15.5 externalTrafficPolicy: Local 后部分节点失败
这是预期风险之一。假设:
节点 N1:没有后端 Pod
节点 N2:有后端 Pod
外部负载均衡器把流量发送到 N1,而 Service 使用 Local,N1 没有可用本地 Endpoint,那么 N1 不能像 Cluster 模式那样转发到 N2。
应检查:
kubectl get pod -l app=echo -o wide
kubectl get svc echo -o yaml
同时验证外部负载均衡器的健康检查是否能把 N1 排除。仅仅修改 Service 字段而不验证健康检查,可能把“源 IP 保留”换成“部分节点黑洞”。
16. 模式选择与生产取舍
16.1 iptables
适合:
- 节点规模和 Service 数量中等;
- 团队已有成熟 iptables 排障能力;
- 发行版兼容性优先;
- 不希望引入较新的 kube-proxy 模式。
风险主要是规则规模和更新成本,而不是基本语义不同。
16.2 IPVS
适合性不能只根据历史经验判断。它需要额外内核能力,且 Kubernetes 对其长期维护方向已经发生变化。新集群是否采用,应以当前 Kubernetes 版本官方状态和发行版支持矩阵为依据,而不是照搬旧教程。
16.3 nftables
在支持的 Kubernetes 版本、内核和发行版上,nftables 是值得评估的新路径,尤其是 Service 数量较大、希望使用更现代规则表达的场景。
迁移前必须进行真实流量验证,而不是只执行:
kubectl get pods
至少应测试:
- ClusterIP;
- NodePort;
- LoadBalancer;
- UDP Service;
- 双栈;
- SessionAffinity;
externalTrafficPolicy: Local;- Pod 删除和滚动更新;
- 节点重启;
- kube-proxy 重启;
- conntrack 接近上限时的行为。
性能数字不能脱离版本、内核、CNI 和工作负载直接引用。模式切换后应通过连接建立速率、规则同步耗时、CPU、丢包、P99 延迟和故障恢复时间进行基准测试。
17. 需要明确区分的规范、实现与经验
Kubernetes API 语义
以下属于 Service API 的语义范围:
ClusterIP、NodePort、LoadBalancer等类型;port与targetPort的关系;sessionAffinity: ClientIP;externalTrafficPolicy;internalTrafficPolicy;- EndpointSlice 对后端状态的表达。
但 API 语义通常不会保证某个具体 iptables 链名、IPVS 调度实现细节或 nftables 表结构。
常见实现
以下是常见实现,不应当当作永远不变的接口:
- kube-proxy 使用
KUBE-SVC-*、KUBE-SEP-*链; - IPVS 通过
ipvsadm查看虚拟服务; - kube-proxy 在 nat 表中处理 SNAT;
- 新连接根据当前 Endpoint 集合选择后端;
- 已建立连接受 conntrack 或 IPVS 状态影响。
运维建议
以下属于生产经验,而不是 Kubernetes API 保证:
- 规则规模很大时评估 nftables;
- 对 SessionAffinity 做容量和故障测试;
- 使用
Local前验证本地 Endpoint 与健康检查; - 不直接 flush 节点上的防火墙规则;
- 不用“规则存在”替代端到端验证;
- 模式切换必须有回滚方案。
18. 最小诊断路径
当一个 Service 访问失败时,可以按下面顺序执行:
# 1. Service
kubectl get svc <service> -n <namespace> -o yaml
# 2. EndpointSlice
kubectl get endpointslice -n <namespace> \
-l kubernetes.io/service-name=<service> -o wide
# 3. Pod 与应用端口
kubectl get pod -n <namespace> -l <selector>
kubectl exec -n <namespace> <pod> -- ss -lntup
# 4. 客户端内测试 DNS 和 Service
kubectl exec -n <namespace> <client-pod> -- \
getent hosts <service>.<namespace>.svc.cluster.local
kubectl exec -n <namespace> <client-pod> -- \
curl -v --connect-timeout 3 \
http://<service>.<namespace>.svc.cluster.local:<port>/
# 5. 节点侧确认 kube-proxy
kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide
kubectl -n kube-system logs <kube-proxy-pod> --tail=200
# 6. 按实际模式查看内核状态
sudo iptables-save -t nat
sudo ipvsadm -Ln
sudo nft list ruleset
# 7. 必要时检查路由、conntrack 和抓包
ip route
sudo conntrack -L
sudo tcpdump -ni any 'host <cluster-ip> or host <pod-ip>'
这条路径的核心是保持因果顺序:
对象是否正确
-> 后端是否存在
-> 应用是否监听
-> kube-proxy 是否同步
-> 内核是否有对应状态
-> 数据包是否按预期经过各接口
-> 返回路径和连接跟踪是否正常
只检查 kube-proxy 日志,无法排除 DNS、CNI、MTU、NetworkPolicy 和应用端口问题;只检查 EndpointSlice,也无法证明节点上的内核转发状态已经更新。
Service 转发的本质是:控制器对象描述意图,kube-proxy 将意图编译为节点状态,Linux 内核根据规则、路由和连接跟踪处理数据包。iptables、IPVS 和 nftables 的主要差异在于“如何表达和执行这组四层转发状态”,而不是改变 Service 的基本 API 语义。理解这一点后,Session、源地址、长连接、NodePort、LoadBalancer 和故障诊断就可以放回同一条数据路径中分析,而不必把每种现象当成彼此独立的功能。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Service:ClusterIP、NodePort、LoadBalancer、EndpointSlice 和流量
- 下一篇:Kubernetes DNS:CoreDNS、Service Discovery、Search、缓存和故障
- 延伸:Kubernetes 网络排障:DNS、Service、CNI、MTU、Conntrack 和抓包
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论