Cilium: 原生路由 (Native Routing) 模式下的 eBPF 数据路径

​ 随着云原生生态的发展,各种新技术、新项目层出不穷,而在容器网络领域,以 eBPF 为核心的 Cilium 已成为性能最强的容器网络方案之一,它显著提升了网络性能与可观测性。然而,eBPF 在带来强大的性能的同时,也带来了数据路径的复杂性。理解其内部工作原理对于故障排查至关重要。因此,本文旨在详细剖析 Cilium 原生路由模式的 eBPF 数据路径,阐明其报文转发流程,为生产实践提供参考。

一、前置信息

1. 什么是 eBPF

​ eBPF 本质上是一个运行在内核空间的轻量级沙盒虚拟机。它允许开发者使用 C 语言等编写程序,编译为 eBPF 字节码,并在通过内核的严格验证后,动态加载至内核执行。这种设计巧妙地平衡了灵活性、性能与安全性,使得在不修改内核源码或重启系统的前提下,动态扩展内核功能、实时调整系统行为成为可能。

2. Cilium 场景下,eBPF 运行在哪些地方

image-cilium-navite-route-hostrouting-datapath

上面是一个简化 Linux 网络路径图,上面绿色部分,就是 Cilium 会挂载 eBPF 的挂载点。

XDP (eXpress Data Path):

XDP 是在报文还没有进入网络协议栈之前就介入的 Hook,这样能使得 XDP 性能超强,但是同时它也有一个缺点,就是它只能运行在物理网卡上。

XDP 主要有以下三种工作模式:

如果 Cilium 启用了 XDP 加速,那么 Cilium 会自动按性能由高到低进行加载,优先使用性能较高的模式。

XDP 主要应用场景包括:

TC Ingress

TC Ingress 是属于网络协议栈内的 Hook,所以所有网络设备,都可以使用此 Hook 。

数据包从网卡驱动程序处理完毕,内核为其成功分配了核心的 sk_buff 结构后,便正式进入了内核协议栈,此时就会触发 TC Ingress 钩子。

Cilium 在这里可以对数据包进行高效处理,例如执行网络策略、实现负载均衡、将流量重定向到其他网络设备或丢弃非法报文。这是 Cilium 中最核心、最常用的挂载点之一。

TC Ingress 的主要返回值有三个:

TC Egress

当一个数据包已经走完了整个内核协议栈(包括路由决策、Netfilter 等所有处理),在即将被交给网卡驱动程序、发送到物理网络之前的最后一刻,就会触发 TC Egress 钩子。

这里是数据包离开节点的最后一道“关卡”,也是执行出口流量控制的理想位置。Cilium 在这里可以实现出口网络策略 (Egress Policy)、执行 SNAT (源地址转换),或者对数据包进行服务质量(QoS)标记。

TC Egress 的返回值与 TC Ingress 完全相同,其主要含义为:

二、Cilium 到底优化了什么

​ 在传统的 HostGW 模式中,或者其他没有开启 eBPF 特性的 Cilium Native Routing 模式中,流量从外部进入 Pod,或者 Pod 从内部把流量发出去,都会需要经过两层网络协议栈,一层是宿主机的,一层是 Pod 自身的。

image-20250614122427839

​ 如果上图所示,流量从外部进入后,会先进入宿主机的 Netfilter 框架,然后由内核根据路由表,转给 cni0 网桥,或者 pod 的 veth-pair 网卡,最后到达 Pod,Pod 再自身还会经过自己的网络协议栈。这样就造成了资源的浪费,因为在报文的转发流程上,Netfilter 是非常消耗资源的。

Cilium 利用 eBPF 做了什么路径优化呢?

image-20250614123007383

如上图所示:

通过该路径,Cilium 完全绕过了宿主机的 Netfilter 框架,显著提升了 Pod 网络的报文转发效率。

报文是如何被 eBPF 程序接管的?

image-20250615130952970

上面中 Cilium 实现跳过宿主机的 Netfilter ,核心是利用了图片上的两个 eBPF 程序。

特殊 eBPF 程序:

图片上还有一个 Egress Hook,相对来说就不是那么重要了,也不会对 Cilium 的 eBPF 数据路径产生影响。

TC Ingress Hook 上 eBPF 转发报文有两个核心的内核调用函数:

三、数据路径

1. Pod 出宿主机

image-20250615131649371

所有 Pod 出流量都会通过 Pod 所在 veth-paic 宿主机测挂载的 TC Ingress Hook 上的 from_container 接管并处理。如果是发往其他宿主机的流量,将会通过 bpf_redirect_neigh()函数,查询宿主机的路由表信息。最终将报文通过物理网卡转出,其中有关于 SNAT 相关工作,会通过eth0上面的 TC Ingress to_netdev完成。

简化后方便理解的网络流转图:

image-20250615132413083

2. 外部流量进入 Pod

image-20250615133100097

而外部流量进入 Pod,会通过宿主机网卡TC Ingress Hook上面挂载的from_netdev进行处理,在这里实现连接追踪等功能。如果是 Pod 的报文,会通过指定endpoint信息,尾调对应 Pod 的 cil_lxc_policy函数。将所有逻辑都交由cil_lxc_policy处理。

cil_lxc_policy在收到报文会,会进行策略判断是否放行,最终Drop或者使用bpf_redirect_peer()将报文转给 Pod 网络名称空间的的 veth 网卡。

简化后方便理解的网络流转图:

image-20250615132427613

3. 同宿主机 Pod 与 Pod 通信

image-20250615131444329

当报文离开 Pod 后,会立即由其所在宿主机上的 veth-pair 网卡进行处理,具体由 from_container 这个 eBPF 程序接管。

判断如果是同宿主机 Pod ,会通过尾调调用 ipv4_local_delivery 函数,而 ipv4_local_delivery 就负责同宿主机 Pod 报文的投递,ipv4_local_delivery 后续会通过尾调调用目标 Pod 的 cil_lxc_policy 的 eBPF 程序,目标 Pod 的 cil_lxc_policy 经过策略判断后是否放行后,会调用 bpf_redirect_peer() 将报文转发到目标 Pod 内部 veth 网卡。

简化后方便理解的网络流转图:

image-20250615132449419

4. Pod 与 Pod 跨宿主机通信

image-20250615131844036

Pod 跨通信的时候,其实就是 Pod 出入宿主机通信的组合,Pod 先通过bpf_redirect_neigh()跳过本机 Netfilter ,然后从 eth0 转出去,最终在到达对端宿主机后,被bpf_redirect_peer()直接转给目标 Pod 。

简化后方便理解的网络流转图:

image-20250615132500429

四、性能提升情况

根据isovalent的给出的测试数据:

image-20250615135157407

传统方案与物理机原生性能差距巨大(约36%的损耗),而 eBPF 方案将这个差距显著缩小到了仅有约 9%,是传统方法性能的 1.41 倍。这意味着在容器中运行的应用几乎可以获得与直接在物理机上运行相媲美的网络性能。

当然,isovalent 毕竟是 Cilium的商业化公司,不能尽信,真想要获取Cilium的性能提升,还是要自己对其进行详尽的性能测试。

题外话1: 这里为什么会特意标注 veth 呢?veth-pair 不是容器网络构建的标准方案吗?为什么还需要写出来呢?

其实又出了一个新东西,在Cilium的支持下,能把性能提升至和宿主机几乎同一个层次,不过版本要求较高,对于Cilium本身,1.16就开始支持了,但是对于 Linux 内核来说,要 Linux Kernel 6.7 版本才支持。

这个东西叫netkit,给的数据说比veth-pair提升了 12% 的性能。也是非常恐怖的性能提升。

题外话2: 其实 HostNetwork 也是一种很好的方式。

如果现在使用的 Flannel 或者 Calico,在完全不动网络架构的情况下,不管是 Pod 网络是 Overlay 还是Underlay,都可以直接将超高网络需求的 workload 设置为 HostNetwork 模式,不要以为这种方式不优雅,不少大厂都这么干,简单直接的性能提升方式。(当然,用之前先规划清楚是否有安全方面或者端口方面、以及服务注册方面的内容需要注意。)

结束语

​ 综上所述,Cilium 借助 eBPF 技术在 Linux 内核中构建了一条高效的数据路径,这是其实现高性能网络的基础。对于在生产环境中部署和维护 Cilium 而言,深入理解原生路由模式下的报文转发细节并非可有可无,而是保障系统稳定、实现快速故障定位的关键。掌握其核心原理,是充分发挥云原生网络优势的必要前提。