Netpath
学习路径46 / 49 · K8s 网络看全程 →
原理深入预计 30 分钟

kube-proxy 的三代实现与 eBPF 替换

iptables 模式在几千个 Service 时会退化。这节讲退化的原因和替代方案。

学完这节你能做到

  • 解释 iptables 模式的规则数量与匹配开销为何随 Service 数增长
  • 说清 IPVS 模式改善了什么、没改善什么
  • 判断是否该开 kubeProxyReplacement

建议先学

跳过这几节会看不懂本节的部分推导

同一件事,三代实现

Service 那节讲过,它的本质是一条分布式 DNAT 规则。这一节讲这条规则是怎么落地的—— 因为实现方式直接决定了集群能撑多少个 Service。

代模式转换发生在查找方式
一userspace用户态代理进程已废弃,每个包都要进出用户态
二iptables内核 netfilter规则链线性匹配
三IPVS内核 IPVS哈希表
四eBPF 替换socket 层 / tc 钩子哈希 map,且根本不进转发路径

iptables 模式为什么会退化

每个 Service 会生成一组规则:一条进 KUBE-SERVICES 链的匹配,一条跳转, 再加每个后端一条 DNAT 规则和概率匹配。

# 看规模
iptables-save | wc -l
iptables-save -t nat | grep -c KUBE-

问题有两个,而且性质不同:

① 匹配开销。netfilter 的链是线性遍历的:包要依次比对,直到命中。 Service 数从 100 涨到 5000,链就长 50 倍。虽然 kube-proxy 用跳转链做了分组优化, 但本质仍是遍历。

② 规则同步风暴——这个更致命。

任何一个 Endpoint 变化(Pod 重启、扩缩容、就绪状态翻转)都要重新计算并下发整份规则集。 Service 多的时候,一次同步可能是几十万行规则的替换,耗时以秒计。后果:

Pod 变化 → kube-proxy 重算 → iptables-restore 加锁写入
                                    ↓
              这段时间内规则不一致,新连接可能被丢
                                    ↓
              大规模变更(滚动更新)时表现为「间歇性连接失败」
×iptables 模式的症状不是「慢」,是「抖」

新人常以为 iptables 模式的问题是转发慢。其实单个包的匹配开销在几千 Service 时也就几十微秒, 业务基本感知不到。

真正的症状是滚动更新期间的间歇性失败:connection refused、 偶发 5xx、部分请求超时,而且过一会儿自己就好了。查网络查不出任何问题, 因为丢包发生在规则同步的窗口里。

判断依据:看 kube-proxy 的日志与指标里的同步耗时。

kubectl logs -n kube-system <kube-proxy-pod> | grep -i "syncProxyRules"

同步耗时到秒级就该考虑换实现了。

IPVS 模式改善了什么

IPVS 是内核里专门做四层负载均衡的模块,用哈希表而不是规则链:

ipvsadm -Ln                          # 看所有虚拟服务与后端
ipvsadm -Ln -t 10.96.132.41:80       # 看某个 Service
TCP  10.96.132.41:80 rr
  -> 10.244.1.7:8080   Masq  1  12  3
  -> 10.244.2.9:8080   Masq  1  15  2
改善了没改善
查找从 O(n) 变 O(1)仍然依赖 conntrack
增删单个后端不用重算全表仍然要维护一部分 iptables 规则(做 SNAT/伪装)
支持多种调度算法(rr、lc、sh 等)转发路径长度没变

调度算法是 IPVS 独有的能力:

算法含义适合
rr轮询(默认)后端同质、请求均匀
lc最少连接长连接、请求耗时差异大
sh源地址哈希需要会话保持

conntrack:两种模式共同的软肋

iptables 和 IPVS 都要靠 conntrack 记住"这条连接被 DNAT 成了哪个后端", 回包才能还原成 ClusterIP(Service 那节讲过)。这张表是有容量的:

# 当前用量与上限
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max

# 每 CPU 的失败计数 —— 这两个在涨就是撑不住了
conntrack -S | tr ' ' '\n' | grep -E 'insert_failed|drop|early_drop'

表满的症状很特别:新连接随机失败,已有连接完全正常。 高并发短连接的业务(API 网关、爬虫、压测)最容易撞上。

# 临时缓解:调大上限(注意内存占用,每条约 300 字节)
sysctl -w net.netfilter.nf_conntrack_max=1048576

# 看谁占得多
conntrack -L | awk '{print $NF}' | sort | uniq -c | sort -rn | head
!调大 conntrack 只是缓解

根治要减少连接数或缩短连接生命周期:开启连接复用(HTTP keep-alive)、 调低 nf_conntrack_tcp_timeout_time_wait、或者干脆换成不依赖 conntrack 的实现。

dmesg 里出现 nf_conntrack: table full, dropping packet 就是确诊。

eBPF 替换:不只是更快

Cilium 的 kubeProxyReplacement 不是"把 iptables 规则换成 eBPF 规则", 而是换了转换发生的位置:

iptables / IPVS:
  应用 connect(ClusterIP) → 包进转发路径 → DNAT 改写 → conntrack 记录 → 发出

eBPF (socket 层):
  应用 connect(ClusterIP) → eBPF 在 connect() 时就把目的地址改成 Pod IP
                          → 后续所有包一路都是 Pod IP,没有 DNAT、不需要 conntrack

三个收益,从大到小:

  1. 不需要 conntrack——上面那一整类故障消失
  2. 规则查找是哈希 map,且随 Service 数增长几乎不变
  3. 单个 Endpoint 变化只更新 map 里一条,没有全量同步风暴
# Cilium 侧看 Service 表(对应 ipvsadm -Ln)
cilium-dbg service list
cilium-dbg service get <id>

# 确认替换生效
cilium-dbg status | grep -i kubeproxy
KubeProxyReplacement:   True   [eth0 10.0.12.34]

要不要换:一张判断表

情况建议
Service 少于几百、没遇到问题不动。iptables 模式最成熟、生态兼容性最好
滚动更新期间有间歇性失败,同步耗时到秒级换
conntrack 反复告警、insert_failed 在涨换(eBPF)或减少连接数
需要会话保持等调度算法IPVS
已经在用 Cilium直接开 kubeProxyReplacement
有依赖 iptables 规则的第三方组件先验证,见下面
!迁移前必须验证的四件事
  1. NodePort 与 hostPort 行为是否一致(这两个最容易出差异)
  2. externalTrafficPolicy: Local 的源 IP 保留是否仍然生效
  3. 依赖 iptables/conntrack 的第三方组件:某些 CNI 插件、监控 agent、 安全 agent 会直接读 conntrack 表,替换后它们看不到连接了
  4. 回滚路径:先在一个节点上验证,别全集群一次切

迁移是行为变更而不是性能调优。用真实流量验证过再全量。

检查点

Checkpoint单选

集群有 4000 个 Service,滚动更新期间出现间歇性 connection refused,过一会儿自己好。最可能的原因?

Checkpoint单选

eBPF 替换相比 IPVS,最本质的改善是什么?

Checkpoint单选

已有连接都正常,但新连接随机失败。先查什么?

这节课的落点

  • Service 的四代实现:userspace → iptables → IPVS → eBPF 替换
  • iptables 模式的真痛点是同步风暴(滚动更新期的间歇失败),不是转发慢
  • IPVS 把查找变成 O(1) 并带来调度算法,但仍然依赖 conntrack
  • conntrack 表满的指纹:已有连接正常、新连接随机失败,dmesg 里有 table full
  • eBPF 替换在 connect() 时改地址,整条路径不需要 conntrack,消灭一整类故障
  • 换不换看症状:没问题就别动;同步到秒级或 conntrack 反复告警才换
  • 迁移是行为变更:先验证 NodePort/hostPort、Local 策略、第三方组件,单节点先跑

延伸资料