
kube-proxy 从 iptables 模式切到 IPVS 模式这件事在集群规模小的时候几乎感觉不到差别但只要节点上 Service 数量过了几百、Pod 副本数上千iptables 那条链的线性匹配开销就会开始咬人。我最早动这个念头是因为一个中等规模的业务集群里 Service 已经逼近两千节点上iptables-save的输出动辄十几万行规则刷新时 CPU 尖刺明显新建连接的延迟抖动也跟着上来了。IPVS 基于哈希表做转发决策时间复杂度是常数级天然适合这种场景。这篇内容就是把我自己从选型、改造、验证到踩坑的完整过程摊开讲清楚适合已经对 Kubernetes 网络模型有基本概念、正在被大规模 Service 转发性能困扰、或者单纯想把集群网络底座升级一档的运维和平台开发同学参考。哪怕你只是刚接触 kube-proxy 的调度模式文中的原理拆解和操作步骤也能让你照着走一遍。1. 为什么要把调度方式换成 IPVS1.1 iptables 模式在大规模集群下的真实瓶颈要理解这次改造的价值得先把 iptables 模式的工作机制说透。kube-proxy 在 iptables 模式下会为每一个 Service 生成一组规则ClusterIP 的访问靠KUBE-SERVICES链做匹配再跳到对应的KUBE-SVC-xxx链最后分发到KUBE-SEP-xxx端点链。问题在于iptables 的规则匹配本质上是顺序遍历一条链上的规则越多一个数据包要走到目标规则需要经过的比对次数就越多。打个生活化的比方iptables 像一本没有目录的纸质电话簿你要找一个联系人只能从第一页开始一页页翻。Service 有一千个平均每个包要翻五百页才能找到对应规则。而 IPVS 更像一个哈希索引的通讯录你报出名字它直接算出该翻到哪一页一步到位。这个差异在小集群里被缓存和规则命中率掩盖了但规模一上来差距是指数级拉开的。具体到实测数据我在一个 3 节点、每节点约 1500 个 Service 的环境里做过对比iptables 模式下iptables-restore单次全量同步耗时接近 4 秒期间 kube-proxy 的 CPU 占用能冲到单核 80% 以上切到 IPVS 后同样的 Service 规模规则同步耗时降到 300 毫秒以内CPU 峰值不到 20%。这个量级的差异直接决定了集群在频繁扩缩容时网络平面的稳定性。1.2 IPVS 的哈希转发原理与适用边界IPVSIP Virtual Server是内核里的一套四层负载均衡实现它把 Service 的 ClusterIP 和端口注册成一个虚拟服务virtual service把后端 Pod 注册成真实服务器real server转发时通过哈希表直接定位。kube-proxy 在 IPVS 模式下不再写海量 iptables 规则而是通过 netlink 接口调用 IPVS 的内核模块来维护转发表。这里有个关键点很多人会忽略IPVS 模式并不是完全抛弃 iptables。kube-proxy 仍然会用少量 iptables 规则处理 SNAT、masquerade、NodePort 的某些边界情况以及 IPVS 不支持的场景比如某些特殊协议的端口映射。所以切过去之后你依然能看到 iptables 规则只是数量从十几万行骤降到几百行这才是性能提升的来源。IPVS 的适用边界也要说清楚。它擅长的是四层转发对七层特性无感它依赖内核模块ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack等内核版本太低或者裁剪过的内核可能缺模块。另外 IPVS 的调度算法rr、wrr、lc、sh 等决定了后端分发的均衡策略kube-proxy 默认用 rr轮询这个默认值在多数场景够用但如果你有长连接业务可能需要考虑会话保持相关的配置。1.3 改造前必须确认的三个前提动手之前有三件事必须先确认否则改到一半会卡住。第一是内核模块可用性。在目标节点上执行lsmod | grep ip_vs看模块是否已加载没有的话modprobe ip_vs试加载再确认ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack这几个依赖模块都在。有些精简版系统镜像会裁掉这些模块那就得先换内核或装对应模块包这一步不通过后面全是空谈。第二是kube-proxy 的部署形态。是以 DaemonSet 跑在集群里还是以系统服务跑在宿主机上前者改配置相对简单改 ConfigMap 重启 Pod 即可后者要改节点上的配置文件并重启服务回滚成本更高操作要更谨慎。两种形态的配置路径和生效方式完全不同搞混了会出现改了没生效的迷惑现象。第三是集群里是否有依赖 iptables 规则做特殊拦截的组件。有些网络插件或者安全组件会往 iptables 里插自定义链IPVS 模式下这些链的挂载点可能变化需要提前梳理。我遇到过某监控组件依赖KUBE-SERVICES链做流量统计切 IPVS 后统计归零后来改成从 IPVS 的统计接口取数才恢复。2. 改造方案设计与关键参数取舍2.1 整体切换思路先灰度后全量我的做法不是一把梭全集群切换而是先单节点灰度、再分批推进。原因很直接kube-proxy 的模式切换会影响该节点上所有 Pod 的 Service 访问一旦出问题影响面是整个节点而不是单个服务。灰度能把这个爆炸半径控制住。具体节奏是挑一个非核心业务的节点把它上面的 kube-proxy 切成 IPVS观察至少 24 小时重点看 Service 访问成功率、新建连接延迟、kube-proxy 自身资源占用、以及 IPVS 转发表的条目数是否与预期 Service 数吻合。确认稳定后再按批次比如每次 20% 节点推进每批之间留出观察窗口。这个节奏看起来慢但比全量切换后回滚要快得多——回滚一次全集群的代价远比多等几天大。2.2 调度算法与转发模式的参数选择kube-proxy 的 IPVS 配置里有几个参数值得掰开说。ipvs.scheduler决定调度算法默认rr轮询。如果你的后端 Pod 性能不均wrr加权轮询配合权重更合适如果是需要会话保持的长连接场景sh源地址哈希能让同一源 IP 的请求固定落到同一后端。但要注意sh在 NAT 环境下可能因为源地址被改写而失效用之前得确认网络路径。ipvs.strictARP这个参数控制是否开启严格 ARP 模式默认false。当 ClusterIP 和后端 Pod 在同一网段、且存在 ARP 冲突风险时建议设为true避免节点响应不属于自己的 ARP 请求导致流量错乱。这个坑我在早期项目里踩过表现为偶发的 Service 访问超时抓包才定位到是 ARP 应答抢答。ipvs.excludeCIDRs用来排除某些 CIDR 不走 IPVS 转发典型场景是节点本地服务或者需要直连的网段。这个参数不设的话某些本地回环访问可能被错误地卷进 IPVS 转发出现访问不通的怪现象。参数默认值推荐场景注意事项ipvs.schedulerrr通用场景长连接考虑 sh性能不均考虑 wrripvs.strictARPfalse同网段部署建议 true避免 ARP 抢答导致流量错乱ipvs.excludeCIDRs空有本地直连需求时配置排除后该网段不走 IPVSipvs.tcpTimeout0沿用内核默认长连接业务可调大过大会占用 conntrack 表ipvs.udpTimeout0沿用内核默认UDP 业务按需调整UDP 无连接状态超时影响回收2.3 回滚预案的设计任何改造都要想好怎么退。IPVS 切回 iptables 的操作本身不复杂——把模式改回去、重启 kube-proxy 即可但回滚的时机和触发条件要提前定好。我给自己定的红线是灰度节点上 Service 访问错误率超过 0.5% 持续 5 分钟或者出现大面积连接超时立即回滚该节点。回滚前要保留现场ipvsadm -Ln的输出、iptables-save的快照、kube-proxy 的日志这些是事后定位问题的关键。我习惯在切换前先跑一遍ipvsadm -Ln /tmp/ipvs-before.txt和iptables-save /tmp/iptables-before.txt回滚后对比差异能快速判断是配置问题还是内核问题。3. 实操过程从配置修改到验证落地3.1 环境预检与内核模块加载正式动手前先在目标节点上做一轮预检。这一步不能省我见过太多人直接改配置重启结果 kube-proxy 起不来日志里报cant find ip_vs module回头再补模块中间服务已经抖了一轮。预检命令按顺序执行# 检查内核版本建议 4.19 以上 uname -r # 检查 IPVS 相关模块是否已加载 lsmod | grep -E ip_vs|nf_conntrack # 若未加载手动加载并验证 modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack # 再次确认模块状态 lsmod | grep ip_vs # 检查 ipvsadm 工具是否可用用于后续验证 which ipvsadm || echo ipvsadm not installed如果modprobe报模块找不到说明内核没编进这些模块需要装kernel-modules-extra之类的包或者换内核。这一步过了再确认ipvsadm工具在后面验证全靠它。注意模块加载是临时的重启后会丢失。要持久化得把模块名写进/etc/modules-load.d/ipvs.conf每行一个模块名。别只modprobe完就以为万事大吉节点一重启又回到解放前。3.2 修改 kube-proxy 配置的两种路径配置修改分两种部署形态路径完全不同先确认你的集群是哪种。DaemonSet 形态kube-proxy 以 Pod 形式跑在kube-system命名空间。配置在 ConfigMapkube-proxy里改mode字段为ipvs并按需补上ipvs段。改完 ConfigMap 后需要重启 kube-proxy 的 Pod 让配置生效——注意是重启 Pod不是滚动更新 Deployment因为 kube-proxy 是 DaemonSet直接kubectl rollout restart daemonset/kube-proxy -n kube-system即可。apiVersion: v1 kind: ConfigMap metadata: name: kube-proxy namespace: kube-system data: config.conf: |- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: ipvs ipvs: scheduler: rr strictARP: true excludeCIDRs: [] # 其余字段保持原样系统服务形态kube-proxy 以 systemd 服务跑在宿主机配置文件通常在/var/lib/kube-proxy/config.conf或/etc/kubernetes/kube-proxy-config.yaml具体路径看你的部署方式。改完mode: ipvs后systemctl restart kube-proxy重启服务。这种形态下没有 ConfigMap 兜底改之前务必先备份原文件cp config.conf config.conf.bak回滚时直接覆盖回去。两种形态改完后都要确认 kube-proxy 日志里出现类似Using ipvs Proxier的字样这是模式生效的直接证据。如果日志还是Using iptables Proxier说明配置没被读到检查路径和字段名是否写对。3.3 验证 IPVS 转发表与 Service 连通性配置生效后验证分三层转发表、连通性、性能。第一层看转发表。ipvsadm -Ln会列出所有 virtual service 和对应的 real server。正常情况下集群里有多少个 Service含 ClusterIP 类型这里就该有多少条 virtual service 记录每条下面挂着对应的后端 Pod 地址和端口。如果条目数明显偏少可能是 kube-proxy 还没同步完等几十秒再看如果一直偏少查 kube-proxy 日志有没有同步报错。# 查看 IPVS 转发表 ipvsadm -Ln # 统计 virtual service 数量与集群 Service 数对比 ipvsadm -Ln | grep -c TCP\|UDP # 查看某个具体 Service 的后端分布 ipvsadm -Ln | grep -A 5 10.96.0.10第二层看连通性。在节点上直接访问一个 ClusterIP 的 Service确认能通再从 Pod 内部访问确认跨节点也通。我习惯用curl打一个已知的 HTTP 服务配合-w输出耗时顺便看延迟是否正常。如果访问不通先ipvsadm -Ln看该 Service 有没有对应条目再看后端 Pod 是否 Ready逐层排除。第三层看性能。对比切换前后的新建连接延迟和 kube-proxy 资源占用。我用的方法是ab或wrk对某个 Service 压一轮记录 P99 延迟同时top看 kube-proxy 进程的 CPU。正常情况下 IPVS 模式下 kube-proxy 的 CPU 应该明显低于 iptables 模式延迟抖动也更小。3.4 灰度推进与全量切换的节奏控制单节点验证通过后进入灰度推进。我的做法是按节点分批每批之间留观察窗口。具体操作上如果是 DaemonSet 形态可以通过给节点打标签配合 nodeSelector 控制哪些节点先跑新配置或者直接分批重启不同节点上的 kube-proxy Pod用kubectl delete pod指定节点上的那个 Pod让它重建时读到新 ConfigMap。每批推进后重点盯三个指标Service 访问错误率、节点上ipvsadm -Ln的条目数是否与预期一致、kube-proxy 日志有无异常。这三个指标稳定 24 小时再推下一批。全量切换完成后别急着收工再观察一周因为有些问题比如长连接超时、conntrack 表满需要时间才暴露。提示灰度期间如果发现某节点异常回滚只需把该节点的 kube-proxy 配置改回iptables并重启不影响其他已切换节点。这种按节点独立回滚的能力是灰度策略最大的价值别为了省事跳过灰度。4. 常见问题与排查技巧实录4.1 切换后 Service 访问不通的排查路径这是最高频的问题排查要按层走别一上来就瞎改。我的排查顺序是先看 IPVS 表有没有该 Service 的条目再看后端 real server 是否健康最后看网络路径和 conntrack。如果ipvsadm -Ln里压根没有目标 Service 的条目问题在 kube-proxy 同步层——查 kube-proxy 日志有没有Failed to sync之类的报错常见原因是 Service 的某些字段 IPVS 不支持比如某些特殊协议的端口名或者 kube-proxy 权限不足调不了 netlink。如果条目在但后端为空说明 Endpoints 没同步过来查 Service 的 selector 是否匹配到 Pod、Pod 是否 Ready。如果条目和后端都在但访问不通就要看 conntrack 和网络路径了conntrack -L | grep ClusterIP看有没有异常状态的连接tcpdump抓包看包到底走到了哪。现象可能原因排查命令处理方式IPVS 表无该 Servicekube-proxy 同步失败查 kube-proxy 日志修复配置或权限后重启有条目但后端为空Endpoints 未同步kubectl get endpoints检查 selector 和 Pod 状态条目后端正常但访问超时conntrack 或网络路径问题conntrack -L、tcpdump清理 conntrack 或查路由偶发访问失败ARP 冲突或 strictARP 未开arping、查 strictARP 配置开启 strictARP长连接频繁断开tcpTimeout 过短查 ipvs.tcpTimeout调大超时值4.2 conntrack 表满导致的连接丢弃IPVS 依赖 conntrack 做连接跟踪表满时会直接丢新连接表现为偶发访问失败或新建连接超时。这个问题在 iptables 模式下也存在但 IPVS 模式下因为转发路径变化暴露得更明显。排查方法是conntrack -C看当前条目数sysctl net.netfilter.nf_conntrack_max看上限。如果当前数逼近上限就是表满了。处理分两步短期调大nf_conntrack_max长期优化nf_conntrack_tcp_timeout_established等超时参数让空闲连接更快回收。# 查看当前 conntrack 条目数和上限 conntrack -C sysctl net.netfilter.nf_conntrack_max # 临时调大上限重启失效 sysctl -w net.netfilter.nf_conntrack_max1048576 # 持久化写入 echo net.netfilter.nf_conntrack_max1048576 /etc/sysctl.conf注意调大 conntrack 上限会占用更多内存按节点内存量力而行。一般每 1 万条目约占用几 MB百万级条目要预留几百 MB 内存别在小内存节点上盲目调大。4.3 内核模块缺失与版本不匹配模块缺失的表现是 kube-proxy 起不来日志报cant find ip_vs或failed to load ip_vs。前面预检章节已经讲了怎么加载这里补充一个容易忽略的点模块版本要和内核版本匹配。有些节点升级过内核但没重启uname -r显示的是新内核实际跑的还是旧内核modprobe加载的模块和运行内核不匹配会出现加载成功但功能异常的情况。确认方法是对比uname -r和uname -a的输出以及/lib/modules/$(uname -r)目录是否存在。如果目录不存在说明当前运行内核没有对应模块需要重启到新内核或者装匹配的模块包。这个坑比较隐蔽因为modprobe可能不报错但 IPVS 功能就是不对。4.4 从 IPVS 回退 iptables 的注意事项回退操作本身简单但有几个细节要注意。第一回退前先ipvsadm -C清空 IPVS 表避免残留条目干扰第二回退后 iptables 规则会重新全量生成大规模集群下这个同步过程可能耗时几秒到几十秒期间 Service 访问会有短暂抖动要选低峰期操作第三回退后确认iptables-save的规则数恢复到切换前的量级如果明显偏少说明 kube-proxy 还没同步完再等等。回退后别急着删 IPVS 模块留着不影响 iptables 模式运行万一还要再切回去省得重新加载。真正要清理的是那些为 IPVS 临时加的 sysctl 参数如果确认不再用 IPVS再考虑回滚这些参数。5. 我踩过的坑与实操心得5.1 strictARP 没开导致的偶发超时这个坑值得单独说。早期一次切换后Service 访问出现偶发超时概率大概千分之几抓包发现是节点对不属于自己的 ClusterIP 做了 ARP 应答导致流量被引到错误节点。根因是strictARP没开节点在 ARP 层面抢答了。开启strictARP: true后问题消失。这个参数的作用是让节点只对属于自己的 IP 做 ARP 应答避免同网段下的 ARP 冲突。如果你的集群节点和 Pod 在同一网段或者用了某些会广播 ARP 的网络方案强烈建议开启。代价是几乎没有收益是消除一类隐蔽的偶发故障。5.2 调度算法选错导致的长连接抖动默认的rr轮询在短连接场景很均衡但长连接场景下如果后端 Pod 数量变化扩缩容轮询会把新连接分到新 Pod而老连接还挂在老 Pod 上导致负载不均。我遇到过一次扩容后新 Pod 几乎没流量、老 Pod 被打满的情况就是rr在长连接下的典型表现。后来对长连接业务单独用sh源地址哈希同一源 IP 固定落到同一后端扩缩容时影响面可控。但sh也有代价——后端 Pod 数量变化时哈希映射会重排部分连接会重新分布所以扩缩容要配合优雅下线让老连接自然结束。5.3 监控指标要跟着换切 IPVS 后原来基于 iptables 规则数做的监控会失效因为规则数骤降不代表异常。要补上 IPVS 维度的监控ipvsadm -Ln --stats能拿到每个 virtual service 的连接数、包量、字节量这些才是判断转发是否正常的依据。我习惯把ipvsadm -Ln --stats的输出定期采集对比 Service 数量和连接分布异常时能快速定位是哪个 Service 出了问题。另外 kube-proxy 自身的 metrics 里IPVS 模式下的kubeproxy_sync_proxy_rules_duration_seconds和kubeproxy_sync_proxy_rules_last_timestamp_seconds依然有效可以用来判断同步是否及时。这两个指标在切换后要重点盯同步延迟变大往往意味着 kube-proxy 压力上来了。5.4 节点重启后的模块持久化前面提过模块加载是临时的这里再强调一次。节点重启后如果模块没持久化kube-proxy 起不来整个节点的 Service 访问全挂。持久化方法是在/etc/modules-load.d/下建配置文件把ip_vs、ip_vs_rr、ip_vs_wrr、ip_vs_sh、nf_conntrack都写进去每行一个。这个文件在系统启动早期被读取确保模块在 kube-proxy 启动前就加载好。我现在的习惯是任何涉及内核模块的改造第一件事就是把持久化配置写好再谈其他。因为节点重启是迟早的事临时加载的模块撑不过一次重启这个坑踩一次就够记一辈子。6. 改造后的效果与后续可扩展方向全量切换并稳定运行一个月后我复盘了几个关键数据kube-proxy 的 CPU 占用从平均单核 40% 降到 8% 左右规则同步耗时从秒级降到百毫秒级Service 访问的 P99 延迟抖动明显收敛。这些收益在 Service 规模继续增长时会进一步放大因为 IPVS 的转发开销不随 Service 数量线性增长。后续可扩展的方向一个是把 IPVS 的调度算法按业务类型做精细化配置比如在线业务用rr、长连接用sh、批处理用wrr通过 kube-proxy 的配置分段或者按命名空间做差异化另一个是结合 IPVS 的统计接口做更细粒度的流量观测把每个 Service 的连接数、包量纳入监控大盘替代原来基于 iptables 规则的观测方式。这两件事都不难难的是先把模式切换这步走稳后面的优化才有基础。我个人在实际操作中的体会是kube-proxy 换 IPVS 这件事技术难度不高难在节奏控制和细节确认。预检做扎实、灰度不跳过、回滚预案想清楚这三件事做到位切换过程基本不会出大问题。真正容易翻车的地方往往是那些看起来不起眼的参数比如 strictARP和持久化配置比如模块加载它们平时不显山露水出问题时却能让整个节点网络瘫痪。把这些细节提前想到、做到比事后救火省心得多。