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 模式的问题是转发慢。其实单个包的匹配开销在几千 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
根治要减少连接数或缩短连接生命周期:开启连接复用(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
三个收益,从大到小:
- 不需要 conntrack——上面那一整类故障消失
- 规则查找是哈希 map,且随 Service 数增长几乎不变
- 单个 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 规则的第三方组件 | 先验证,见下面 |
- NodePort 与 hostPort 行为是否一致(这两个最容易出差异)
externalTrafficPolicy: Local的源 IP 保留是否仍然生效- 依赖 iptables/conntrack 的第三方组件:某些 CNI 插件、监控 agent、 安全 agent 会直接读 conntrack 表,替换后它们看不到连接了
- 回滚路径:先在一个节点上验证,别全集群一次切
迁移是行为变更而不是性能调优。用真实流量验证过再全量。
检查点
集群有 4000 个 Service,滚动更新期间出现间歇性 connection refused,过一会儿自己好。最可能的原因?
eBPF 替换相比 IPVS,最本质的改善是什么?
已有连接都正常,但新连接随机失败。先查什么?
这节课的落点
- Service 的四代实现:userspace → iptables → IPVS → eBPF 替换
- iptables 模式的真痛点是同步风暴(滚动更新期的间歇失败),不是转发慢
- IPVS 把查找变成 O(1) 并带来调度算法,但仍然依赖 conntrack
- conntrack 表满的指纹:已有连接正常、新连接随机失败,
dmesg里有 table full - eBPF 替换在
connect()时改地址,整条路径不需要 conntrack,消灭一整类故障 - 换不换看症状:没问题就别动;同步到秒级或 conntrack 反复告警才换
- 迁移是行为变更:先验证 NodePort/hostPort、
Local策略、第三方组件,单节点先跑
延伸资料
- The Kubernetes Networking Guide ↗
- k8s-in-action
network/cilium/README.md