☰
kube-proxy从iptables切换到IPVS:大规模集群Service转发性能优化实战
2026/10/27 21:51:16 网站建设 项目流程

kube-proxy 从 iptables 模式切到 IPVS 模式,这件事在集群规模小的时候几乎感觉不到差别,但只要节点上 Service 数量过了几百、Pod 副本数上千,iptables 那条链的线性匹配开销就会开始咬人。我最早动这个念头,是因为一个中等规模的业务集群里 Service 已经逼近两千,节点上iptables-save的输出动辄十几万行,规则刷新时 CPU 尖刺明显,新建连接的延迟抖动也跟着上来了。IPVS 基于哈希表做转发决策,时间复杂度是常数级,天然适合这种场景。这篇内容就是把我自己从选型、改造、验证到踩坑的完整过程摊开讲清楚,适合已经对 Kubernetes 网络模型有基本概念、正在被大规模 Service 转发性能困扰、或者单纯想把集群网络底座升级一档的运维和平台开发同学参考。哪怕你只是刚接触 kube-proxy 的调度模式,文中的原理拆解和操作步骤也能让你照着走一遍。

1. 为什么要把调度方式换成 IPVS

1.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 的哈希转发原理与适用边界

IPVS(IP 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,性能不均考虑 wrr
ipvs.strictARPfalse同网段部署建议 true避免 ARP 抢答导致流量错乱
ipvs.excludeCIDRs空有本地直连需求时配置排除后该网段不走 IPVS
ipvs.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 起不来,日志里报can't 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_max=1048576 # 持久化写入 echo "net.netfilter.nf_conntrack_max=1048576" >> /etc/sysctl.conf

注意:调大 conntrack 上限会占用更多内存,按节点内存量力而行。一般每 1 万条目约占用几 MB,百万级条目要预留几百 MB 内存,别在小内存节点上盲目调大。

4.3 内核模块缺失与版本不匹配

模块缺失的表现是 kube-proxy 起不来,日志报can't 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)和持久化配置(比如模块加载),它们平时不显山露水,出问题时却能让整个节点网络瘫痪。把这些细节提前想到、做到,比事后救火省心得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询