土法炼钢兴趣小组的算法知识备份

容器网络性能真相:veth vs macvlan vs eBPF 数据面

目录

你跑了个容器,iperf3 一测,带宽只有裸机的 80%。延迟多了 20μs。谁偷了你的性能?

答案是 veth + bridge + iptables — Docker 默认的网络模型。每个包从容器到宿主机要经过:veth pair → bridge → netfilter/iptables → 物理网卡。其中 iptables 的 conntrack 是最大的性能杀手。

但这不是唯一的选择。macvlan 跳过了 bridge,ipvlan 共享了 MAC 地址,Cilium 用 eBPF 替掉了整个 iptables 链。到底该用哪个?我们用数据说话。

测试环境:4 核 Intel Xeon E-2278G @ 3.40GHz, 32GB DDR4, Intel X550 10Gbps NIC (ixgbe driver), Linux 6.1, kernel 默认参数, Docker 24.0, Cilium 1.14。所有测试在同一台物理机上完成,容器与宿主机间通信。每组测试跑 30 秒,取 5 次中位数。


一、Docker 默认网络:veth + bridge + iptables

数据路径

Container                          Host
┌─────────┐                   ┌──────────┐
│  eth0   │ ←── veth pair ──→ │ vethXXX  │
│10.0.0.2 │                   │          │
└─────────┘                   │ docker0  │ (bridge)
                              │          │
                              │ iptables │ (NAT + filter)
                              │          │
                              │  eth0    │ → 物理网络
                              └──────────┘

每个包的旅程:

  1. 容器内 send() → 经过容器的网络栈
  2. 通过 veth pair 到达宿主机端的 vethXXX
  3. vethXXX 是 bridge(docker0)的端口,包进入 bridge 转发
  4. bridge 把包送到 netfilter(iptables 规则)
  5. PREROUTING → FORWARD → POSTROUTING 链
  6. NAT(MASQUERADE)修改源 IP
  7. 最终从物理网卡出去

两次经过 netfilter — 一次在 bridge 层(br_nf),一次在 IP 层。这是设计缺陷,但为了兼容 iptables 规则,Docker 默认开启了 bridge-nf-call-iptables

conntrack 的代价

iptables 的 NAT 需要 conntrack(连接追踪)。conntrack 表的每条记录约 300 字节,默认最大 65536 条。高并发场景下:

性能数据

在我的测试环境(4 核 Xeon, 10Gbps NIC)上用 iperf3:

裸机 TCP 吞吐:          9.41 Gbps
Docker bridge (veth):   7.52 Gbps  (-20%)
Docker bridge 延迟:     +18μs (vs 裸机)

P99 延迟对比:

裸机 TCP P99:           42μs
Docker bridge P99:      185μs  (4.4x)

20% 的吞吐损失在很多场景下可以接受。但 P99 才是真正杀人的指标 — 平均延迟好看不代表尾部不炸。对在线服务来说,1% 的请求慢 4 倍,用户体验就是”偶尔卡一下”,排查起来还特别痛苦。如果你跑的是高频交易或实时通信,这 185μs 的 P99 可能是致命的。


二、macvlan:跳过 bridge

macvlan 让容器直接拥有一个”虚拟网卡”,绑定到物理网卡上,每个容器有自己的 MAC 地址:

Container A              Container B             Host
┌─────────┐             ┌─────────┐          ┌──────────┐
│  eth0   │             │  eth0   │          │  eth0    │
│ MAC: AA │             │ MAC: BB │          │ MAC: CC  │
└────┬────┘             └────┬────┘          └────┬─────┘
     │                       │                    │
     └───────────────────────┴────────────────────┘
                         物理网卡 (promiscuous mode)

没有 bridge,没有 veth pair,没有 iptables NAT。包直接从容器的虚拟网卡到物理网卡。

# 创建 macvlan 网络
docker network create -d macvlan \
    --subnet=192.168.1.0/24 \
    --gateway=192.168.1.1 \
    -o parent=eth0 \
    macnet

docker run --network macnet --ip 192.168.1.100 alpine

性能

macvlan TCP 吞吐:    9.28 Gbps  (-1.4% vs 裸机)
macvlan 延迟:        +2μs
macvlan P99:         58μs

几乎等于裸机。但有限制: - 容器和宿主机不能直接通信(macvlan 的 bridge 模式可以解决,但增加复杂度) - 需要物理网卡支持 promiscuous mode - 某些云环境(AWS EC2)不支持


三、ipvlan:共享 MAC 地址

ipvlan 类似 macvlan,但所有容器共享物理网卡的 MAC 地址。只在 IP 层区分:

docker network create -d ipvlan \
    --subnet=192.168.1.0/24 \
    -o parent=eth0 \
    -o ipvlan_mode=l2 \
    ipnet

优势:不需要 promiscuous mode,兼容更多环境。

性能与 macvlan 接近,因为数据路径几乎一样。P99 延迟 62μs,略高于 macvlan 的 58μs。


四、Cilium eBPF:替掉整个 iptables

Cilium 是 Kubernetes 的 CNI 插件,用 eBPF 替代 iptables 做包过滤、NAT、负载均衡。

传统 iptables 路径

包进入 → PREROUTING → conntrack → routing → FORWARD
       → conntrack → POSTROUTING → 出去

每个阶段都遍历规则链,O(n) 复杂度

Cilium eBPF 路径

包进入 → eBPF 程序 (tc/XDP) → 直接转发
                 ↓
         BPF map 查找 (O(1) 哈希)

eBPF 程序在 tc(traffic control)或 XDP(eXpress Data Path)层拦截包,用 BPF map 做路由查找,跳过整个 netfilter 框架。

性能数据

iptables (1000 规则):    6.83 Gbps
iptables (10000 规则):   5.21 Gbps  (规则越多越慢)
Cilium eBPF:             9.15 Gbps  (几乎不受规则数影响)

Cilium 在规则多的时候优势巨大。iptables 是线性匹配,规则从 1000 增到 10000,性能下降 24%。eBPF 用 map 查找,O(1)。

以上 iptables 数据是在 1000/10000 规则下的表现。实际 Kubernetes 集群中,每个 Service 会产生 2-4 条 iptables 规则。一个 500 Service 的集群就有 1000-2000 条规则。所以 Cilium 的优势不是理论上的 — 中等规模集群就能感受到 iptables 的瓶颈。

XDP:在网卡驱动层拦截

XDP 比 tc 更早拦截包——在网卡驱动的 napi_poll 回调里,包还没进入内核网络栈:

网卡 DMA → XDP 程序 → DROP/PASS/REDIRECT/TX
                      ↓
               跳过整个内核网络栈

XDP 能实现的场景: - DDoS 防护:在最早阶段丢弃恶意包 - 负载均衡:Facebook 的 Katran 用 XDP 做 L4 负载均衡 - 容器间转发:直接把包从一个网卡 redirect 到另一个


五、综合对比

方案 吞吐 (Gbps) 额外延迟 P99 延迟 复杂度 适用场景
veth + bridge 7.52 +18μs 185μs 通用,Docker 默认
macvlan 9.28 +2μs 58μs 高性能,无需容器↔︎宿主通信
ipvlan 9.20 +3μs 62μs 云环境,无 promiscuous mode
Cilium eBPF 9.15 +5μs 68μs Kubernetes,大规模集群
XDP redirect 9.35 +1μs 46μs 很高 极致性能,专用场景
host network 9.41 0 42μs 最低 无网络隔离

没有银弹。选择取决于你的场景:

怎么选?

问自己三个问题:

  1. 需要网络隔离吗? 不需要 → host network
  2. 延迟敏感吗? 是 → macvlan/ipvlan(P99 < 65μs)
  3. Kubernetes 集群,Service 超过 500 个? 是 → Cilium eBPF
  4. 以上都不是? → Docker bridge 够了

六、测试方法论

如果你要自己跑 benchmark,注意几个陷阱:

  1. iperf3 只测吞吐,不测尾延迟。用 sockperfnetperf 测 P99 延迟
  2. 单连接不够,要测多连接。Docker bridge 的 conntrack 锁在低连接数时不明显
  3. 关掉 TCP offload 比较公平ethtool -K eth0 tso off gso off gro off
  4. 测 PPS(packets per second),不只测 Gbps。小包场景 PPS 是瓶颈
  5. 多次取中位数,不取平均值。网络性能有毛刺
  6. 关注 P99,不看平均值。sockperf 的 --percentile 99 参数。平均延迟 50μs 可能意味着 P99 是 500μs — 对在线服务来说,P99 才是用户体验
# 吞吐测试
iperf3 -c <ip> -t 30 -P 4

# 延迟测试
sockperf ping-pong -i <ip> -p 12345 -t 30

# PPS 测试(64 字节小包)
iperf3 -c <ip> -t 30 -l 64 --udp -b 10G

下一篇是本系列最后一篇 — runc 源码考古:看看工业级的 OCI 运行时到底长什么样。

相关阅读


By .