容器网络延时与乱序包:veth 的开销到底有多大?
2026/7/26 0:38:43 网站建设 项目流程

容器网络延时与乱序包:veth 的开销到底有多大?

实验环境:Ubuntu 24.04 / 内核 6.8.0-106-generic / Cgroup v2 / Docker 29.1.3 / 华为云 FlexusX 8C16G(8vCPU/16G)。
全程只操作 docker0 / veth / 自建 netns,未触碰 eth0 与主路由;iperf3 用的镜像为本机临时构建。

前一篇我们证明了「容器流量确实走过 veth→docker0→iptables→eth0」。那这个 veth 到底带来了多少性能代价?很多人凭感觉说「veth 有开销但无所谓」,但到底是多少、为什么有、什么时候该躲开它——本文用 ping、iperf3 和内核计数给出量化答案。


1. 引子:一次「网卡跑不满」的排查

某业务把压测客户端放进容器后,吞吐比裸机低了 5%~10%,且偶发 TCP 重传。直觉怀疑是 veth。要坐实这个猜测,得先回答三件事:

  1. veth 让单次收发的延迟增加了多少?
  2. veth 让总吞吐下降了多少?
  3. 所谓的「乱序包」是真发生了,还是只是都市传说?

下面逐项实测。


2. 实测一:延迟(RTT)到底多了多少

2.1 四组对照(每组 100 个包,单 ms)

路径说明avg RTT
宿主 → 对端 192.168.0.145直连,无 veth0.083 ms
容器(bridge) → 对端 192.168.0.145经 veth+docker0+MASQUERADE0.123 ms
容器(–net=host) → 对端 192.168.0.145共享宿主栈,无 veth0.114 ms
宿主 → docker0(172.17.0.1)本地桥,无 veth0.015 ms
容器 → docker0(172.17.0.1)走 veth pair0.028 ms

采集命令示例(容器内 ping 需--cap-add NET_RAW):

# 宿主直连对端$ping-c100-i0.02192.168.0.145|greprtt rtt min/avg/max/mdev=0.070/0.083/0.214/0.015 ms# 容器(bridge)到同一对端$dockerexecpingcping-c100-i0.02192.168.0.145|grepround-trip round-trip min/avg/max=0.105/0.123/0.267 ms# 容器(--net=host)到同一对端$dockerrun--rm--cap-add NET_RAW--net=host busyboxping-c50-i0.02192.168.0.145 round-trip min/avg/max=0.094/0.114/0.266 ms# 走 veth 的一段:容器 ping 自己的网关 docker0$dockerexecpingcping-c100-i0.02172.17.0.1|grepround-trip round-trip min/avg/max=0.020/0.028/0.057 ms

2.2 怎么读这组数据

  • veth 的「单跳」代价:对比「宿主→docker0(0.015)」和「容器→docker0(0.028)」,多出来的 ~0.013ms 就是一对 veth 收发一个往返的纯开销;对比「宿主→对端(0.083)」与「容器→对端(0.123)」,桥+ NAT 路径多 ~0.04ms(单向约 0.02ms)。绝对值很小,但每包都付,高 PPS 场景下会累积。
  • –net=host 的优势:容器(–net=host)→对端 0.114ms,明显比 bridge 的 0.123ms 更接近宿主原生的 0.083ms——因为它没有 veth 这一跳
  • 这些数字单位是 0.1ms 量级,所以「是否感知得到」取决于你的 SLA:长尾延迟敏感(金融、实时)值得优化;普通 Web 服务可忽略。

3. 实测二:吞吐(iperf3)掉了多少

三档对比,都是「客户端 → 宿主上的 iperf3 服务端」,仅改变客户端所在网络栈:

模式路径吞吐
[A] 基线宿主→宿主localhost,无容器/veth28.4 Gbits/sec
[B] bridge容器(bridge)→宿主经 veth + docker027.3 Gbits/sec
[C] host容器(–net=host)→宿主无 veth27.6 Gbits/sec
# 服务端(宿主)$ iperf3-s-p5209-D$ ss-ltn|grep5209&&echoYES# [A] 基线$ iperf3-c127.0.0.1-p5209-t10[5]0.00-10.00 sec33.1GBytes28.4Gbits/sec sender# [B] bridge 容器作为客户端$dockerrun--rmiperf3img iperf3-c172.17.0.1-p5209-t10[5]0.00-10.00 sec31.8GBytes27.3Gbits/sec sender# [C] --net=host 容器作为客户端$dockerrun--rm--net=host iperf3img iperf3-c127.0.0.1-p5209-t10[5]0.00-10.00 sec32.1GBytes27.6Gbits/sec sender

结论:在单机回环类路径上,veth 大约吃掉~4% 吞吐(28.4→27.3)。注意这是在「本机内存拷贝」极限带宽(~28Gbps)下测的;一旦流量真走物理网卡(比如 10G/25G 线速),veth 的 CPU/软中断开销占比会更明显,因为瓶颈从内存变成 CPU,而 veth 恰恰多吃 CPU。


4. 原理剖析:veth 的开销从哪来

4.1 一次 veth 收发的内部旅程

veth 是「一对虚拟以太网卡」,从一端xmit的包会直接塞进对端的接收队列。关键在于:这一塞,会触发对端 CPU 的NET_RX_SOFTIRQ软中断。于是单个包要过两遍协议栈:

发送进程 └─ write()/sendmsg() 系统调用 └─ 协议栈(1):TCP 分段、IP 封装 └─ veth 设备 xmit:把 skb 放进对端 backlog └─ **触发 NET_RX 软中断 (ksoftirqd)** └─ 协议栈(2):IP 解析、TCP 收包、放入 socket 接收缓冲区 └─ 接收进程 wake_up + 拷贝到用户态
  • 两次协议栈:veth 让一个包在「发送侧」和「接收侧」各走一遍 TCP/IP 栈(虽然都在内核,但两次解析、两次软中断)。
  • 两次软中断 / 上下文切换:发送完成一次(可能在进程上下文),接收侧又要在ksoftirqd软中断上下文处理一次。跨 CPU 时还伴随着 IPI(处理器间中断)唤醒。
  • docker0 桥再叠一层:bridge 要做 FDB 查表、可能过ebtables/bridge netfilter;若开了端口映射,还要走iptables的 conntrack + DNAT/SNAT——这些全在软中断里同步做。

4.2 为什么 --net=host 没有这些

--net=host的容器直接共享宿主的 network namespace,没有 veth、没有桥、没有 NAT。包从进程发出去只过一遍宿主协议栈,接收也只过一遍,软中断对少了一半。所以延迟更低、吞吐更高——上面的实测 [C] 就是证据。

一句话:veth 的代价 = 为「网络隔离」付的「多一次协议栈 + 多一次软中断」的税。隔离是安全红利,税是性能代价,架构上是个权衡。


5. 实测三:乱序包与 OFO 计数

「veth 会导致乱序」是个常被提起的说法。真相是:veth 本身几乎不会制造乱序,真正制造乱序的是多队列 + RPS/RFS 把同一数据流拆到不同 CPU。

5.1 先看默认 RPS 配置

$cat/sys/class/net/eth0/queues/rx-0/rps_cpus ff# 物理网卡:RPS 开启(掩码 ff=所有 CPU),多队列$cat/sys/class/net/docker0/queues/rx-0/rps_cpus 00# 网桥:关闭$cat/sys/class/net/veth3cb1d0a/queues/rx-0/rps_cpus 00# veth:单队列、RPS 关闭$cat/proc/sys/net/ipv4/tcp_reordering3# 允许的最大乱序度(超过才判丢/重传)

关键:veth 是单队列、RPS 默认关闭。同一 TCP 流的所有包走同一条 veth,自然按序到达——所以 veth 本身不是乱序源。乱序来自物理多队列网卡把一条流的包分散到不同 CPU,不同 CPU 上软中断处理速度不一致 → 包到达 socket 时次序被打乱。

5.2 观测乱序计数

nstat(或netstat -s)看内核计数。在跑一段 30 并发流的 iperf3 前后对比:

$ nstat-az|grep-iE"OFOQueue|Reorder|RcvQDrop"TcpExtTCPSACKReorder60.0TcpExtTCPOFOQueue1025370.0# 进入「乱序队列」的包总数(累计)TcpExtTCPRcvQDrop00.0# 跑 30 流 iperf3 15s 后$ nstat-az|awk"/TcpExtTCPOFOQueue/{print \$2}"102537# 增量 = 0$netstat-s|grep-ireorder Detected reordering6timesusing SACK

读这张表

  • TcpExtTCPOFOQueue:接收端把「不连续(乱序)」的段放进 OFO 队列的累计次数。在本机 veth/回环路径上,跑满 30 并发流后增量 = 0——印证了「单队列 veth 不产生乱序」。
  • TcpExtTCPSACKReorder/Detected reordering N times using SACK:靠 SACK 实际检测到的乱序次数(本机累计 6 次,来自开机后的一般流量,非本次实验制造)。
  • tcp_reordering=3:TCP 允许 3 个包以内的乱序不触发快速重传,靠「稍等一下看是否补齐」来吸收偶发乱序,避免误重传。

注意:本机 veth 路径乱序≈0,不代表生产环境也安全。真实网卡多队列 + RPS 把同一条流散到多 CPU 时,OFO 计数会显著上升,进而引发 SACK、甚至假性快速重传,拖慢吞吐——这正是「网卡跑不满」的常见根因之一。

5.3 缓解手段展示

  • RPS(Receive Packet Steering):把软中断均匀分散到多 CPU,提升多流吞吐;但单流乱序风险上升。配置示例(在容器 veth / 非 eth0上演示,避免动主网卡):
    # 让 veth 的 rx 队列把软中断导向 CPU0+CPU1(掩码 3 = 0b0011)echo3>/sys/class/net/veth3cb1d0a/queues/rx-0/rps_cpuscat/sys/class/net/veth3cb1d0a/queues/rx-0/rps_cpus# 变 03
  • RFS(Receive Flow Steering):在 RPS 基础上,按「流」而不是「hash」把包导向上次处理该流的应用所在 CPU,减少缓存 miss,并降低单流乱序。需要配合:
    echo4096>/proc/sys/net/core/rps_sock_flow_entries# 全局流表大小echo1024>/sys/class/net/eth0/queues/rx-0/rps_flow_cnt# 每队列流表
  • irqbalance / IRQ 亲和:把网卡队列中断绑到固定 CPU,避免与业务抢占。
  • tcp_reordering:一般不动;只在确认是「良性乱序」(如多路径)且重传过多时酌情调大。

6. 方案:何时选哪种网络模式

场景推荐理由
多数微服务、Webbridge(默认)隔离好、有 NAT、易用;开销可接受
延迟/PPS 极度敏感、可信负载(节点 agent、加速代理)–net=host无 veth,延迟最低、吞吐最高;代价是失去网络隔离、端口易冲突
需接近线速、又要独立 L2 身份(如 NFV、LB)macvlan / ipvlan容器直接在父网卡上拿 MAC/IP,绕过 bridge+veth,开销极低;注意 L2 隔离与 hairpin 限制
极致性能、绕过宿主协议栈SR-IOV / 网卡透传容器独享 VF,几乎零开销;成本与灵活性代价高
多队列网卡想榨干 CPUbridge +RPS/RFS 调优提升多流并发,但要监控 OFO 计数

经验法则:默认用 bridge;当 profiling 证明 veth 软中断成为瓶颈(CPU 某一核si跑满、/proc/softirqs的 NET_RX 很高、OFO 计数上涨)时,再考虑 --net=host 或 ipvlan/macvlan。


7. 总结

  1. 延迟:veth 单跳往返约 +0.013ms,跨 NAT 出网约 +0.04ms(单向)。绝对值小,但按包累积。
  2. 吞吐:本机极限带宽下 veth 吃掉约 4%(28.4→27.3 Gbps);真走物理网卡时 CPU 占比更高,差距更明显。
  3. 原理:veth 让每个包「过两遍协议栈 + 触发两次软中断」,docker0 桥与 iptables NAT 再叠加在内;--net=host因无 veth 省掉这一半开销。
  4. 乱序:veth 本身单队列、RPS 默认关,不是乱序源;乱序来自多队列网卡 + RPS 把同流分散到多 CPU。观测靠nstat TcpExtTCPOFOQueue/netstat -s的 reorder 行;缓解靠 RPS/RFS/IRQ 亲和。
  5. 选型:默认 bridge;瓶颈确证在 veth 软中断时,升级到 --net=host / ipvlan / macvlan / SR-IOV。

8. 思考题

  1. 如果宿主机是 25G 物理网卡、容器跑大带宽,你觉得 veth 的吞吐损耗百分比会比本文的 4% 更大还是更小?为什么?
  2. mpstat -P ALL 1/proc/softirqs观察一次 bridge 模式 iperf3,哪颗 CPU 的NET_RX软中断最忙?把它和 veth 的rps_cpus对照,你能解释负载分布吗?
  3. --net=host省了 veth,却也失去了网络命名空间隔离。在一个多租户节点上,你会怎么权衡「性能」与「安全」?ipvlan 能两全吗?
  4. 本文 OFO 增量=0。请设计一组实验,在多队列物理网卡 + RPS的真实入口方向上,复现并放大乱序(提示:需要外部流量打进来,而不是本机回环)。

网络模块三篇到此结束。安全模块下一篇:《Privileged 权限:你的容器真的需要吗?》—— 我们将实测--privileged的能力边界与逃逸风险,并给出--cap-add最小权限方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询