文章

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 处理
ClusterIPeBPF 在 socket 层 (cgroup sendmsg) 把 VIP 解析成 backend IP,本机 backend 直接走,跨节点走 TC eBPF DNAT
NodePortXDP/TC eBPF 在节点网卡收包点做 DNAT 到 backend(支持 externalTrafficPolicy
LoadBalancer依赖云控制器分配 LB IP;KPR 把 LB IP 同样登记进 eBPF map,处理方式同 NodePort
ExternalNameDNS 层 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 行为
Clusterbackend 可跨节点,回包经节点(或 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.10KPR 无法启用升级内核
NodePort 端口范围冲突--service-node-port-range 与 host 端口撞调整范围
DSR + 低 MTU大包被丢(封装超 MTU)用 hybrid 或降 MTU
Maglev 表小导致不均大集群 backend 分配不均调大 tableSize

关联知识

学习时间

阶段时间备注
Service/KPR2026-07-16完成:KPR 原理、四类型、DSR、Maglev、Socket LB、externalTrafficPolicy、ClusterMesh LB