
在 Kubernetes 里Service 的代理模式是每个接触集群的人迟早都会遇到的坎。尤其当你在群里问“iptables 和 IPVS 到底选哪个”的时候一定会有人告诉你“集群大了就用 IPVS小了无所谓。”这句话听过很多遍但真到自己要调优、要排障、要切换模式的时候只靠这一句是完全不够的。我自己带过不少从几百到几千节点的集群也陪着开发排过 ClusterIP 偶尔超时、Pod 扩容后流量还是打到旧节点这种问题。今天这篇就从数据流、内核转发机制到生产实操把 iptables 代理模式和 IPVS 代理模式的区别一次说清楚。这篇文章不是让谁去背八股而是给正在维护集群、或者在 Service 性能调优时拿不定主意的朋友看。看完你能明白两条转发链路各自做了什么、什么时候会出问题、以及怎么安全地把集群切到 IPVS 模式。1. 先从数据流说起Service 到底怎么把流量送进 Pod1.1 kube-proxy 在 Service 转发中的位置很多人把 Service 当成一个“魔法虚拟 IP”其实 Kubernetes 本身不做数据面的转发真正干活的是每个节点上的kube-proxy。kube-proxy 会实时 watch Service 和 EndpointSlice 的变化然后把“目标 IP 是 ClusterIP 端口”的流量改写为“目标 IP 是某个 Pod IP 端口”。这个改写动作在不同的代理模式里实现方式完全不同。早期有 userspace 模式性能太差基本被淘汰后面长年默认使用的是 iptables 模式再后来出现了 IPVS 模式。这里说的“代理模式”本质上就是 kube-proxy 把 Service 数据面规则“编程”进内核时选择的编程工具和编程方式。我习惯把它理解成Service 是一个需求描述kube-proxy 是施工队iptables 和 IPVS 是两种不同的施工方案。需求都一样——把流量送到一个可用的后端 Pod 上但方案决定了性能上限、故障表现和排查手段。1.2 iptables 模式是怎么把流量转出去的iptables 模式下kube-proxy 会在 NAT 表里为每个 Service 生成一串链和规则。核心逻辑是流量进入节点后先经过KUBE-SERVICES链匹配 ClusterIP、协议、端口命中 Service 后跳转到对应的KUBE-SVC-xxxx链KUBE-SVC-xxxx里通过statistic模块模拟随机选择按概率跳到某个KUBE-SEP-xxxx链KUBE-SEP-xxxx链则执行 DNAT把目标 IP 改成某个后端 Pod 的 IP。典型规则长这样-A KUBE-SVC-ABCDE -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-X -A KUBE-SVC-ABCDE -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-Y -A KUBE-SVC-ABCDE -j KUBE-SEP-Z看到没有3 个后端的时候第一条规则命中概率是 1/3第二条是 1/2最后一条兜底。后端越多概率链越长。流量第一次建立连接时会走完这条链选出一个 Pod然后这个连接会被 conntrack 记录下来后续属于同一连接的数据包就直接按 conntrack 表项转发不会再重新遍历规则。所以 iptables 模式的关键点在于新建连接时需要遍历链规则越多建连开销越大。1.3 IPVS 模式又是怎么转出去的IPVS 模式完全换了一套思路。它不再为每个后端生成一条一条的 iptables 链而是直接在内核里创建一个“虚拟服务器”把 Service 的 ClusterIP:Port 作为虚拟服务地址把每个后端 Pod 作为一个 RealServer 挂上去。kube-proxy 通过内核接口把 Service 和 Endpoint 信息同步给 IPVSIPVS 本身就是一个内核态的 L4 负载均衡器。数据包到节点以后IPVS 通过哈希表查找这个连接应该归属哪个虚拟服务然后按照配置的调度算法选一个后端做 NAT 转发。用ipvsadm -Ln能看到类似下面的输出IP Virtual Server version 1.2.1 Prot LocalAddress:Port Scheduler Flags - RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 10.96.0.1:443 rr - 192.168.1.11:6443 Masq 1 0 0 TCP 10.96.0.10:53 rr - 192.168.1.21:53 Masq 1 0 0 - 192.168.1.22:53 Masq 1 0 0IPVS 不需要再像 iptables 那样搞一大堆串联的规则。它的核心结构是一张哈希表查找复杂度是 O(1)调度算法也已经在内核里实现所以性能和可扩展性都有明显提升。2. 为什么说 IPVS 是“更大规模下的 iptables 替代案”2.1 iptables 的规则数量会随 Endpoint 线性膨胀iptables 模式的本质问题是Service 越多、后端 Pod 越多规则就越多。而且每个 Service 的规则链是“串联”的新连接要把这条链跑完才知道去哪。举个例子集群里有 200 个 Service平均每个 Service 有 10 个后端看起来也就 2000 个加载项听起来不多。但每条规则在后面其实还关联了KUBE-SEP链而每个后端除了 DNAT可能还有 MASQUERADE、RETURN 等配套规则。实际插入 NAT 表的规则总数会到一万条以上这在一台 8 核 16G 的节点上已经能明显感觉到变化。更难受的是更新。只要某个 Service 有一个 Endpoint 变化kube-proxy 都要重新计算整个 Service 的规则集然后调用iptables-restore全量下发。规则上万条之后每次下发耗时可能从几毫秒涨到几百毫秒。Endpoints 频繁抖动时kube-proxy 的 CPU 会明显波动节点转发性能也受影响。这不是 iptables 本身不安全而是“用一大堆链式规则模拟负载均衡选择”这个思路面对大规模 Service 时必然会有瓶颈。2.2 IPVS 用“表驱动”替代“链匹配”IPVS 是 Linux 内核里很早就存在的 LVS 核心组件它做负载均衡的思路是先维护一张虚拟服务器表再维护每个虚拟服务器下的后端列表。数据包进来后做哈希查找然后选后端。最直观的差别是iptables 的复杂度跟规则条数相关IPVS 的查找复杂度跟条数几乎无关。哪怕后面挂 100 个后端哈希查找一次也能定位到对应的虚拟服务再根据调度算法选具体的一个后端。这个选后端的动作是在内核里直接完成的不需要在用户态每次都跑“下一条规则”。同时 IPVS 自带多种调度算法轮询rr、加权轮询wrr、最少连接lc、加权最少连接wlc、源地址哈希sh、目标地址哈希dh、最短预期延迟sed、不排队nq。iptables 模式的“随机命中选择”本质上只算一种粗糙的负载均衡没有能力根据后端实际负载做选择。多说一句IPVS 模式下 iptables 并没有被彻底扔掉。kube-proxy 仍会保留少量 iptables 规则负责包伪装、转发链放行、Hairpin 等逻辑。但它不再为每个 Endpoint 生成一整套SVC/SEP链核心调度全部交给 IPVS这就有本质区别。2.3 遇到的边界IPVS 不等于零开销要客观说一句IPVS 模式也不是毫无成本。首先它依赖内核模块至少得有ip_vs如果你要用具体调度算法还得有对应的ip_vs_rr、ip_vs_wrr、ip_vs_sh等模块。某些安全加固特别严格的内核环境可能没编译这些模块需要联系基础设施团队提前加进去。其次是 IPVS 同样依赖 conntrack而不是完全替代 conntrack。即使 IPVS 自己在内核里维护了连接条目在 Kubernetes 的 NAT 场景下数据包还是需要经过 conntrack 做状态跟踪和回包反向转换。所以“上了 IPVS 就完全不碰 conntrack”这个想法是错的。3. 功能、性能和运维上的对比别只盯着一行结论3.1 一张表看明白核心差异对比之前先统一一个认知两种模式处理的都是 L4 范围内的转发都是 NAT 模式都不能感知七层协议。下面这张表是我在实际运维中的经验总结不是简单的网摘。对比项iptables 代理模式IPVS 代理模式核心数据结构链式规则哈希表新连接查找复杂度与规则条数相关可能到 O(n)哈希查找接近 O(1)调度算法随机选择rr / wrr / lc / wlc / sh / dh / sed / nq规则数量与 Service、Endpoint 数量正相关核心规则在 IPVS 表中iptables 规则少很多Service/Endpoint 较多时规则下发慢kube-proxy CPU 偏高表项管理更轻扩容更平滑可观测性iptables -t nat -L/iptables-saveipvsadm -Ln/ipvsadm -LncsessionAffinity支持支持externalTrafficPolicy支持支持内核模块依赖无额外要求需要ip_vs系列模块排查门槛普通运维对 iptables 更熟悉需要懂ipvsadm和 LVS 基本概念需要强调的是上面这些是“通常情况下的表现”。如果你集群只有两三个节点、几十个 Serviceiptables 模式下没准根本感知不到性能差异。这时候为了“新潮”去切 IPVS反而会引入新的认知成本。3.2 调度算法不是越多越好要按场景选IPVS 支持那么多调度算法不代表你在 Kubernetes 里应该随便换。kube-proxy 默认使用rr这也是大多数场景下最稳的选择。真正的问题是你要不要一开始就上wlc或sh我见过有人为了“负载均衡更智能”把调度算法改成lc结果后端 Pod 里有一个连接数长期很高新的请求反而被大量调度到另一个 Pod。原因是长连接、短连接混合的情况下连接数并不一定代表真实负载。lc和wlc更适合“连接处理耗时接近但连接数量差异大”的场景而不是万能方案。如果你的业务对会话连续性有强需求可以用sh按源 IP 哈希到固定的后端。但要注意如果某个后端 Pod 挂了IPVS 重新选后端时原本打到它上面的那批会话还是会被切走。业务层如果有重试机制会更好。我的建议是没有特殊需求就用rr。想保留客户端 IP 的亲和性先看 Service 的sessionAffinity: ClientIP能不能满足不要一上来就改调度算法。3.3 什么场景可以继续用 iptables不要形成一个思维定式IPVS 全面优于 iptables必须切换。对小集群、低流量、后端数量稳定的业务iptables 模式简单直接而且大家更熟悉它的排查方式。另一个现实因素是有些发行版或云平台对 kube-proxy 的管理是封装过的。比如你用托管集群可能根本没有权限去改 kube-proxy 的 ConfigMap。这时候纠结 IPVS 没有任何意义不如在业务层加负载均衡或者直接靠外部负载均衡器把流量打到 Service 上。如果集群规模在 50 节点以内Service 和 Endpoint 总量不大iptables 模式完全够用。切换 IPVS 并不能带来质的提升反而要额外确认内核模块、安装ipvsadm、学习一套新的观测命令。性能优化是系统工程先测量再优化不要为了“优化”而优化。4. 手把手把集群切到 IPVS 模式4.1 切换前先确认当前模式和内核模块任何切换的第一步都不是改配置而是搞清楚现状。先看看当前 kube-proxy 到底是什么模式kubectl -n kube-system get cm kube-proxy -o yaml还是那个老问题不同发行版里 kube-proxy ConfigMap 的结构不一样。有的直接在data底下能看到mode: iptables有的会放在data.config.conf里找不到就看 kube-proxy 启动日志kubectl -n kube-system logs ds/kube-proxy -c kube-proxy --tail20 | grep -i proxier如果能搜到Using iptables Proxier说明当前是 iptables 模式如果搜到Using ipvs Proxier说明已经切过了。接着看内核模块lsmod | grep ip_vs如果没有任何ip_vs相关输出至少要加载基础模块和常用的调度算法模块modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_lc modprobe ip_vs_wlc modprobe ip_vs_sh modprobe ip_vs_sed modprobe ip_vs_nq这里有个小坑只加载ip_vs基础模块IPVS 默认调度算法rr才能跑如果你计划用wrr但没加载ip_vs_wrrkube-proxy 启动时会报错或者回退最好一次把所有算法模块都加载好。4.2 修改 kube-proxy 的 mode 并触发滚动重启确认模块没问题后改 ConfigMapkubectl -n kube-system edit cm kube-proxy找到代理模式相关配置原来是mode: 或者mode: iptables改成mode: ipvs ipvs: scheduler: rr改完以后触发 kube-proxy 的滚动重启kubectl -n kube-system rollout restart ds kube-proxy这里要特别提醒这不是立即完成的操作。DaemonSet 滚动会一个节点一个节点地更新期间 kube-proxy 会在节点上短暂重启新的 Service 规则会重新下发。如果你在维护窗口内操作影响会更小。有些发行版会额外要求你在kubeadm配置里同步改kube-proxy的 ConfigMap或者通过 Helm 管理 kube-proxy。不管入口是什么最终都要保证 DaemonSet 的 Pod 使用的是新配置。4.3 验证 IPVS 规则真的生效了改完不要急着收工必须验证。先看 Pod 日志kubectl -n kube-system logs ds/kube-proxy -c kube-proxy --tail20 | grep -i proxier如果看到Using ipvs Proxier说明 kube-proxy 已经以 IPVS 模式运行。再用ipvsadm查看规则ipvsadm -Ln集群里现有的 Service 应该能对应看到 IPVS 虚拟服务条目。同时再看一下 iptables 规则的变化你会明显发现 NAT 表里不再有大量KUBE-SEP链iptables -t nat -L KUBE-SERVICES -n | head如果看不到ipvsadm命令说明节点上没装需要先安装# Ubuntu/Debian apt install -y ipvsadm # CentOS/RHEL yum install -y ipvsadm有条件的集群建议提前在所有节点上都装上ipvsadm否则切换后想排查都无从下手。4.4 切换时最容易踩的坑切换 IPVS 模式后最常见的坑是 kube-proxy 明明没报错但某个 Service 突然访问不通。先查内核模块是否在节点重启后自动加载了。modprobe只是临时加载重启后可能又没了。最好把模块写进/etc/modules-load.d/ipvs.conf确保节点重启后模块自动加载。另一个容易踩的坑是切换后 kube-proxy 的转发规则会重建虽然已建立的连接理论上由 conntrack/IPVS 连接表接管但如果你在这个过程中又同时缩容了后端 Pod还是会有一部分连接被重置。所以做切换时尽量避免跟发版、扩缩容操作叠加。还有一点IPVS 模式下 kube-proxy 仍然保留了一部分 iptables 规则千万别看到还有 iptables 规则就觉得切换失败。关键判断标准是看日志里的Proxier类型以及ipvsadm -Ln有没有虚拟服务条目。5. 常见问题与排查实录5.1 切到 IPVS 后 Service 为什么突然不通这种情况我排查过好几次原因大多不是 IPVS 本身的问题而是内核模块没加载。看 kube-proxy 日志如果出现类似IPVS proxier will not be used because the following required kernel modules are not loaded: [ip_vs ip_vs_rr]的输出说明 kube-proxy 已经回退到 iptables 模式或者干脆没法完成代理逻辑。处理方式是先确认内核模块lsmod | grep ip_vs没有就手动加载然后重启 kube-proxy Pod。如果重启后还是不通再看 Service 的 Endpoints 是否 Ready以及节点上的ipvsadm -Ln里有没有对应的虚拟服务。IPVS 模式同样依赖 EndpointSlice/Endpoints 同步后端 Pod 不 ReadyIPVS 表里也不会有 RealServer。5.2 流量分布不均是不是 rr 调度没用有次同事跑过来问为什么后端两个 Pod用 IPVS 的rr调度连接数还是差距很大原因要看业务请求模型。rr调度只保证“新建立连接的来源选择是轮流来的”但它管不了连接存活时间。如果你的业务是大量长连接一个 Pod 里的连接可能长期被占用另一个 Pod 的连接被复用长时间看连接数天然不均衡。这不是 IPVS 的 bug而是“轮询”这种算法本身的语义。这种场景下要看业务能不能接受连接在 Pod 间重新分配。如果连接可以随时重建那就用lc或wlc让新建连接倾向于当前连接数更少的后端。如果连接不能断那就得在业务层做一致性哈希比如通过 Service 的sessionAffinity: ClientIP保证同一来源固定到同一后端。5.3 IPVS 模式下拿不到客户端真实 IP这不是 IPVS 模式独有的问题但我确实见过不少人切到 IPVS 后才发现 NodePort 后端收到的源 IP 变了。默认转发方式下数据包经过节点转发到 Pod 时源地址会被伪装成节点 IP。要保留客户端真实 IP必须把 Service 的externalTrafficPolicy改为Local让流量只调度到本节点上的 Pod不做跨节点转发。这里要提醒一句改成Local后如果某个节点没有 Ready 的后端 Pod从它进来的 NodePort 流量会被直接丢弃。这是正常行为不是故障。所以用Local前先评估后端 Pod 是不是能打散到所有节点否则要配合 PodDisruptionBudget 或拓扑分布约束。5.4 小集群要不要跟风切换到 IPVS如果你只有十几台节点、几百个 Endpointiptables 模式跑得好好的我不建议为了“更好”去切。切换 IPVS 会引入新的内核模块依赖、新的运维命令和新的排障知识。对于小规模集群这些成本比收益更明显。真要优化先把节点资源和 conntrack 参数调好很可能收益更大。但如果集群规模已经大到 iptables 规则数千条、kube-proxy 更新时 CPU 明显飙升、服务质量下降那就应该认真评估 IPVS。就我自己的经验超过 100 个 Service 并且后端 Pool 经常变动的集群切换到 IPVS 后能明显看到 kube-proxy 更“安静”规则更新也更平稳。最后再分享一个经验无论切不切都要把ipvsadm相关命令和内核模块检查方法写进运维手册别等出故障再临时翻文档。而且任何切换前先挑一个非核心业务 Service 做验证再逐步放量。这个话题看着简单真正操作起来那些位于 iptables 规则细节和内核模块依赖里的坑才是真正考验运维功底的地方。