AIGC标识 为什么 Cilium 要监听 EndpointSlice 而不是 Endpoints

结论

Cilium 监听 EndpointSlice 而不是 Endpoints,本质原因是 Endpoints 在大规模 Service 后端场景下不可扩展:一个 Service 的所有后端 Pod 被塞进同一个 Endpoints 对象,任何一个 Pod 状态变化都会触发整个大对象被重写并广播给所有 watcher;而 EndpointSlice 把后端分片存储(默认每片 ≤100 个端点),单次变更只影响其中一个小分片,写入、网络、反序列化开销都按分片比例下降。再加上 Endpoints 已在 Kubernetes v1.33 被标记弃用,EndpointSlice 是现行唯一的 source of truth。

分析版本

  • Kubernetes:v1.27+(EndpointSlice 自 v1.21 GA;v1.33 起 Endpoints 仅做兼容镜像,标记 deprecated)
  • Cilium:v1.14+(默认 kube-proxy replacement 模式下,agent 通过 EndpointSlice 同步 Service 后端到 eBPF LB map)
  • 关键参数:kube-controller-manager --max-endpoints-per-slice,默认 100,最大可配 1000

详细分析

1. Cilium 为什么必须"监听"这两个资源之一

Cilium 在 kube-proxy free 模式下,直接在内核里用 eBPF 做 Service 的负载均衡(SNAT/DNAT、一致性哈希、会话保持)。它需要知道:

  • 每个 Service 的 ClusterIP/NodePort 后端是哪些 PodIP:Port
  • 这些后端当前是否就绪(ready / terminating)
  • 后端属于哪个节点、哪个拓扑域

这份数据的权威来源是 Kubernetes 控制面生成的 Endpoints / EndpointSlice 对象。每个节点上的 cilium-agent 都要 watch 它,然后把结果写入本机的 eBPF SERVICE_MAP、ENDPOINT_MAP。

也就是说,每个节点都是 watcher,节点数 × Service 数 × Pod 数决定了这个 watch 通道的总流量。选哪种资源,直接决定了大规模集群下控制面和 agent 的 CPU/网络负载。

2. Endpoints 的结构性缺陷:单对象 + 全量重写

Endpoints API 的设计是 1:1:每个带 selector 的 Service 对应一个同名 Endpoints 对象,所有后端 IP 平铺在一个 subsets[].addresses 数组里。

这带来三个连锁问题:

  • 对象体积随后端数线性膨胀。一个有 1000 个后端的 Service,Endpoints JSON 可能达到数百 KB 甚至 MB 级别。
  • 任何一个 Pod 变化 = 整个对象 PUT。Pod 重启、readiness 探针翻转、terminating,都会让 endpoint-controller 重写整个 Endpoints 对象。
  • apiserver 把完整对象广播给所有 watcher。每个 cilium-agent 收到的是整个大对象,必须全量反序列化、和本地状态做 diff。

结果是一次单个 Pod 的抖动,被放大成"etcd 一次大写入 + apiserver 一次大广播 + N 个 agent 一次大反序列化"。在几千个 Service、几万个 Pod 的集群里,这是控制面扩缩的主要瓶颈之一。历史上 Endpoints 对单 Service 后端数还有约 1000 的隐性上限,超过后行为就不正常。

3. EndpointSlice 的设计:分片 + 增量更新

EndpointSlice(discovery.k8s.io/v1)把同一个 Service 的后端拆成多个独立对象,默认每个 slice 最多装 100 个端点(--max-endpoints-per-slice)。1000 个后端就变成 10 个 slice。

关键差异在变更传播:

  • Pod-A 落在 slice-3 里,那么它就绪状态翻转时,只有 slice-3 被 UPDATE;
  • slice-1、slice-2、slice-4 …… slice-10 的 resourceVersion 完全不变,watch 事件根本不产生;
  • apiserver 广播给每个 cilium-agent 的只是那一个几 KB 的小分片。

Endpoints vs EndpointSlice 变更传播对比

从数量级看,同样一次 Pod 抖动:

维度 Endpoints EndpointSlice(10 片)
etcd 写入对象 1 个大对象(数百 KB) 1 个小分片(数 KB)
apiserver 广播体积 全量 约 1/10
agent 反序列化/diff 全量列表 单个分片
未受影响的其他 watcher 全部收到全量事件 完全收不到事件
单 Service 后端上限 约 1000 无硬上限(受 slice 数扩展)

4. 功能层面的差异

除了性能,EndpointSlice 本身表达能力也更强,Cilium 需要这些元数据来做正确的转发和策略:

  • 双栈(IPv4/IPv6):一个 Service 可以同时有 v4、v6 后端,EndpointSlice 原生支持,Endpoints 表达别扭。
  • 多端口:EndpointSlice 按端口分组,一个 slice 聚焦一组端口。
  • 拓扑与状态:每个 endpoint 携带 nodeName、zone、conditions.ready、conditions.serving、conditions.terminating 等字段。Cilium 的节点感知路由、本地/远端后端选择、优雅终止(connection draining)都依赖这些字段。
  • Terminating 后端的精确处理:Cilium 需要把正在终止的 Pod 从 LB map 里摘出去但保留存量连接,Endpoints 的粗粒度状态不够用。

5. 弃用时间线

  • Kubernetes v1.21:EndpointSlice GA。
  • v1.33(2025-04):官方博客《Continuing the transition from Endpoints to EndpointSlices》明确,Endpoints 只保留由 EndpointSlice 镜像而来的兼容路径,新控制器应当直接消费 EndpointSlice;未来版本会逐步移除对 Endpoints 的写入。
  • 这意味着即使不考虑性能,继续 watch Endpoints 在未来版本会拿到越来越不完整、最终消失的数据。Cilium 作为活跃维护的 CNI,必然跟随上游。

6. 不要混淆:CiliumEndpointSlice(CES)是另一回事

Cilium 文档里还会出现一个 CiliumEndpointSlice (CES),那是 Cilium 自己的 CRD,和 Kubernetes 的 EndpointSlice 不是一回事:

  • K8s EndpointSlice:描述 Service 的后端,给 Cilium 做 Service 负载均衡 决策用。
  • CiliumEndpointSlice (CES):把 CiliumEndpoint (CEP,描述 Pod 身份、加密、策略) 批量打包,在集群内传播,用来做 网络策略和路由 决策,目的是减少 agent 之间同步 CEP 时对 apiserver 的压力。

两者解决的是不同环节的扩展性问题,不要因为名字相似而混在一起。

posted on 2026-09-25 21:29  王景迁  阅读(8)  评论(0)    收藏  举报

导航