4. 容器网络数据面基础

容器网络的数据面并不是“容器直接发一个包到网卡”这么简单。一个从容器内进程发出的报文,通常要依次经过容器网络命名空间内的协议栈、veth 对、宿主机的网桥或路由、netfilter 规则链、TC 或 XDP 挂载点,最后才到达物理网卡;如果启用了 SR-IOV、DPU 或 SmartNIC,部分处理还会被卸载到硬件。理解这条链路上“谁在什么位置处理什么”,是判断性能瓶颈和选择卸载方案的前提。

一、报文在 Linux 网络栈中的真实路径

以最常见的 veth + 网桥模型为例,容器 A 的 eth0 实际是一对 veth 的一端,另一端挂在宿主机某个网桥上。容器内进程调用 send() 后,报文先进入容器网络命名空间的 TCP/IP 栈,完成路由查找、邻居解析和封装,然后从 veth 发出。由于 veth 是成对设备,报文会立刻出现在宿主机侧的 veth 上,被网桥接收并做二层转发或三层路由。

这里有一个容易被忽略的细节:veth 的收发是“软中断上下文中的一次内存拷贝”,并不经过物理介质。因此容器间同宿通信的瓶颈往往不在带宽,而在 CPU 的软中断处理和协议栈开销。当报文需要出宿主机时,才会经过宿主机的路由决策、netfilter 的 POSTROUTING 链,最终交给物理网卡驱动。

二、netfilter:规则链与钩子点

netfilter 是 Linux 内核提供的包过滤与修改框架,它在协议栈的关键位置埋了五个钩子点:PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。每个钩子点上可以挂载不同优先级的表,常见的有 raw、mangle、nat、filter。容器场景中最常打交道的两类规则是:

  • DNAT/SNAT:kube-proxy 的 iptables 模式会在 PREROUTING 做 Service 的 DNAT,在 POSTROUTING 做 MASQUERADE,实现集群内服务发现和出网地址转换。
  • FORWARD 过滤:Docker 默认在 FORWARD 链插入规则,决定容器流量是否允许转发到外部。

netfilter 的代价在于规则是线性匹配的。当 Service 数量达到数千、iptables 规则达到上万条时,每个新建连接都要遍历大量规则,延迟和 CPU 占用都会明显上升。这正是 eBPF 和 IPVS 被引入的动机。

三、eBPF、TC 与 XDP 的分工

eBPF 本身不是数据面,而是一种可编程的内核执行机制。它真正的价值在于可以挂载到不同的钩子点上,用编译后的字节码替代或补充传统内核路径。在容器网络数据面中,最常见的三个挂载点是:

  • TC(Traffic Control):挂在网卡的 ingress/egress qdisc 上,能访问完整的 skb,可以做重定向、封装、NAT、负载均衡。Cilium 的默认模式就是 TC eBPF。
  • XDP(eXpress Data Path):挂在驱动收包的最早阶段,此时 skb 还没分配,性能最高,适合 DDoS 清洗、快速丢弃和简单转发,但可用的辅助函数比 TC 少。
  • socket 层 eBPF:挂在 cgroup 或 socket 上,用于连接跟踪、sockmap 加速同宿通信,绕过部分协议栈。

三者的关系可以这样理解:XDP 最早、最快、能力最受限;TC 稍晚、稍慢、能力最全;socket 层最靠近应用,适合做策略和旁路。实际部署中常常组合使用,例如 XDP 做快速丢弃,TC 做 Service 负载均衡,socket 层做同宿加速。

四、与硬件卸载的衔接关系

当数据面从内核软件路径继续下沉,就进入硬件卸载领域。衔接关系大致分三个层次:

  1. 网卡卸载:checksum、TSO、LRO、RSS 等由网卡完成,减轻 CPU 负担,但不改变数据面逻辑。
  2. 流表卸载:把 TC/XDP 中已经建立的连接跟踪和转发规则下发到网卡的 flow steering 表,后续报文由网卡直接转发,不再经过内核 eBPF 程序。典型代表是 Mellanox ConnectX 系列的 ASAP² 和 Marvell 的硬件卸载能力。
  3. DPU/SmartNIC 卸载:整个容器网络数据面(包括 veth、网桥、netfilter、eBPF 逻辑)被搬到 DPU 的 ARM 核或专用加速器上运行,宿主机 CPU 只负责业务。这是云原生网络卸载的最终形态。

需要强调的是,卸载不是“全有或全无”。一条连接可能首包走软件路径建立状态,后续包走硬件快路径;也可能因为报文特征不匹配而回落到软件路径。判断卸载是否生效,通常要看网卡的 flow 表计数、eBPF map 的命中情况,以及 CPU 软中断占比是否下降。

容器报文数据面路径与卸载衔接关系 容器网络命名空间 应用进程 send() TCP/IP 协议栈 eth0 (veth 端) veth pair 宿主机内核网络栈 网桥/路由 netfilter TC eBPF socket eBPF XDP 连接跟踪 物理网卡 / SmartNIC 驱动 + NAPI 收包 硬件 flow 表 / 卸载引擎 checksum / TSO / RSS 流表卸载:首包软件建流,后续包硬件快转 XDP / TC / socket eBPF 挂载点对比 挂载点 位置 能力 典型用途 性能特征 XDP 驱动收包最早阶段 丢弃/重定向/简单转发 DDoS 清洗、LB 入口 最快,无 skb 分配 TC eBPF qdisc ingress/egress NAT/封装/重定向/CT Cilium Service LB 能力全,略慢于 XDP socket eBPF cgroup/socket 层 连接策略/sockmap 同宿通信加速 最靠近应用,旁路协议栈 软件路径 硬件卸载

五、如何判断该在哪一层做优化

面对一个容器网络性能问题,可以按下面的顺序排查:先看 mpstat -P ALL 1 和 /proc/softirqs,确认软中断是否集中在少数 CPU 上;再用 bpftool prog show 和 tc filter show dev eth0 ingress 确认 eBPF 程序是否已挂载;接着用 ethtool -S eth0 | grep -i steer 查看硬件 flow 表命中计数;最后对比开启卸载前后的 PPS 和 CPU 占用。

判断卸载是否真正生效,关键看三点:网卡 flow 表计数是否增长、eBPF map 的命中率是否下降、软中断 CPU 占比是否降低。三者同时变化,才说明快路径已经接管。

总结来说,Linux 网络栈、netfilter、eBPF、TC、XDP 和硬件卸载并不是互相替代的关系,而是一条从通用到专用、从软件到硬件的连续光谱。容器网络数据面的设计,本质上就是在这条光谱上为每一类流量选择最合适的处理位置:控制面策略留在用户态,连接建立走软件路径,稳定流量下沉到硬件快路径。理解这条衔接关系,后续讨论具体卸载方案时才能有的放矢。