Cilium Service 与 KPR 深入
Cilium Service 与 KPR 深入
KPR 是什么
KPR (kube-proxy replacement) = 用 eBPF 程序接管 kube-proxy 的全部职责(Service 转发、NodePort、负载均衡、NAT),不再需要 kube-proxy 进程,也不写 iptables/nftables 规则。
kube-proxy (iptables 模式):
Service VIP 10.96.0.10
→ iptables 规则链(每 Service 一条,O(n) 查表)
→ DNAT 到随机 Endpoint
→ conntrack 记录
问题:Service 上千条时规则爆炸、更新慢、每包都走 Netfilter
KPR (eBPF):
Service VIP 10.96.0.10
→ eBPF map (cilium_lb4_services) O(1) 查到 backend
→ 直接改写目的 IP(或 Socket LB 连本地 backend 免协议栈)
→ 自管连接跟踪 (cilium_ct4),不经 Netfilter
性能本质见 ../linux/ebpf/eBPF 性能模型:省掉”每条包走 Netfilter 规则遍历 + conntrack 查表”,且跨节点转发可绕过 conntrack(见 DSR)。
四类型 Service 在 KPR 下的实现
| Service 类型 | KPR 处理 |
|---|---|
| ClusterIP | eBPF 在 socket 层 (cgroup sendmsg) 把 VIP 解析成 backend IP,本机 backend 直接走,跨节点走 TC eBPF DNAT |
| NodePort | XDP/TC eBPF 在节点网卡收包点做 DNAT 到 backend(支持 externalTrafficPolicy) |
| LoadBalancer | 依赖云控制器分配 LB IP;KPR 把 LB IP 同样登记进 eBPF map,处理方式同 NodePort |
| ExternalName | DNS 层 CNAME,不经过数据面(CoreDNS 处理) |
DSR(Direct Server Return,性能关键)
默认 NAT 模式下,回包要从 backend 经节点再做反向 NAT,节点成瓶颈。DSR 让 backend 直接把回包发给客户端(源 IP 仍是 Service/VIP):
NAT 模式(默认):
Client → Node (DNAT) → Backend → Node (reverse NAT) → Client
(回包过节点两次,节点 CPU 压力大)
DSR 模式:
Client → Node (DNAT, 加一层封装记住 VIP) → Backend → Client
(backend 直接回,绕过节点;用 Geneve/DLB 选项携带原 VIP)
开启(推荐大流量 NodePort/LB 场景):
bpf:
lbMode: dsr # 或 "snat"(默认)、"hybrid"
# hybrid:本地点对点走 DSR,跨远端用 SNAT(兼顾性能与 MTU)
- DSR 有 MTU 代价(要塞原始 VIP 信息),所以 hybrid 更常用
lbMode: dr(direct routing) 另一种变体,适合 BGP 环境
Maglev 一致性哈希(连接稳定性)
KPR 默认用 Maglev 哈希选 backend,而不是简单轮询:
- 每个 Service 维护一张 Maglev 查找表(基于 backend 列表生成)
- 同一 (srcIP, dstIP, …) 五元组尽量命中同一 backend
- backend 扩缩容时,只有 1/N 的连接会被重新映射(对比普通哈希大量乱跳)
bpf:
lbAlgorithm: maglev # 默认;也可 "random"
# Maglev 表大小可调(越大越均衡但占内存):
# maglev: { tableSize: 65537 }
这对 长连接 / 有状态服务 / 连接亲和 很重要:kube-proxy iptables 用的是随机模式(
-m statistic --mode random),后端一变连接就散。
Socket LB(连本机 backend 免走协议栈)
KPR 最强优化之一:当客户端 Pod 和某个 backend 在同一节点,eBPF 在 socket 系统调用层(cgroup sendmsg / sockops)直接把连接指向本地 backend,包根本不进网络协议栈:
Pod-A 连 Service VIP
→ cgroup eBPF 在 sendmsg() 时查 map
→ 发现本机有 backend-B
→ 直接 socket 重定向(loopback 语义,零拷贝、零 conntrack)
效果:同节点 Service 调用延迟接近本地 IPC,且不占 conntrack 表。开启(bpf.socketLB 默认在 1.16+ 启用)。
externalTrafficPolicy 的 KPR 语义
| 值 | KPR 行为 |
|---|---|
| Cluster | backend 可跨节点,回包经节点(或 DSR 直回) |
| Local | 只在有本地 backend 的节点上响应 NodePort/LB;无本地 backend 的节点直接丢弃,保证客户端源 IP 不变、且流量不跨节点 |
Local模式下如果节点没有对应 backend,该节点的 NodePort 不通——排障时常被误判为故障。
反向 NAT 与连接跟踪
KPR 自己维护 conntrack(cilium_ct4_* map),不依赖 Netfilter:
- 正向:VIP→backend DNAT 时记录 (orig, reply) 映射
- 反向:backend 回包时按 map 还原成 VIP 源
- DSR 模式下反向 NAT 在 backend 处完成(所以回包不过节点)
查反向 NAT:
cilium bpf nat list # 看 SNAT/DNAT 记录
cilium bpf ct list # 看连接跟踪(替代 conntrack -L)
ClusterMesh 的全局负载均衡
当多个集群通过 ClusterMesh 互联,Service 变成全局:
Cluster A 的 Service "foo" + Cluster B 的 Service "foo"
→ Global Service(annotation: io.cilium/global-service: "true")
→ 任意集群访问 foo,eBPF 从全局 backend 池选(可配 affinity 优先本地集群)
- 跨集群流量走 ClusterMesh 的隧道(基于节点间 WireGuard/IPsec 或明文)
- 故障转移:某集群 backend 全挂,流量自动导向其他集群
开启 KPR 的注意事项
# 必须确认:
kubeProxyReplacement: true
# 且安装时 kube-proxy 要被禁用(kubeadm 加 --skip-phases=addon/kube-proxy,或 Helm 设
# kubeProxyReplacement 后删 kube-proxy DaemonSet)
常见坑:
| 坑 | 表现 | 解决 |
|---|---|---|
| kube-proxy 没禁 | 双份转发,偶发 RST | 删 kube-proxy DS |
| 内核 < 5.10 | KPR 无法启用 | 升级内核 |
| NodePort 端口范围冲突 | --service-node-port-range 与 host 端口撞 | 调整范围 |
| DSR + 低 MTU | 大包被丢(封装超 MTU) | 用 hybrid 或降 MTU |
| Maglev 表小导致不均 | 大集群 backend 分配不均 | 调大 tableSize |
关联知识
- Cilium 知识总览 — 入口
- Cilium 架构与数据面组件 — KPR 加载的 eBPF 程序(XDP/TC/socket)
- ../linux/ebpf/eBPF 性能模型 — 为什么 KPR 比 iptables 快
- ../k8s/特性详解/nftables kube-proxy 详解 — 被替代的 kube-proxy 实现
- ../k8s/网络架构/K8s Service 模型与转发实现 — Service 模型底层
- Cilium Hubble 可观测性与运维排障 — 怎么验证 KPR 生效
学习时间
| 阶段 | 时间 | 备注 |
|---|---|---|
| Service/KPR | 2026-07-16 | 完成:KPR 原理、四类型、DSR、Maglev、Socket LB、externalTrafficPolicy、ClusterMesh LB |