← 返回文章目录

eBPF/Cilium 如何接管 Kubernetes Service 数据路径

从 ClusterIP 到后端 Pod,拆开 Cilium 的控制面、BPF map、socket LB 与 tc/XDP 数据路径。

KubernetesCiliumeBPFServicekube-proxy
展开本文目录
  1. 控制面把 Service 变成 BPF 状态
  2. 一次连接可能很早就被改写
  3. Cilium 替换 kube-proxy 后改变了什么
  4. 迁移时别直接拔掉 kube-proxy
  5. 一条实用的排查链路
  6. 参考资料

在 Kubernetes 里创建一个 Service 后,会得到一个 ClusterIP。很多人第一次排查网络时,会去节点上找这个 IP 绑在哪块网卡上,结果什么也找不到。

这很正常。ClusterIP 通常是一个虚拟地址,它代表一组会变化的后端,而不是某台机器真实持有的地址。客户端访问 ClusterIP 时,节点上的数据路径会依据 Cilium agent 从 EndpointSlice 同步到 BPF map 的状态选择后端,再把发往虚拟地址的连接转到真实 Pod。

传统环境常由 kube-proxy 把 Service 和后端变化转成节点上的转发表,具体实现可能使用 iptables、nftables 等模式。Cilium 开启 eBPF kube-proxy replacement 后,可以由 Cilium 负责 ClusterIP、NodePort、LoadBalancer、externalIPs 以及部分 hostPort 的 Service 转发。

“接管”说的是这段 L4 Service 数据路径。Kubernetes Service、EndpointSlice 和 API Server 仍然存在,Cilium 也仍要监听这些对象。Ingress、Gateway、L7 路由和 service mesh 可能继续依赖 Envoy 等用户态代理,Cilium 只是可以把代理放在节点级别并用 eBPF 做重定向,不能简单概括成“所有网络逻辑都进了内核”。

控制面把 Service 变成 BPF 状态

假设有一个 Service:

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080

控制器会根据 selector 维护对应的 EndpointSlice。Cilium agent 监听 Service 和 EndpointSlice 的变化,再把前端地址、端口和后端地址写入相应的 BPF map;流量经过时,数据路径还会使用连接跟踪、反向 NAT 等 map 保存必要状态。可以把 map 理解成内核与用户态共享的键值存储:agent 负责同步控制面信息,挂在网络钩子上的 BPF 程序负责查询并更新流量状态。

这是一条很重要的边界。eBPF 没有取代 Kubernetes 控制面,也不会自己猜出后端。后端 Pod Ready、扩缩容或终止时,状态仍然要经过 Kubernetes API,再由 Cilium 同步到节点。

理解这些 map 时,可以先抓住三类关系。前端表回答“这个 Service IP 和端口对应哪个服务”;后端表保存可选的 Pod IP 和端口;连接跟踪、反向 NAT 等状态则保证同一条连接的后续数据包和回包能得到一致处理。具体 map 名称与字段会随版本和功能变化,不必先背命令。排障时更重要的是核对:控制面声明的后端数量,是否与故障节点实际加载的后端数量一致。

当 Service 不通时,排查也应分两层:

  1. Kubernetes 层看到的 Service、EndpointSlice 是否正确;
  2. Cilium 是否把相同的信息编程到了本机数据路径。

只看 kubectl get svc 不够,只看 BPF map 也不够。

一次连接可能很早就被改写

Cilium 的 Service 转发并不只有一个挂载点。具体走哪条路径,取决于版本、内核能力、安装参数、流量方向和工作负载类型。

对常见的 TCP 连接,socket-level load balancer 可以挂在 cgroup 的 socket 钩子上。当进程调用 connect() 连接 ClusterIP 时,BPF 程序就查询 Service map 并选择后端。应用以为自己连接的是虚拟 IP,内核里的 socket 实际已经指向后端 Pod。把服务选择提前到 socket 层,可以减少逐包路径重复查询 Service 转发表的工作;具体回程和 NAT 仍取决于所用数据路径。

简化后的路径如下:

应用 connect(Service IP)
  → cgroup socket BPF
  → 查询 Service / backend map
  → 选择后端 Pod IP
  → 建立连接

socket LB 并不适合所有场景。有些 sidecar 需要先看到原始 ClusterIP;KubeVirt、Kata Containers、gVisor 一类环境的网络 namespace 和 socket 路径也可能不同。Cilium 可以让 Pod namespace 绕过 socket 改写,回退到 veth 上的 tc BPF 程序做逐包负载均衡。

在 tc 路径里,包已经存在。BPF 程序在虚拟网卡或主机网卡的 ingress/egress 钩子上读取五元组,查询 map,执行后端选择和必要的地址转换,再交给路由系统。后端在另一节点时,后续仍要经过 Cilium 配置的隧道或原生路由。换成 eBPF 并不会让跨节点网络凭空消失。

XDP 是更早的接收路径,可用于某些 NodePort 或 LoadBalancer 的转发加速。它是否启用、网卡驱动是否支持、流量是否符合使用条件,都需要单独验证。不能看到 Cilium 就默认所有包都走 XDP,也不能笼统地说 Cilium 一定绕过 Netfilter。共存模式、回退路径、SNAT 配置和外部组件都可能继续使用 Netfilter。

Cilium 替换 kube-proxy 后改变了什么

最明显的变化不是 YAML,而是运维观察点。

过去排查 Service,工程师习惯查 kube-proxy 日志、iptables/nftables 规则和 conntrack。换成 Cilium 后,Service 状态主要落在 BPF map,流量事件可以通过 Hubble 或 Cilium 的监控命令观察。原来的 Linux 网络知识依旧有用,但还要理解 cgroup、tc/XDP 钩子、BPF map,以及 agent 如何从 Kubernetes 同步状态。

数据路径更可编程,也更容易把身份、策略和可观测信息放进同一套系统。它并不自动保证更快。性能取决于内核、网卡、流量模型、map 大小、负载均衡算法和配置,应该用自己的请求分布做压测。

故障形态也会变化。例如:

  • Service 与 EndpointSlice 正常,但某些节点的 BPF 状态没有及时同步;
  • Pod 间直连正常,访问 ClusterIP 失败,问题落在 Service 转换层;
  • 普通 Pod 正常,带 sidecar 或特殊运行时的工作负载异常;
  • 内部流量正常,NodePort 或 LoadBalancer 异常,问题落在外部流量策略、SNAT/DSR 或设备选择;
  • 升级后新连接正常,旧连接中断,原因是新旧 NAT/连接状态彼此并不共享。

这些现象比“eBPF 比 iptables 快”更值得记住。

迁移时别直接拔掉 kube-proxy

Cilium 官方文档明确提醒,在已有连接的集群里切换 kube-proxy 与 eBPF 替代方案,连接可能中断。两套数据路径各自维护 NAT 和连接状态,不会自动交接。先删除 kube-proxy、再慢慢调 Cilium,也会让 Service 流量出现空窗。

更稳妥的做法是先确认四件事。

其一,核对当前 Kubernetes、Cilium、Linux 内核和容器运行时的兼容矩阵,尤其是 cgroup v2、socket LB 及特殊运行时要求。

其二,建立测试清单。ClusterIP、NodePort、LoadBalancer、外部源地址、externalTrafficPolicyinternalTrafficPolicy、双栈、hostNetwork、DNS、sidecar 和网络策略都应覆盖。

其三,保存迁移前的流量基线,至少包括连接错误率、延迟、丢包、conntrack 压力和 Service 后端分布。没有基线,迁移后很难区分真实回归与观察方式变化。

其四,准备可执行的回退方案。回退不是把 Helm 参数改回去这么简单,还要考虑现有连接、节点逐步切换顺序和控制面 API 的可达性。

一条实用的排查链路

遇到 “Service 不通” 时,可以按下面的顺序缩小范围:

Service selector
  → EndpointSlice 中是否有 Ready 后端
  → Cilium agent 是否健康并连得上 API Server
  → 本节点 Service / backend BPF 状态是否存在
  → 流量在 socket、tc 或 XDP 的哪一层被处理
  → 后端路由与策略是否允许
  → 回包是否按预期返回

kubectl get endpointslice 用来确认控制面事实;Cilium CLI 和 agent 内的 BPF 查看命令用来核对节点状态;Hubble 适合观察流量被转发、拒绝或丢弃的位置。具体命令会随版本调整,生产操作应以所用版本的官方文档为准。

测试时也不要只在单个节点上访问一次。至少要覆盖同节点后端、跨节点后端、后端扩缩容以及节点重启,才能看出状态同步和回程路径有没有问题。

理解 Cilium 的关键,不是背下一组 Helm 参数,而是把链路分清楚:Kubernetes 负责声明 Service 和后端,Cilium agent 把声明变成 BPF 状态,内核钩子在不同位置处理连接和数据包,底层路由再把包送到目标。哪一层的事实不一致,就去查那一层。

参考资料