之前排查一个业务 Service 偶发超时的时候,我在节点上把iptables-save导出来,光是KUBE-SVC-*和KUBE-SEP-*相关的规则就刷了好几屏。集群规模其实不大,几十个 Service,但后端 Pod 一多,iptables 模式的规则数量就呈线性膨胀。那会儿才开始认真琢磨 Service 底层的 Iptables 与 IPVS 到底差在哪。后来把 kube-proxy 切到 IPVS 模式,一条ipvsadm -Ln就能扫完所有转发关系,超时和规则同步的问题也随之消失。
这篇不打算从头讲怎么搭集群,重点是把 Service 流量进节点之后的底层路径拆开:iptables 模式做了哪些动作,IPVS 模式又是怎么换了一套打法;为什么大家总说"后端一多、规则一大就选 IPVS";以及线上排错时,怎么从规则层反向定位问题。适合正在被 Service 流量问题折磨、或者想把 kube-proxy 原理彻底搞清楚的读者。
1. 先搞明白:Service 与 kube-proxy 在流量链路里的真实角色
1.1 Service 解决的并不是"负载均衡"
很多人一提到 Service,第一反应是"负载均衡"。这个理解不算错,但容易把思路带偏。Service 最核心的使命,是给一组动态变化的 Pod 提供一个稳定的访问入口。
Pod 在 Kubernetes 里是"短命"的,滚动更新、故障重启、扩缩容都会让 Pod IP 变化。如果客户端直接记 Pod IP,那 Pod 一换地址服务就断了。Service 的 ClusterIP 是一个虚拟 IP,它不挂在任何网卡上,也不属于任何真实负载均衡器,它存在的意义就是让客户端只认这个不变的入口,至于后面 Pod IP 怎么变,那是控制面的事。
所以 Service 的底层,本质上是一个"流量重定向"问题:怎么把发往 ClusterIP:Port 的包,改写为发往某个 PodIP:Port,并且让回程流量也能原路返回。理解了这一点,再看 iptables 和 IPVS 的差异,就看的是同一件事:谁来负责这个重定向,以及怎么重定向得又快又稳。
1.2 kube-proxy 真正的身份:规则生成器,而不是代理
这是我在排查问题过程中最想纠正的一个误区。很多流量链路图画得好像有个"proxy"进程在中间转发流量,实际上 kube-proxy 并不转发数据包,它只是一个规则同步器。
kube-proxy 通过 watch 机制监听 API Server 里的 Service 和 Endpoints/EndpointSlice 变化。一旦 Service 或后端 Pod 有变化,它就把最新状态翻译成节点本地的转发规则。在 iptables 模式下,这些规则是 netfilter 的 nat 表规则;在 IPVS 模式下,这些规则是内核 IPVS 模块里的虚拟服务器和真实服务器条目。
也就是说,真正的数据包转发发生在 Linux 内核的 netfilter 框架里,kube-proxy 只负责"配置内核"。这也是为什么 kube-proxy 挂了,已生成的规则短时间内还能继续工作,直到下次规则需要变更。
1.3 userspace、iptables、IPVS:三条不同年代的实现路线
Kubernetes 早期还有一种 userspace 模式,那才是真正意义上的用户态代理:kube-proxy 监听随机端口,把流量在用户态转发给后端 Pod。这种方式实现简单,但每次收发包都要在用户态和内核态之间切换,性能很差,Kubernetes 1.14 之后已经彻底移除了。
后来出现 iptables 模式,用内核态的 netfilter 做 DNAT,性能比 userspace 高了一个量级。但 iptables 规则是链表式的,线性匹配,Service 和后端越多,规则越庞大,匹配开销越明显。
IPVS 模式则把负载均衡逻辑放进了内核的 IPVS 模块。IPVS 是 Linux 内核自带的四层负载均衡框架,用哈希表组织 virtual server 和 real server,查找复杂度接近 O(1),还天然支持多种调度算法。Kubernetes 在 1.11 之后把 IPVS 模式提升为稳定特性,目前也是大规模集群最推荐的方式。
三种模式可以简单用下面这张表对照:
| 模式 | 转发主体 | 查找方式 | 负载均衡算法 | 现状 |
|---|---|---|---|---|
| userspace | kube-proxy 用户态 | 端口映射表 | 轮询 | 已移除 |
| iptables | 内核 netfilter | 链表线性遍历 | 仅随机概率 | 稳定但规则膨胀 |
| IPVS | 内核 IPVS 模块 | 哈希表 | rr / wrr / lc / sh 等 | 大规模优先 |
2. Iptables 模式:一条 Service 流量怎么被"链式翻译"到 Pod
2.1 先认准 nat 表,以及它挂载的钩子点
iptables 模式里,Service 相关的所有关键规则都在nat表里。Linux 内核在处理网络包时,会在固定路径上调用 netfilter 的钩子函数,其中和转发关系最密切的是这样一段路径:
- 包进入节点后,先经过
PREROUTING钩子,这是 nat 表修改目标地址的最佳位置; - 然后内核做路由决策,判断这个包是发给本机进程还是需要转发;
- 如果是转发,经过
FORWARD钩子; - 出节点前还会经过
POSTROUTING钩子,这里适合做源地址伪装。
kube-proxy 做的事情,就是把自定义链挂到这些钩子上。你执行iptables -t nat -S的时候,会看到类似下面的跳转关系:
-A PREROUTING -m addrtype --dst-type LOCAL -j KUBE-SERVICES -A OUTPUT -m addrtype --dst-type LOCAL -j KUBE-SERVICES -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING注意这里有两条KUBE-SERVICES跳转,一条在PREROUTING,一条在OUTPUT。这是因为访问 ClusterIP 的流量有两类来源:外部进入节点的包走PREROUTING,而节点本机进程访问 ClusterIP 的包会直接从OUTPUT钩子出来。如果只挂一条,就会出现"外面能通、本机却不通"的诡异现象。
2.2 KUBE-SERVICE / KUBE-SVC / KUBE-SEP:三级链的接力
一条 Service 的 iptables 规则,实际上是三个层级的链在接力。我在节点上模拟了一条典型规则,拆开看就非常清晰。
第一级是KUBE-SERVICES链,它的作用是按 ClusterIP:Port 做门卫匹配:
-A KUBE-SERVICES -d 10.96.102.6/32 -p tcp --dport 80 -j KUBE-SVC-XXXXXX意思很直白:只要是发往10.96.102.6:80的包,就跳转到属于这个 Service 的KUBE-SVC-XXXXXX链。
第二级是KUBE-SVC-XXXXXX链,它实现负载均衡。iptables 本身没有传统 LB 那种调度算法,kube-proxy 用的是statistic模块,通过概率分配到不同后端。假如这个 Service 有 3 个后端,规则长这样:
-A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.3333 -j KUBE-SEP-AAAAAA -A KUBE-SVC-XXXXXX -m statistic --mode random --probability 0.5000 -j KUBE-SEP-BBBBBB -A KUBE-SVC-XXXXXX -j KUBE-SEP-CCCCCC第一条规则以 1/3 概率把包丢给第一个后端;没被选中的包继续走第二条,以 1/2 概率丢给第二个后端;前两条都没命中的包,最后必然落到第三个后端。这种"概率递减"的设计,保证了每个后端分到的流量大致相等。从数学上看,三个后端分别收到 1/3、1/3、1/3,非常巧妙,但本质上也说明一个问题:iptables 模式不支持加权调度,所有后端权重是等价的。
第三级是KUBE-SEP-XXXXXX链,它执行真正的 DNAT,把包的目的地址改成 Pod IP:
-A KUBE-SEP-AAAAAA -p tcp -m tcp -j DNAT --to-destination 172.20.1.5:8080到这里,一个发往 ClusterIP 的包,目标地址已经被改写为某个具体 Pod 的 IP 和端口。接下来内核重新做路由决策,把包转发到对应节点。
注意:如果你在 Service 上开了
sessionAffinity: ClientIP,KUBE-SVC链的实现会从上面的随机概率换成recent模块,基于源 IP 做固定后端选择。这也是 iptables 模式"会话保持"的底层来源。
2.3 DNAT 之后的隐形功臣:conntrack
DNAT 做完之后,有一个很容易被忽略的问题:回程包怎么回到客户端?
IP 协议本身是无状态的,如果只做 DNAT,Pod 收到的包源地址还是客户端地址,Pod 回包时直接以 PodIP 作为源地址发出去。客户端收到的数据明明是自己访问 VIP 得到的,源地址却变成了一个内网 Pod IP,这个连接根本对不上。
救场的是 netfilter 的 conntrack(连接跟踪)模块。当一个包经过 nat 表完成 DNAT 后,conntrack 会在连接表里记录"原始方向是 VIP 到客户端,应答方向是 Pod 到客户端"这种映射。后续同一连接的回程包,会在POSTROUTING阶段被还原目标地址,让客户端看到的依然是 ClusterIP。
这也带来了一个常见的故障点:conntrack 表是有限大小的,默认值通常在十几万到几十万条之间。如果集群里短连接特别多,或者有异常连接没释放,conntrack 表可能被打满。这时候新连接无法建表,Service 就会出现明显的随机超时。用cat /proc/sys/net/netfilter/nf_conntrack_count对比nf_conntrack_max,就能快速判断是不是这个原因。
2.4 NodePort 场景为什么离不开 MASQUERADE
ClusterIP 只能在集群内被访问,如果要从集群外访问 Service,就得靠 NodePort 或 LoadBalancer。NodePort 模式的链路里,还有一个容易被忽略的动作:MASQUERADE,也就是源地址伪装。
当外部客户端访问NodeIP:NodePort时,目标地址会先被改写为 PodIP,源地址还是客户端公网 IP。如果这个 Pod 在其他节点上,包会跨节点转发。问题出在回程:Pod 回包时,它的默认网关是所在节点的 CNI 网桥,包会直接发回给客户端。可客户端怎么知道 PodIP 是哪个网段?它根本不会路由这个包。
kube-proxy 的解决办法,是在部分场景下对包打标记,并在POSTROUTING阶段做 MASQUERADE。节点会把包源地址改成自己的 IP,回程流量先回到节点,再由节点利用 conntrack 还原成客户端地址后转发回去。代价就是客户端看到的源 IP 变成了节点 IP,完整的源 IP 透传就丢了。
如果希望保留客户端源 IP,可以把 Service 的externalTrafficPolicy设置为Local,这样流量只会被分到本节点的后端 Pod,回程直接从本节点的 Pod 出去,不需要跨节点,也就不需要做 MASQUERADE。代价是本节点没有后端 Pod 时,外部访问会直接失败。这是一个典型的"保源 IP 还是保高可用"的取舍。
3. IPVS 模式:把负载均衡逻辑搬进内核态的代价与收益
3.1 Virtual Server 与 Real Server:IPVS 眼中的 Service 和 Pod
IPVS 是 Linux 内核自带的四层负载均衡框架,它把"分发流量"这件事下沉到了内核态。在 IPVS 的视角里,一个 Service 就是一个 Virtual Server(虚拟服务器),每个后端 Pod 就是一个 Real Server(真实服务器)。
kube-proxy 在 IPVS 模式下,会通过 netlink 接口与内核 IPVS 模块通信。你创建了一个 ClusterIP 为10.96.102.6的 Service,kube-proxy 就在内核里创建一个绑定到10.96.102.6:80的 virtual server;每来一个后端 Pod,就往这个 virtual server 下添加一条 real server 条目。
用ipvsadm查看时,输出非常直观:
IP Virtual Server version 1.2.1 Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.96.102.6:80 rr -> 172.20.1.5:8080 Masq 1 0 0 -> 172.20.1.6:8080 Masq 1 0 0 -> 172.20.1.7:8080 Masq 1 0 0Forward这列显示的Masq,表示 IPVS 使用的是 NAT 模式,也就是会改写目标地址再转发。IPVS 收到一个发往 ClusterIP 的包后,通过哈希表定位到对应的 virtual server,再根据调度算法选出一个 real server,做 DNAT,然后把包交给网络栈继续转发。整个查表过程都在内核态,不会反复遍历成百上千条 iptables 规则。
IPVS 本身也维护自己的连接状态,回复包会按照连接记录原路还原目标地址,所以它不像 iptables 模式那样完全依赖 conntrack 来做 DNAT 的反向还原。但在 Kubernetes 的具体实现里,kube-proxy 依然会在边缘场景保留一部分 iptables 规则,比如KUBE-MARK-MASQ打标记和KUBE-POSTROUTING做 SNAT,来处理 NodePort、外部流量等特殊情况。
3.2 内核模块与启动校验:为什么有时候配置了 ipvs 却还是 iptables
IPVS 模式虽然好,但它依赖内核模块。切 IPVS 模式最常踩的坑,就是内核没加载ip_vs模块,导致 kube-proxy 静默回退到 iptables。
kube-proxy 在启动时会检查当前内核是否支持 IPVS,如果检测不到需要的内核模块,它会打一条日志并退回 iptables。很多人配置了--proxy-mode=ipvs,却始终看到KUBE-SVC规则在增长,就是模块没加载导致的。
在节点上执行下面几条命令,就能确认模块状态:
lsmod | grep ip_vs modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh建议在节点初始化阶段就把这些模块加进去,并写入模块加载配置,让机器重启后也能自动加载。常用的模块包括ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_lc、ip_vs_sh、ip_vs_dh等。具体的加载配置方式,取决于节点的发行版和初始化工具。
我用过一个比较省心的方式是:在安装 kube-proxy 之前,用一段 DaemonSet 或启动脚本统一执行modprobe,再确认/proc/modules里能看到ip_vs,最后才允许 kube-proxy 容器启动。这样能避免集群建好之后才发现模式不对的尴尬。
3.3 调度算法:rr、wrr、lc、sh 的真实差距
IPVS 最吸引人的地方,除了性能,还有调度算法的多样性。Kubernetes 里可以通过 kube-proxy 的--ipvs-scheduler参数指定默认调度算法,常见的几种:
rr:轮询,每个后端轮流接收请求,这是 Kubernetes 的默认选择。wrr:加权轮询,可以为不同后端设置不同的权重,适合后端规格不一致的场景。但 Kubernetes Service 层面的 Endpoint 没有暴露权重概念,所以这项能力在标准用法里发挥空间有限。lc:最少连接,把新连接分给当前活跃连接数最少的后端。听起来比轮询聪明,但如果后端处理连接速度差距大,反而可能因为"连接数少=处理快"的假设不成立,导致负载失衡。sh:源地址哈希,同一源 IP 的请求总被分到同一个后端,相当于一种底层的会话保持手段。
个人建议,如果没有特殊需求,默认rr就够了。Kubernetes 调整负载的粒度在 Service 和 ReplicaSet 层面,通常不会要求数据面去感知后端规格差异。真要做会话保持,正确的做法是设置 Service 的sessionAffinity: ClientIP,kube-proxy 会为 IPVS virtual server 配置 persistent 模板,比手工换调度算法更可控。
sessionAffinity在 IPVS 模式下,会体现为ipvsadm输出里的 flags 和 timeout。例如设置sessionAffinityConfig.clientIP.timeoutSeconds后,相同源 IP 的连接会被持续调度到同一个 real server,直到超时时间过期。这个机制在 iptables 模式下只能靠recent模块模拟,可配置性和可观测性都差不少。
3.4 IPVS 的边界:它也不是万能的
IPVS 再强,也只是一个四层转发框架,它有自己明确的边界。
第一,IPVS 不理解 HTTP、TLS、URL 这类七层信息。它只看协议、IP、端口,所以做不了基于路径的转发,也做不了 TLS 终止。这些工作得交给 Ingress Controller 或服务网格。
第二,IPVS 模式在 Kubernetes 里仍然会依赖一部分 iptables 规则。kube-proxy 需要维护KUBE-FORWARD链来保证转发链路的默认策略正确,也需要用KUBE-MARK-MASQ和KUBE-POSTROUTING处理特定场景的源地址伪装。所以 IPVS 模式不是彻底甩开 iptables,而是把最核心、最耗时的负载均衡逻辑从 iptables 链式匹配换成了哈希表。
第三,IPVS 对协议的支持受内核版本限制。比如 SCTP 支持要看内核是否满足条件,Windows 节点也不支持 IPVS。在混合操作系统集群里,i ptables 模式仍是兜底选项。
第四,IPVS 模式解决的是"数据面调度性能"问题,解决不了"控制面数据同步"的延迟、DNS 缓存、以及 CNI 网络本身的丢包。曾经见过一个集群切了 IPVS 之后依然有超时,最后定位到是 conntrack 表加nf_conntrack_dying_timeout参数的问题,和数据面调度方式无关。排查时要清醒:IPVS 不是万能药,它只负责跨节点转发的效率和调度质量。
4. 从性能开撕:全量刷新的 iptables 和增量更新的 IPVS
4.1 规则数量怎么算:后端数量导致的链膨胀
为什么 iptables 模式在规则一多之后就扛不住?要回答这个问题,得先量化一下规则数量。
一个普通的 ClusterIP Service,每增加一个后端 Pod,就要新增至少两条规则:一条在KUBE-SVC链上参与概率选择,一条在独立的KUBE-SEP链上执行 DNAT。如果集群里有 200 个 Service,平均每个 5 个后端,光这两类链上的规则就有 2000 条,再算上KUBE-SERVICES入口规则、KUBE-MARK-MASQ标记规则,几千条规则是常态。
netfilter 的 iptables 匹配是典型的线性遍历:包进入KUBE-SERVICES链后,一条一条匹配目标地址,命中了再跳到KUBE-SVC链,接着又是几条规则逐条匹配。规则越多,平均匹配次数越多,留给包转发的 CPU 开销就越大。虽然现代内核有一些规则缓存优化,但规则频繁变化时,缓存命中率会明显下降。
下面这张表可以直观展示增长趋势:
| Service 数 | 平均后端数 | 预计核心转发规则量(约) | 查找方式 |
|---|---|---|---|
| 50 | 3 | 300+ | 线性遍历 |
| 200 | 5 | 2000+ | 线性遍历 |
| 500 | 10 | 10000+ | 线性遍历 |
| 任意 | 任意 | 取决于 virtual server 数量 | 哈希查找 |
IPVS 则完全不同。每个 Service 在内核里对应一个 virtual server,IPVS 使用哈希表按"协议+目标地址+端口"直接定位 virtual server,再在 real server 集合中选择目标。这个查找过程不随全局 Service 数量线性增长,所以大规模场景下优势极其明显。
4.2 iptables-restore 的全量同步,正是大规模集群的痛点
规则多只是静态层面的问题,动态层面还有一个更隐蔽的痛点:iptables 模式的规则同步是全量刷新。
kube-proxy 每次检测到 Service 或 Endpoints 变化,都会基于当前集群状态重新生成一份完整的 iptables 规则集,然后用iptables-restore一次性写入内核。注意,这里重建的不是某一条规则,而是所有 Service 相关的全套规则。
假设集群里同时滚动更新几十个 Deployment,Endpoints 频繁增删,kube-proxy 就会陷入高频的全量规则重建。规则总量越大,单次iptables-restore耗时越长。在极端情况下,一轮同步还没完成,下一轮变化又来了,kube-proxy 只能不断追赶,表现为规则持续不稳定,偶发出现短暂空窗期。
IPVS 模式的同步逻辑不一样。kube-proxy 在同步时会做 diff,只对新增或删除的 virtual server、real server 通过 netlink 做增量更新。后端 Pod 变化时,不需要重建全部转发规则,只是往已有的 virtual server 下添加或摘除一个 real server 条目。这种增量语义在大规模集群里意义重大,尤其适合频繁发布、快速扩缩容的业务。
4.3 IPVS 模式的可维护性优势不只是性能
除了性能,IPVS 带给运维的另一个好处是"可读性"。
iptables 模式的规则只能通过iptables-save或iptables -t nat -nL KUBE-SVC-XXXXXX来查看,规则链名称是随机 hash,想看一个 Service 对应哪些后端,得在KUBE-SERVICES、KUBE-SVC、KUBE-SEP三层之间来回跳。如果规则被其他组件或 CNI 插件混入,梳理起来会更加痛苦。
IPVS 模式下的ipvsadm -Ln输出非常直观:VIP 表示 Service,VIP 下面缩进列出的就是所有后端 Pod,ActiveConn 和 InActConn 还能实时反映每个后端的连接分布。定位问题的时候,一眼就能看清"这个 Service 现在有哪些后端、连接均衡不均衡"。
当然,IPVS 模式也不是完全不需要看 iptables。前面说过,KUBE-FORWARD、KUBE-MARK-MASQ、KUBE-POSTROUTING这些链依然存在。只是它们数量固定且规则简单,不会再随 Service 数量膨胀,排查压力小得多。
4.4 生产选型建议:到底该用哪个模式
如果你的集群还很小,Service 只有几十个,iptables 模式完全够用。它部署最简单,不需要关注内核模块,排错资料也最多。这个阶段直接硬切 IPVS,反而可能因为模块加载、调度参数、旧文档里的命令习惯等问题多花不少时间。
一旦集群规模开始增长,或者你明显感觉到 Service 规则数量已经到了几千条、后端发布频繁导致规则同步压力大,就该考虑迁移 IPVS。Kubernetes 官方也明确倾向 IPVS 模式作为大规模集群的推荐方案。
还有一个常见选择是让 CNI 插件或 Kubernetes 发行版默认启用 IPVS。很多云厂商和发行版已经默认这么做。如果你自己管理集群,最稳妥的路线是:部署初期就加载好ip_vs模块,kube-proxy 直接指定--proxy-mode=ipvs,同时保留 iptables 相关的基础检查能力,这样既拿到性能红利,又不会失去故障排查手段。
5. 线上排错实战:从一条 Service 不通反推到底层
5.1 第一步:确认 kube-proxy 当前运行模式
Service 不通的时候,先别急着看应用日志,第一步要确认 kube-proxy 到底工作在什么模式。因为同样的症状,在不同模式下的排查路径完全不同。
看 kube-proxy 的启动参数是最直接的方式:
kubectl -n kube-system get pod -l component=kube-proxy -o yaml关注--proxy-mode参数。如果没显式配置,kube-proxy 默认会走 iptables 模式(在支持 IPVS 且模块加载的情况下,需要显式指定才启用 IPVS,默认仍偏向 iptables)。
再用命令验证实际状态:
ipvsadm -L -n | head如果有输出,说明节点正在用 IPVS 模式;如果提示No such file or directory或者没有任何 virtual server,大概率还是 iptables 模式。对照iptables -t nat -S | grep KUBE-SVC | wc -l,也能做个交叉验证:规则数很多而 IPVS 输出为空,就是 iptables 模式无疑。
常见误区:kube-proxy 设置了
--proxy-mode=ipvs,但实际仍在跑 iptables。这多半是内核模块没加载。按前文 3.2 节的方法确认lsmod | grep ip_vs。
5.2 手撕 iptables 规则:从 KUBE-SERVICE 一路追到 KUBE-SEP
如果确认是 iptables 模式,排错的核心动作就是"顺链摸瓜"。先找到 Service 对应的 ClusterIP:
kubectl get svc <service-name> -o jsonpath='{.spec.clusterIP}'然后查KUBE-SERVICES链,找到匹配这个 ClusterIP 的跳转规则和KUBE-SVC链名:
iptables -t nat -nL KUBE-SERVICES | grep 10.96.102.6输出了类似KUBE-SVC-XXXXXX的链名之后,再查这条链:
iptables -t nat -nL KUBE-SVC-XXXXXX这一步能看到概率规则和KUBE-SEP链。接着把这些KUBE-SEP-YYYYYY链全部列出来,确认每个链里 DNAT 的目标地址和端口是不是对应的 Pod IP 和 containerPort:
iptables -t nat -nL KUBE-SEP-YYYYYY整个链条走完,你就能判断问题是出在路由层面,还是规则层面。如果规则本身没问题,再继续往路由和转发层排查:
sysctl net.ipv4.ip_forward ip route get 172.20.1.5这里有个容易忽略的细节:Service 的targetPort是容器端口,不是 Pod 的监听端口。如果规则里 DNAT 的端口和实际监听端口不一致,Service 当然不通,但kubectl get endpoints看起来却一切正常。
5.3 用 ipvsadm 看虚拟服务与真实后端
如果确认是 IPVS 模式,就用ipvsadm来排查。
先看有没有对应的 virtual server:
ipvsadm -L -n | grep 10.96.102.6再看该 VIP 下的 real server 列表:
ipvsadm -L -n --sort | grep 10.96.102.6 -A 5重点关注三列:
Weight:后端权重。Kubernetes 默认权重是 1,如果异常变成 0,说明该后端被摘除,可能是 readiness 探针挂了。ActiveConn:当前活跃连接数。如果某个后端数值异常高,可能是热点后端。InActConn:非活跃连接数。这个值一般不用太纠结,但如果持续增长,说明连接没被正确关闭。
ipvsadm还支持实时连接表查看:
ipvsadm -l -n -c | grep 10.96.102.6这个命令能看到当前连接表中,源 IP 到 VIP、VIP 到 real server 的映射关系。配合它,可以判断某个客户端 IP 是不是被会话保持固定到了某个后端,也能检查连接超时参数是否合理。
还有一个小技巧:IPVS 模式下如果某个 Service 设了externalTrafficPolicy: Cluster,客户端源 IP 会被伪装成节点 IP。这时不要依赖 Pod 侧日志里的 client IP 来排查,因为那已经不是真实客户端地址了。
5.4 高频故障根因梳理
Service 流量不通,根因往往集中在几个固定位置。我在不同集群里反复见过这几类问题,列出来供大家对照:
第一,ip_forward未开启。iptables 和 IPVS 模式都依赖内核转发。检查/proc/sys/net/ipv4/ip_forward是否为 1。CNI 插件一般会设置,但有些手动装好的节点容易漏。
第二,CNI 网络路由不可达。规则把目标改成了 PodIP,但节点到 PodIP 的路由依赖 CNI 插件的路由表和网络策略。在源节点执行ping <podIP>或ip route get <podIP>,能快速判断是不是 CNI 层的问题。
第三,conntrack 表满。短连接多、异常连接未清理时,会出现 Service 随机超时。处理方法除了调大nf_conntrack_max,还要分析应用层的连接复用是否合理。
第四,本机进程访问 ClusterIP 不通,但外部访问正常。这时优先检查OUTPUT链上的KUBE-SERVICES跳转是否被其他防火墙规则拦截了。kube-proxy 的规则优先级和用户自定义规则冲突时,会出现这种"内外表现不一致"的奇怪现象。
第五,IPVS 模式会话保持不生效。检查 Service 是否设置了sessionAffinity: ClientIP,以及sessionAffinityConfig.clientIP.timeoutSeconds是否满足预期。同时确认 kube-proxy 启动参数里没有把--ipvs-scheduler改成与预期冲突的算法。
第六,NodePort 访问不通但 ClusterIP 正常。重点检查KUBE-NODEPORTS链规则、节点安全组是否放通端口,以及externalTrafficPolicy是否为Local导致某些节点没有后端而拒绝连接。
这些根因里的前两条,即使从 iptables 换成 IPVS 也不会消失,因为它俩本来就不属于调度规则的问题。这正好说明一件事:理解底层机制的意义,不只是为了选型,更是为了在故障时能快速判断"问题到底是不是 kube-proxy 引起的"。
最后再分享一个我自己的习惯:每次新建集群,我会先在节点上跑一遍lsmod | grep ip_vs,然后在第一台节点上临时把 kube-proxy 切到 IPVS 模式,用ipvsadm确认虚拟服务器列表能正常生成,再决定后续节点怎么初始化。这个动作看起来简单,但能提前过滤掉大量"看似配了 IPVS 实际还在 iptables"的隐性坑。排查 Service 流量问题时,我也建议各位养成先确认数据面模式、再看规则链、最后才查应用层的习惯。数据面的规则是死的,一层层追下去,问题基本都能浮出水面。