东西向包路径讲清 veth/TC 与 identity 之后,值班下一问往往是:「Service 的 VIP 到底在哪被改成后端?」在 kube-proxy 的 iptables/IPVS 世界里,答案是 netfilter 链与 conntrack;在 Cilium 的 kube-proxy replacement(KPR) 里,答案是 socket 级 LB + 包路径 LB + 若干 BPF service/backend map。本篇只钉这条替代路径的机制差与失败模式,不重写 k8s-network/13 的产品调参清单。
本文是「Cilium / eBPF 数据面内核」系列第 6 篇(共 16 篇)。→ 系列目录
篇目 核心内容 第 5 篇 · 东西向包路径 Pod→Pod 停顿点 第 6 篇 · kube-proxy 替代 ClusterIP/NodePort/外部流量 BPF 路径 第 7 篇 · 策略编译 NetworkPolicy → BPF
版本锚定:Cilium v1.20.0(
docs.cilium.io/en/v1.20/;源码 tag v1.20.0)。无真实集群时不粘贴伪造cilium status/cilium bpf lb list输出;命令只写语义与前置条件。
一、KPR 要解决的不变量
kube-proxy 的三种实现(userspace、iptables、IPVS)都遵守同一语义契约:
- ClusterIP:集群内把
ServiceIP:Port映射到 ready 后端。 - NodePort / LoadBalancer / externalIPs:节点外可达前端,再落到后端。
- 会话亲和、优雅终止、externalTrafficPolicy:在 DNAT/SNAT 与本地优选之间折中。
iptables 模式的代价是熟悉的:规则数随
Service×后端增长,匹配近似线性;全量
iptables-restore
与全局锁在大规模下变成控制面税。k8s-network/09
与 eBPF
vs netfilter 已写过这条争论。Cilium KPR
的工程命题不是「再优化一条 iptables」,而是:用 BPF
map
的常数时间查找与原子更新,替代链遍历与全量刷新,同时尽量保持
K8s Service 语义。
v1.20 文档(Kube-Proxy Free /
kubeproxy-free)把完整替换钉成:kubeProxyReplacement=true
时,ClusterIP、NodePort、LoadBalancer、externalIPs 与
hostPort 由 eBPF 路径承担;Helm 默认
kubeProxyReplacement=false 时,通常只保留集群内
ClusterIP 的包路径 LB,其余仍可依赖
kube-proxy。混部不是「半开半关随便试」,而是有明确的冲突面(见第七节)。
二、两条 LB 平面:socket-LB 与 per-packet
KPR 不是单一 hook。文档与数据面分工可收成两层:
| 平面 | 典型挂载 | 服务的流量 | 失败时表象 |
|---|---|---|---|
| Socket-LB | cgroup connect / sendmsg /
recvmsg / getpeername 等 |
本机进程(含 Pod)连 ClusterIP/NodePort | 应用看到偶发连错后端、getpeername 暴露真实
Pod IP |
| Per-packet LB | TC /(可选)XDP | 跨节点、外部入站、未走 socket 改写的包 | VIP 黑洞、NodePort 不通、DSR 回包被云厂商反欺骗丢掉 |
flowchart TD
app["App connect ClusterIP"] --> sock["cgroup socket-LB\nrewrite destination"]
sock --> backend["Local or remote backend"]
ext["External to NodePort"] --> xdp["Optional XDP"]
xdp --> tc["TC ingress on device"]
tc --> svcmap["LB service/backend maps"]
svcmap --> snat["SNAT mode: extra hop"]
svcmap --> dsr["DSR mode: reply direct"]
Socket-LB 的意义:在
connect()
时刻就把目标改成后端,避免「每个包都做
DNAT」;同节点东西向甚至可以与 sockmap
短路路径协作(挂载细节见 第 3
篇)。内核下限在官方 kubeproxy-free
章有明确要求(历史门槛约 4.19.57 / 5.1.16 /
5.2.0+;更高内核有额外优化)。版本边界写进运维基线,比「装了
Cilium 就算 KPR」更准确。
Per-packet 路径负责:外部
NodePort、从网卡进来尚未进入本机 socket 层的流量,以及
socket-LB 覆盖不到的协议边角。Service 查找键语义对齐
LB4_SERVICES / LB6_SERVICES 一类
map(路径在 bpf/ 与
pkg/maps/lbmap/ @ v1.20.0):前端地址 + 端口 +
协议 → service 元数据 → backend 选择。map
容量默认量级可调(如
--bpf-lb-map-max);打满时新条目无法写入,表象是「新建
Service 不生效」或间歇黑洞——排障应先看 map 压力,再怀疑
DNS。
三、ClusterIP:集群内如何落到后端
3.1 东西向 ClusterIP
典型路径(与第 5 篇衔接):
- Pod 内进程
connect(ClusterIP, port)。 - Socket-LB 查 service map,选出 backend(默认随机;可配置)。
- 若 backend 在本节点:本地投递 / redirect,不必出网卡。
- 若 backend 在远端:包按集群路由模式(native / tunnel)发往远端节点;对端再做策略与投递。
与 iptables 的差:没有
KUBE-SERVICES
链上的逐条规则匹配;后端集合变更是 map
增量更新,而不是整张表 rewrite。与 IPVS
的差:IPVS 仍坐在 netfilter 附近,依赖内核 IPVS
调度器与 conntrack 协作;Cilium 把「调度结果」放进 BPF
map,并可用 Maglev 表做南北向一致性哈希(见第五节)。
3.2 会话亲和与优雅终止
v1.20 KPR 状态面可报告 Session Affinity、Graceful Termination 等开关。优雅终止依赖 EndpointSlice 的 terminating 条件(K8s 1.20+ 与对应 feature gate 叙事)。工程含义:后端进入 terminating 时,新连接应停止打到该后端,已有连接可按宽限期排空。失败模式是「滚动更新时偶发 502」:若 map 未及时摘除 terminating 后端,或客户端连接池复用了旧五元组,表现会像应用故障。归因顺序应是:EndpointSlice 条件 → agent 是否编程 backend → CT/socket 是否仍钉死旧后端。
四、NodePort 与外部流量:SNAT、DSR、Hybrid
4.1 默认 SNAT
默认
loadBalancer.mode=snat:外部请求打到节点 A 的
NodePort,若选中的后端在节点 B,则 A 代发并做
SNAT;回包必须回到 A 做反向转换再回客户端。代价清晰:
- 入口节点成为回程必经点(带宽与 CPU)。
- 后端看到的源 IP 是节点地址,不是客户端真实源 IP(策略若按客户端 IP 匹配会失效)。
优点是不强制改 MTU、不依赖云厂商放行「源 IP = VIP」的回包。
4.2 DSR
loadBalancer.mode=dsr:回包由后端节点直接回客户端,源地址用
Service IP/port。客户端源 IP 可保留到后端。代价转移到:
- Dispatch:如何把 Service IP/port 信息带到后端。v1.20 文档列出 IPv4 option / IPv6 dest option、Geneve option、IPIP 等;native 与 tunnel 组合不是任意可配,需按官方矩阵核对。
- MTU:部分 dispatch 需要降低对外 MTU;TCP 可只在 SYN 编码以减轻后续开销。
- 云反欺骗:AWS 等源/目的检查会丢 DSR 回包,必须关检查或换 SNAT/Hybrid。
Hybrid 模式对 TCP 走 DSR、对 UDP 走 SNAT,是「要源 IP
又不想全面砍 MTU」的折中。Annotation-based 模式允许按
Service 注解覆盖全局默认(需
bpf.lbModeAnnotation 与显式
loadBalancer.dsrDispatch)。
4.3 externalTrafficPolicy
| 策略 | KPR 语义要点 | 常见踩坑 |
|---|---|---|
Cluster(默认) |
可跨节点转发;源 IP 是否保留取决于 SNAT/DSR | 以为永远能看到客户端 IP |
Local |
优先/仅本地后端;无本地后端时外部流量行为与健康检查联动 | 节点被 LB 打满但本地无 Pod → 黑洞;集群内访问语义与外部不同 |
internalTrafficPolicy
把类似语义映射到集群内流量。排障时必须先问:失败的是集群内
curl ClusterIP,还是云 LB → NodePort? 两条路径的
map 键与 NAT 模式可以不同。
五、Maglev:南北向一致性哈希,不是东西向银弹
Cilium 实现 Maglev
变体作为后端选择算法(loadBalancer.algorithm=maglev)。奠基叙述来自
Eisenbud 等,NSDI 2016 Maglev: A Fast and Reliable
Software Network Load Balancer:用大查找表 \(M\)(素数)近似一致性哈希,后端变更时尽量限制无关流的重映射比例(论文与
Cilium 文档都强调「约
1%」量级的工程目标;表大小应显著大于最大后端数 \(N\),实践上常取 \(M \gg 100N\))。
v1.20 文档钉死两条常被误读的边界:
- Maglev 主要用于外部(N-S)流量。集群内(E-W)socket 级分配在 connect 时完成,不走 Maglev 表。
- 全集群 agent 必须共享同一
maglev.hashSeed,否则「同一五元组在不同入口节点选到不同后端」,会话粘滞与排障都会崩。
内存税:每个 Service 一份查找表;相对 random
更吃节点内存。开放配置项包括
maglev.tableSize(默认文档叙述为 16381
等素数档)。把 Maglev 当成「所有 Service
延迟银弹」是错的;它优化的是入口一致性与故障转移时的扰动上界,不是东向
socket-LB 的路径长度。
六、相对 iptables / IPVS 的机制对照
| 维度 | iptables kube-proxy | IPVS kube-proxy | Cilium KPR (v1.20) |
|---|---|---|---|
| 查找 | 链上近似 O(规则) | 哈希到虚拟服务 | BPF map O(1) |
| 更新 | 常整表 restore | 增量更好但仍经 netfilter | 原子 map 更新 |
| Conntrack | 内核 nf_conntrack | 同左 + IPVS | BPF CT map(per-CPU 叙事) |
| 一致性哈希 | 弱(statistic 随机) | 有调度算法,非 Maglev 表 | Maglev(N-S) |
| 与策略耦合 | 另一套 iptables | 另一套 | 同数据面 identity/policy map |
争论(有文献与工程两侧):一边是「可编程数据面用常数查找压过 netfilter 线性税」(Cilium 设计叙述;XDP/eBPF 路径见 Høiland-Jørgensen 等 CoNEXT 2018 The eXpress Data Path);另一边是「完全旁路 netfilter 后,运维失去统一的 iptables 观测面,故障语义更陡」。站内 eBPF vs netfilter 已展开;本篇只强调:KPR 赢在更新路径与查找复杂度,不自动消除错误配置与 map 容量事故。
七、失败模式清单(按层归因)
| 观察 | 更可能落点 | 不要先怀疑 |
|---|---|---|
| 仅 ClusterIP 不通,NodePort 正常 | socket-LB / cgroup 挂载、hostNetwork 例外 | 应用代码 |
| 外部 LB 健康检查失败、本地无 endpoints | externalTrafficPolicy=Local |
DNS |
| 跨节点 NodePort 单向通 | SNAT 回程、DSR 被丢、MTU | 证书 |
| 滚动更新偶发连旧 Pod | Graceful / EndpointSlice terminating 未编程 | 内核 bug |
| 新建 Service 全员不通 | lb map 满、agent 未 sync API | 单 Pod 日志 |
| 与 Istio sidecar「有时绕过」 | socket-LB 与 sidecar 抢改写;文档建议
socketLB.hostNamespaceOnly 一类约束 |
随机丢包 |
| 迁移中双 NAT | 同节点 kube-proxy 与 KPR 同时做 Service | 「Cilium 坏了」 |
共存与迁移:官方 Per-node configuration
给出用 CiliumNodeConfig +
节点标签逐步迁移、并把 kube-proxy DaemonSet
亲和到未迁移节点的流程。硬条件:必须配置
k8sServiceHost /
k8sServicePort,否则去掉 kube-proxy 后 agent
自己都连不上 apiserver。禁止在未
drain/未分节点的情况下「原地同时跑两套完整 Service
实现」——NAT 与 conntrack 双写是经典事故源。
无集群时可用的自检命令语义(需可调度集群与 Cilium agent):
# 语义:确认 KPR 是否真正 True,以及 SNAT/DSR、算法、各 Service 类型开关
cilium status
cilium status --verbose # 关注 KubeProxyReplacement Details
# 语义:查看 service/backend 是否已编程(输出勿伪造)
# cilium bpf lb list八、谱系、争论与开放问题
谱系:K8s Service 语义 → kube-proxy iptables/IPVS 实现 → eBPF/XDP 可编程数据面(CoNEXT 2018 XDP)→ Cilium 把 LB 状态搬进 BPF map,并用 Maglev(NSDI 2016)填南北向一致性哈希空缺。Cilium 不是「发明了 Service」,而是改了执行位置与更新代价模型。
争论:
- 完全 KPR vs 混部:运维安全感 vs 双路径复杂度;混部只适合受控迁移窗口。
- DSR vs SNAT:源 IP 与回程跳数 vs 云网络兼容性;没有无条件最优解。
- Maglev 表内存 vs random:一致性与故障扰动 vs 节点内存;大规模 Service 数时要显式容量规划。
开放问题:
- map 压力与节点规模的可观测闭环:何时把 lb map 占用提升为与「API Server 延迟」同级的 SLO 信号?(系列第 12–13 篇方向)
- 与用户态网格抢 socket 改写:sidecar / Ambient 并存时,socket-LB 的默认开启面如何做成可证明的「不会双 DNAT」?(边界见 第 10 篇)
九、hostPort、healthz 与「半开 KPR」
完整 KPR 还接管 hostPort:容器声明的主机端口映射不必再依赖 portmap CNI 插件。失败表象常是「Pod Running 但主机端口不通」——此时应查 agent 是否把 hostPort 编进 LB 相关 map,而不是先怪应用 listen 地址。与 NodePort 共用端口空间时,冲突诊断要同时看 Service 与 Pod hostPort,避免两边各查各的。
健康检查面:迁移文档要求配置
kube-proxy-replacement-healthz-bind-address
一类地址,让负载均衡器或节点探针仍能打到「原 kube-proxy
healthz 语义」的替代端点。漏配时,云 LB 可能把节点标
unhealthy,而集群内 ClusterIP
一切正常——典型的「内外分裂」。
半开配置(kubeProxyReplacement=false
但单独打开 socketLB / nodePort /
externalIPs /
hostPort)只适合受控混部:每一项开启都要回答「kube-proxy
是否仍在写同一前端」。两边都写同一 NodePort
时,数据包走哪条路径取决于 hook 顺序与 conntrack
状态,排障几乎不可复现。迁移窗口之外,目标态应是
单一权威:要么完整 KPR,要么完整
kube-proxy。
XDP 加速(文档中的 NodePort XDP)把部分南北向决策前移到驱动更早的点;它不改变 Service 语义,但改变「丢包发生在 XDP 还是 TC」的观测位置。未开 XDP 时用 XDP 术语解释故障,会找错层。
十、本篇边界
不写:每个云厂商 LB 注解百科、hostPort 全参数表、伪造压测
pps。
下一篇把 NetworkPolicy / CiliumNetworkPolicy 如何编译进
policy map、以及默认拒绝窗口与 identity 漂移的交互钉死。
一句话收束:KPR 把 Service 从 netfilter 链变成 BPF map 上的状态机;ClusterIP 多走 socket-LB,NodePort/外部流量在 SNAT/DSR/Hybrid 之间买源 IP 与兼容性,失败时先分清平面再查 map。
参考资料
规范 / 官方文档(A)
- Cilium v1.20 — Kubernetes Without kube-proxy — KPR 范围、Socket-LB、SNAT/DSR/Hybrid、Maglev、externalTrafficPolicy
- Cilium v1.20 — Per-node
configuration — KPR 滚动迁移与
CiliumNodeConfig - Kubernetes — Service / EndpointSlice /
externalTrafficPolicy语义(官方概念文档)
源码(A)
cilium/ciliumtag v1.20.0:bpf/(LB/CT 相关)、pkg/maps/lbmap/、pkg/datapath/
论文(A)
- Eisenbud et al., Maglev: A Fast and Reliable Software Network Load Balancer, NSDI 2016 — 一致性哈希表 \(M\) 与后端变更扰动
- Høiland-Jørgensen et al., The eXpress Data Path: Fast Programmable Packet Processing in the Operating System Kernel, CoNEXT 2018 — XDP 可编程数据面范式
站内
→ 上一篇:东西向包路径 · 系列目录 · 下一篇:策略编译
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【Cilium / eBPF】东西向包路径:Pod→Pod 停顿点与 netfilter 旁路
沿 Cilium v1.20 Life of a Packet 钉清同节点与跨节点东西向停顿点:Endpoint Policy、Service、socket 加速、加密与 L7 重定向;说明与 netfilter/kube-proxy 旁路关系,并为 kube-proxy 替代篇铺路。
【Cilium / eBPF】数据面全景:五轴坐标系与 16 篇路线
相对 k8s-network/13、ebpf/ 与 Envoy/HAProxy 排除树,钉清 Cilium eBPF 数据面内核缺口;定义 Identity、BPF 挂载、Map 状态、包路径、策略与观测五条坐标系,给出 16 篇阅读路线。
【Cilium / eBPF】排障坐标系:丢包、policy deny、identity 漂移、Service 黑洞与加密失败
按五轴映射 Cilium 东西向故障:通用丢包、Policy denied、identity 漂移窗口、Service/ClusterIP 黑洞、透明加密失败;每轴绑定 status/Hubble/map 层,一次否证一轴。
【Cilium / eBPF】对照替代路径:Calico、kube-proxy、Envoy sidecar 与 Ambient
从机制排除而非跑分出发,对照 Cilium eBPF 数据面与 Calico、kube-proxy iptables/IPVS、Envoy sidecar、Istio Ambient;链接 k8s-network/12–13、envoy/16、haproxy/14,不做延迟排行榜。