容器网络延时与乱序包:veth 的开销到底有多大?
容器网络延时与乱序包veth 的开销到底有多大实验环境Ubuntu 24.04 / 内核 6.8.0-106-generic / Cgroup v2 / Docker 29.1.3 / 华为云 FlexusX 8C16G8vCPU/16G。全程只操作 docker0 / veth / 自建 netns未触碰 eth0 与主路由iperf3 用的镜像为本机临时构建。前一篇我们证明了「容器流量确实走过 veth→docker0→iptables→eth0」。那这个 veth 到底带来了多少性能代价很多人凭感觉说「veth 有开销但无所谓」但到底是多少、为什么有、什么时候该躲开它——本文用 ping、iperf3 和内核计数给出量化答案。1. 引子一次「网卡跑不满」的排查某业务把压测客户端放进容器后吞吐比裸机低了 5%~10%且偶发 TCP 重传。直觉怀疑是 veth。要坐实这个猜测得先回答三件事veth 让单次收发的延迟增加了多少veth 让总吞吐下降了多少所谓的「乱序包」是真发生了还是只是都市传说下面逐项实测。2. 实测一延迟RTT到底多了多少2.1 四组对照每组 100 个包单 ms路径说明avg RTT宿主 → 对端 192.168.0.145直连无 veth0.083 ms容器(bridge) → 对端 192.168.0.145经 vethdocker0MASQUERADE0.123 ms容器(–nethost) → 对端 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/mdev0.070/0.083/0.214/0.015 ms# 容器(bridge)到同一对端$dockerexecpingcping-c100-i0.02192.168.0.145|grepround-trip round-trip min/avg/max0.105/0.123/0.267 ms# 容器(--nethost)到同一对端$dockerrun--rm--cap-add NET_RAW--nethost busyboxping-c50-i0.02192.168.0.145 round-trip min/avg/max0.094/0.114/0.266 ms# 走 veth 的一段容器 ping 自己的网关 docker0$dockerexecpingcping-c100-i0.02172.17.0.1|grepround-trip round-trip min/avg/max0.020/0.028/0.057 ms2.2 怎么读这组数据veth 的「单跳」代价对比「宿主→docker0(0.015)」和「容器→docker0(0.028)」多出来的 ~0.013ms 就是一对 veth 收发一个往返的纯开销对比「宿主→对端(0.083)」与「容器→对端(0.123)」桥 NAT 路径多 ~0.04ms单向约 0.02ms。绝对值很小但每包都付高 PPS 场景下会累积。–nethost 的优势容器(–nethost)→对端 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容器(–nethost)→宿主无 veth27.6 Gbits/sec# 服务端宿主$ iperf3-s-p5209-D$ ss-ltn|grep5209echoYES# [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] --nethost 容器作为客户端$dockerrun--rm--nethost 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 为什么 --nethost 没有这些--nethost的容器直接共享宿主的 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-iEOFOQueue|Reorder|RcvQDropTcpExtTCPSACKReorder60.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_reordering3TCP 允许 3 个包以内的乱序不触发快速重传靠「稍等一下看是否补齐」来吸收偶发乱序避免误重传。注意本机 veth 路径乱序≈0不代表生产环境也安全。真实网卡多队列 RPS 把同一条流散到多 CPU 时OFO 计数会显著上升进而引发 SACK、甚至假性快速重传拖慢吞吐——这正是「网卡跑不满」的常见根因之一。5.3 缓解手段展示RPSReceive Packet Steering把软中断均匀分散到多 CPU提升多流吞吐但单流乱序风险上升。配置示例在容器 veth / 非 eth0上演示避免动主网卡# 让 veth 的 rx 队列把软中断导向 CPU0CPU1掩码 3 0b0011echo3/sys/class/net/veth3cb1d0a/queues/rx-0/rps_cpuscat/sys/class/net/veth3cb1d0a/queues/rx-0/rps_cpus# 变 03RFSReceive 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、加速代理–nethost无 veth延迟最低、吞吐最高代价是失去网络隔离、端口易冲突需接近线速、又要独立 L2 身份如 NFV、LBmacvlan / ipvlan容器直接在父网卡上拿 MAC/IP绕过 bridgeveth开销极低注意 L2 隔离与 hairpin 限制极致性能、绕过宿主协议栈SR-IOV / 网卡透传容器独享 VF几乎零开销成本与灵活性代价高多队列网卡想榨干 CPUbridge RPS/RFS 调优提升多流并发但要监控 OFO 计数经验法则默认用 bridge当 profiling 证明 veth 软中断成为瓶颈CPU 某一核si跑满、/proc/softirqs的 NET_RX 很高、OFO 计数上涨时再考虑 --nethost 或 ipvlan/macvlan。7. 总结延迟veth 单跳往返约 0.013ms跨 NAT 出网约 0.04ms单向。绝对值小但按包累积。吞吐本机极限带宽下 veth 吃掉约 4%28.4→27.3 Gbps真走物理网卡时 CPU 占比更高差距更明显。原理veth 让每个包「过两遍协议栈 触发两次软中断」docker0 桥与 iptables NAT 再叠加在内--nethost因无 veth 省掉这一半开销。乱序veth 本身单队列、RPS 默认关不是乱序源乱序来自多队列网卡 RPS 把同流分散到多 CPU。观测靠nstat TcpExtTCPOFOQueue/netstat -s的 reorder 行缓解靠 RPS/RFS/IRQ 亲和。选型默认 bridge瓶颈确证在 veth 软中断时升级到 --nethost / ipvlan / macvlan / SR-IOV。8. 思考题如果宿主机是 25G 物理网卡、容器跑大带宽你觉得 veth 的吞吐损耗百分比会比本文的 4% 更大还是更小为什么用mpstat -P ALL 1和/proc/softirqs观察一次 bridge 模式 iperf3哪颗 CPU 的NET_RX软中断最忙把它和 veth 的rps_cpus对照你能解释负载分布吗--nethost省了 veth却也失去了网络命名空间隔离。在一个多租户节点上你会怎么权衡「性能」与「安全」ipvlan 能两全吗本文 OFO 增量0。请设计一组实验在多队列物理网卡 RPS的真实入口方向上复现并放大乱序提示需要外部流量打进来而不是本机回环。网络模块三篇到此结束。安全模块下一篇《Privileged 权限你的容器真的需要吗》—— 我们将实测--privileged的能力边界与逃逸风险并给出--cap-add最小权限方案。