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 层做同宿加速。
四、与硬件卸载的衔接关系
当数据面从内核软件路径继续下沉,就进入硬件卸载领域。衔接关系大致分三个层次:
- 网卡卸载:checksum、TSO、LRO、RSS 等由网卡完成,减轻 CPU 负担,但不改变数据面逻辑。
- 流表卸载:把 TC/XDP 中已经建立的连接跟踪和转发规则下发到网卡的 flow steering 表,后续报文由网卡直接转发,不再经过内核 eBPF 程序。典型代表是 Mellanox ConnectX 系列的 ASAP² 和 Marvell 的硬件卸载能力。
- DPU/SmartNIC 卸载:整个容器网络数据面(包括 veth、网桥、netfilter、eBPF 逻辑)被搬到 DPU 的 ARM 核或专用加速器上运行,宿主机 CPU 只负责业务。这是云原生网络卸载的最终形态。
需要强调的是,卸载不是“全有或全无”。一条连接可能首包走软件路径建立状态,后续包走硬件快路径;也可能因为报文特征不匹配而回落到软件路径。判断卸载是否生效,通常要看网卡的 flow 表计数、eBPF map 的命中情况,以及 CPU 软中断占比是否下降。
五、如何判断该在哪一层做优化
面对一个容器网络性能问题,可以按下面的顺序排查:先看 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 占用。
总结来说,Linux 网络栈、netfilter、eBPF、TC、XDP 和硬件卸载并不是互相替代的关系,而是一条从通用到专用、从软件到硬件的连续光谱。容器网络数据面的设计,本质上就是在这条光谱上为每一类流量选择最合适的处理位置:控制面策略留在用户态,连接建立走软件路径,稳定流量下沉到硬件快路径。理解这条衔接关系,后续讨论具体卸载方案时才能有的放矢。