Linux netns+veth自环测试:无物理网卡快速验证网络性能
发布时间:2026/9/16 9:15:06 作者:尧图编辑部 阅读量:1,286

做网络性能测试的时候最怕的就是环境不干净。物理机上有别的业务流量、网卡驱动有奇奇怪怪的 offload 行为、交换机上还有 QoS 策略测出来的数据你根本说不清是网卡的问题、驱动的问题还是协议栈的问题。我以前为了给容器网络方案做基准测试找了几台裸金属服务器折腾了半天最后发现连 RDMA 还是 TCP 都定不下来直接被测试环境劝退。后来我改用 Linux 内核自带的 netns 配合 veth 做自环测试十分钟就能搭出一套完全隔离的虚拟网络环境再跑 Iperf 压吞吐这种玩法在网上也有人戏称为 Magic Iperf 式调参法。它解决的核心痛点是在无物理网卡依赖的前提下快速验证协议栈性能、veth 转发效率、TCP/UDP 参数调优效果。适合搞容器网络、OVS、DPDK 虚拟化网络以及日常排查内核网络栈问题的朋友参考。这篇文章我会把从创建 netns 到跑出稳定带宽数据的完整过程包括参数含义、调优思路、踩坑记录都写清楚。1. 为什么用 netns 做自环测试1.1 自环测试要解决什么问题先明确一下“自环”的含义。在 Linux 网络里最简单的自环就是 ping 127.0.0.1数据包从应用层发出到协议栈转一圈再回到应用层全程不碰物理网卡。但这种方法只能验证协议栈“没坏”测不了真实的吞吐能力因为 loopback 接口的路径太短软中断处理和内存拷贝的开销占比太大测出来的数字没什么参考价值。而基于 netns 的自环测试是把两个网络命名空间用一条veth 虚拟网线连起来在一端跑 Iperf Server另一端跑 Iperf Client。数据流要完整经过应用层 → Socket → TCP/UDP 协议栈 → veth 驱动 → 对端 veth 驱动 → 对端协议栈 → 对端应用层。这条路径比 loopback 长得多更接近真实容器间通信的模型但又不依赖任何物理硬件。我总结下来这种测试模式主要解决三类问题验证内核协议栈基线性能。同一个内核、同一套网卡 offload 配置把物理网卡从链路里摘出去纯粹看 TCP/IP 协议栈能跑多快。验证 veth 和虚拟交换机bridge、OVS的转发能力。容器网络方案很多比如 Flannel、Calico、Docker bridge底层都会用到 veth它的单流和多流吞吐直接决定容器网络上限。调试 TCP 参数和 Iperf 参数。比如窗口大小、队列长度、中断合并这些参数在物理网卡上受驱动影响很难观察规律在 netns 里干扰少很容易看出趋势。1.2 netns veth 组合比物理网卡测试好在哪我自己用过至少三种网络性能测试环境对比下来netns veth 的优势很实在对比维度物理网卡对测loopback 自环netns veth 自环环境搭建成本需要两台机器或双网卡还得配置 IP 路由一条命令搞定三条命令搞定隔离性受交换机/路由器策略影响只测协议栈路径太短完全本地隔离无外部干扰模型贴近容器一般差高veth 就是容器常用虚拟网卡可重复性受硬件波动影响很好极好同一内核下结果稳定测速上限取决于网卡和 PCIe 带宽单流可到几十 Gbps取决于 CPU 和 veth 驱动通常 4-8 Gbps这里必须说一句netns 自环测出来的数字不等于物理网卡的真实吞吐。它的价值在于校准协议栈和虚拟网络转发路径而不是替代硬件基准测试。如果你是想测某块 Mellanox 网卡的极限性能那还是老老实实找两台机器直连。另外我特别喜欢 netns 的一点是它不需要 root 之外的特殊权限在开发机、CI 环境里都能跑。只要内核开启了网络命名空间支持一般发行版默认都是开着的门槛极低。2. 十分钟搭好测试环境2.1 创建网络命名空间先检查当前系统是否支持 netnsip netns list如果没有任何输出说明目前还没有命名空间但不代表内核不支持。你可以直接创建试试sudo ip netns add neptune-a sudo ip netns add neptune-b创建好之后用ip netns list能看到两个全新的隔离网络栈。这时候每个命名空间里只有一个 loopback 接口而且是 down 状态你进里面 ping 127.0.0.1 都 ping 不通需要手动把它拉起来sudo ip netns exec neptune-a ip link set lo up sudo ip netns exec neptune-b ip link set lo up其实后面测试用不到 lo但养成好习惯进入命名空间第一件事就是把 lo 拉起来否则一些应用会因为在回环接口上报错。我习惯用“neptune”这种前缀给命名空间命名纯粹是为了后面清理的时候好辨认。命名规则随意但千万别用中文和特殊字符内核不认。2.2 创建 veth pair 并接线veth 是成对出现的虚拟网卡你创建一条网线两个端点分别在两个命名空间里数据从一端进从另一端出没有任何物理硬件参与。创建 veth pair 的命令是sudo ip link add veth-a type veth peer name veth-b这条命令会在当前命名空间也就是默认的 root 命名空间里创建两个虚拟网卡veth-a 和 veth-b。它们俩之间有一条虚拟的“网线”连着。接下来把两个端点分别塞进前面创建的两个命名空间sudo ip link set veth-a netns neptune-a sudo ip link set veth-b netns neptune-b这一步做完root 命名空间里就看不到这两个网卡了。你可以在各自命名空间里确认sudo ip netns exec neptune-a ip link show veth-a sudo ip netns exec neptune-b ip link show veth-b如果能看到veth-aifN这种带ifN的显示说明 veth pair 关系正确。这里有个新手常犯的错用ip link show在 root 命名空间里找 veth-a结果找不到以为创建失败了——其实它已经被移动到子命名空间里了不在 root 空间显示。配 IP 也是分命名空间进行的sudo ip netns exec neptune-a ip addr add 192.168.88.1/24 dev veth-a sudo ip netns exec neptune-b ip addr add 192.168.88.2/24 dev veth-b sudo ip netns exec neptune-a ip link set veth-a up sudo ip netns exec neptune-b ip link set veth-b up这时候从 neptune-a 里 ping neptune-b 里的 IPsudo ip netns exec neptune-a ping 192.168.88.2能通就说明链路已经可用了。我建议在这个环节先多花一分钟确认 MTU 一致、ARP 能解析因为后面 Iperf 测速异常时最先要怀疑的就是链路层配置。2.3 关于 MTU 和队列的预先检查veth 默认 MTU 是 1500和大多数物理网卡一致。但这里有个隐藏点veth 驱动其实支持很大的 MTU你可以把它改成 9000巨型帧再测吞吐会有明显变化后面调优那节我再细讲。另外 veth 在 4.x 之后的内核里每个 veth 端有一个 TX queue这个队列背后会绑定到某一个 CPU 上。多队列 veth 需要借助ethtool -L结合硬件能力来配置但 veth 本身不支持多队列它只能靠 RPS 做接收方向的软中断分发。这也是为什么单条 veth 单条 Iperf TCP 流很难吃满多核 CPU 的原因之一。在进入性能测试前先看一眼当前 veth 状态sudo ip netns exec neptune-a ethtool -k veth-a | grep -E tx-checksum|scatter-gather如果输出显示tx-checksum-ipv4: on校验和卸载是开启的。veth 驱动不支持真正的硬件校验和卸载但会模拟一个 offload 行为把校验和计算推迟到对端接收时做这个机制我们测速时要心里有数后面分析 CPU 开销时会遇到。3. Iperf 关键参数与第一轮测试3.1 服务端怎么起Iperf 是经典的网络性能测量工具有 Iperf2 和 Iperf3 两个大版本很多发行版默认装的是 Iperf3。这两者参数大部分兼容但有些细节不一致这篇文章统一用 Iperf3 讲。在 neptune-b 里启动服务端sudo ip netns exec neptune-b iperf3 -s默认监听 5201 端口。如果你需要同时开多个测试实例可以用-p指定不同端口。实际生产测试时我建议加一个--logfile参数把服务端的日志也落盘方便后续排障sudo ip netns exec neptune-b iperf3 -s -p 5201 --logfile /tmp/iperf-server.log服务端启动时最好在会话前台跑如果中途崩溃或端口冲突立刻就能看到报错。不要用nohup ... 丢后台否则出问题排查成本高。3.2 客户端参数怎么选客户端在 neptune-a 里启动第一轮先用最朴素的参数sudo ip netns exec neptune-a iperf3 -c 192.168.88.2 -t 10 -i 1-t 10表示测试 10 秒-i 1表示每 1 秒打印一次实时带宽。这跑出来的结果就是 veth 自环路径上TCP 单流传输的吞吐基线。先把 Iperf3 最常用的几个参数理清楚这些是性能测试中的核心操作项参数含义我的建议-c客户端模式后面跟服务端 IP不需要加协议头直接写地址-s服务端模式在隔离命名空间里跑-t测试时长单位秒基准测试至少 10 秒太短跑不出稳定值-i打印间隔单位秒1 秒最佳能观察波动曲线-P并行连接数单流跑不满 CPU 时加通常 4 到 8-l读写缓冲区大小不是越高越好和 MTU、延迟、CPU 相关-wTCP 窗口大小高带宽长链路必须调自环时影响小-R反向测试服务端发客户端收验证双向吞吐差异-uUDP 模式测 UDP 带宽和丢包用-bUDP 模式下的目标带宽如-b 1G代表打满 1Gbps 的 UDP 流很多人忽略-l参数。Iperf3 默认读写 buffer 是 128 KB但这个值是应用层读写 Socket 的缓冲块大小不是内核 TCP 窗口。调大-l可以减少用户态和内核态之间的拷贝次数但会占用更多内存而且不是所有场景都能线性提升。我在自环测试里试过 16 KB、64 KB、256 KB、1 MB 四档发现在 veth 本地回环这种零丢包低延迟路径上-l从 128 KB 调大到 256 KB 吞吐提升约 5% 左右再往上基本无效因为瓶颈早就从应用层拷贝转移到了内核软中断和 veth 驱动。3.3 结果解读从带宽到重传先看我实测的一组数据内核 5.158 vCPU 云主机netns veth 自环[ ID] Interval Transfer Bitrate Retr [ 5] 0.00-1.00 sec 112 MBytes 940 Mbits/sec 0 [ 5] 1.00-2.00 sec 113 MBytes 950 Mbits/sec 0 [ 5] 2.00-3.00 sec 113 MBytes 950 Mbits/sec 0 ... [ 5] 0.00-10.00 sec 1.08 GBytes 930 Mbits/sec 0930 Mbps 这个数字在千兆网时代可能是“满速”但它并不是 veth 的上限而是被 CPU 频率和 Iperf3 单线程模型限制住了。自环路径上 CPU 要处理两次协议栈、两次 veth 驱动中断单核软中断很容易到 100%此时再调应用参数已经没有意义。Iperf3 输出的Retr列是 TCP 重传次数。如果这个数字在自环测试里大于 0基本可以判定不是网络丢包导致的而是内存压力、CPU 调度延迟或 Socket 缓冲区太小。这属于内核栈层面的问题要从sysctl参数入手而不是怀疑 veth 链路“不稳定”。还有个小技巧加上-J参数让 Iperf3 输出 JSON 格式的结果然后用jq解析方便做自动化回归对比。sudo ip netns exec neptune-a iperf3 -c 192.168.88.2 -t 10 -i 1 -J | jq .end.sum_received.bits_per_second这个值就是最终吞吐量bit/s。我经常写一个循环脚本自动调整参数跑多轮把结果收集到 CSV 里画趋势图比起肉眼看终端日志靠谱得多。4. 把吞吐量再往上顶一顶4.1 多流并发与 RPS 软中断分发单流 930 Mbps 显然不够看因为在 8 vCPU 的机器上Iperf3 默认只用一个 CPU 处理发送、一个 CPU 处理接收。veth 的收包路径软中断是绑在某个 CPU 上的不手动优化的话压力全压在一个核上。第一步先加并发流sudo ip netns exec neptune-a iperf3 -c 192.168.88.2 -t 10 -i 1 -P 4-P 4表示同时建立 4 条 TCP 连接。多数情况下多流能把多个 vCPU 用起来吞吐会明显提升。我实测-P 4之后总吞吐能到 3.5 Gbps 左右-P 8能到 5 Gbps 附近。但这个提升不是线性的因为 Iperf3 进程本身是单线程的它内部用事件驱动方式管理多条连接CPU 单核还是可能成为瓶颈。更激进的做法是同时启动多个 Iperf3 客户端进程每个进程绑定不同 CPU但操作复杂度高日常用-P就够了。第二步优化接收方向软中断分发也就是 RPSReceive Packet Steering。veth 是纯软件设备没有硬件多队列收包软中断通常会集中在一个 CPU 上。我们可以手动把每个 veth 端口的 RPS 扩展到所有 CPUsudo ip netns exec neptune-a sh -c echo f /sys/class/net/veth-a/queues/rx-0/rps_cpus sudo ip netns exec neptune-b sh -c echo f /sys/class/net/veth-b/queues/rx-0/rps_cpus这里的f是十六进制掩码表示允许 CPU 0-3 处理该队列的软中断。如果机器是 8 核可以填ff。注意RPS 不是越高越好。CPU 数太多时跨 NUMA 节点访问内存反而会增加延迟吞吐可能不升反降。我建议先用ff跑一轮再用f0只允许 CPU 4-7跑一轮挑数据好的。第三步是顺带把发送方向的 XPS 也设置一下sudo ip netns exec neptune-a sh -c echo f /sys/class/net/veth-a/queues/tx-0/xps_cpus sudo ip netns exec neptune-b sh -c echo f /sys/class/net/veth-b/queues/tx-0/xps_cpusXPS 是发送方向的 CPU 亲和性设置可以让发送软中断和发送进程尽量留在同一个 NUMA 节点上减少跨节点访问。4.2 窗口、缓冲区与 MTU 调优TCP 窗口大小在自环这种低延迟链路里不是主要瓶颈因为带宽延迟积非常小默认窗口就能撑满。但如果你想把 Jumbo Frame 和内核协议栈的极限压出来可以这样调sudo ip netns exec neptune-a sysctl -w net.ipv4.tcp_wmem4096 16384 16777216 sudo ip netns exec neptune-a sysctl -w net.ipv4.tcp_rmem4096 87380 16777216 sudo ip netns exec neptune-b sysctl -w net.ipv4.tcp_wmem4096 16384 16777216 sudo ip netns exec neptune-b sysctl -w net.ipv4.tcp_rmem4096 87380 16777216这三个值分别是 TCP 发送/接收缓冲区的最小值、默认值和最大值。在 veth 自环测试中真正起作用的是最大值因为数据从 veth 发出后马上就被对端接收缓冲区很快就能被填满。调大最大值后配合iperf3 -w 4M可以让内核一次性接收更多的 in-flight 数据。MTU 的调整更直接。把两个 veth 的 MTU 从 1500 改成 9000sudo ip netns exec neptune-a ip link set veth-a mtu 9000 sudo ip netns exec neptune-b ip link set veth-b mtu 9000改完后每个 TCP 段最多携带约 8960 字节数据相比 1500 时的 1460 字节有效载荷提升了 6 倍以上。这意味着同样处理一个数据包的开销能传输的数据量大幅增加。我实测 MTU 9000 时单流吞吐从 930 Mbps 提升到 2.1 Gbps多流能到 6 Gbps 以上。但注意ip link set mtu要两端同时改否则对端会因为 MTU 不一致丢弃大包。如果你在测试中遇到“能 ping 通但 Iperf 起不来”先把 MTU 改回 1500 试试这是最常见的原因。4.3 顺带测一测 UDP 和双向性能TCP 测试受拥塞控制和 ACK 开销影响有时候不能完全反映链路极限。UDP 模式可以更激进地压测sudo ip netns exec neptune-a iperf3 -c 192.168.88.2 -t 10 -u -b 10G-b 10G表示目标带宽 10 Gbps。在自环路径上UDP 的发送速率几乎不受流控影响丢包率如果不为 0通常说明接收端处理不过来了。调大-l可以让每个 UDP 报文更大减少收包数量从而降低丢失概率。反向测试就是加-Rsudo ip netns exec neptune-a iperf3 -c 192.168.88.2 -t 10 -R这时数据流方向是 neptune-b → neptune-a也就是服务端往客户端推流。如果你做了 RPS 设置正反两个方向会共享同一组 CPU可能导致总量受限。双向性能更严谨的测法是两边各启动一个 Iperf3分别跑正向和反向但大多数场景用-R就够了。我一般把完整测试流程固定成一套组合拳# 单流 TCP 基线 iperf3 -c IP -t 10 -i 1 # 多流 TCP 压测 iperf3 -c IP -t 10 -i 1 -P 4 # 调整 MTU 后再压 iperf3 -c IP -t 10 -i 1 -P 4 -l 256K # UDP 压测 iperf3 -c IP -t 10 -u -b 10G -l 1400 # TCP 反向 iperf3 -c IP -t 10 -i 1 -R这套流程跑完基本上对当前内核版本下 veth 自环路径的性能上限就有数了。5. 常见问题排查实录5.1 接口在命名空间里看不见典型现象ip netns exec neptune-a ip link show veth-a报错Device veth-a does not exist。排查思路先确认创建 veth pair 时是不是在 root 命名空间执行的。如果创建命令本身没问题再确认是不是已经被移动到了其他命名空间。用ip netns exec ns ip link show挨个命名空间找一遍或者直接在一个命名空间里用ip link show type veth列出所有 veth 接口。还有一种情况是创建 veth pair 后没有执行ip link set veth-a netns neptune-a导致 veth-a 还在 root 里neptune-a 里自然看不到。这种错通常出现在你按顺序执行命令但不小心跳步的时候。5.2 端口被占用导致服务起不来典型现象iperf3 -s启动时报bind() failed: Address already in use。这个在自环环境里不太常见但如果你在同一个命名空间里跑了多个服务端或者测试程序没退出干净端口就会被占。查一下当前占用情况sudo ip netns exec neptune-b ss -lntp | grep 5201确认进程还在的话直接 kill 或者换个端口-p 5202启动新的服务端。我建议测试时尽量固定端口并写进脚本里避免每次手工起服务时搞混。5.3 Iperf 起不来提示 Connection refused这通常是客户端能 ping 通服务端 IP但服务端进程没有在监听对应端口。优先排查服务端是不是在正确的命名空间里启动的。这在多终端操作时特别容易搞错你以为自己在 neptune-b 里跑了服务端实际是在 root 命名空间里跑的。服务端是不是挂了。前台运行的话看一眼终端有没有报错。用ip netns exec neptune-b ss -lnt确认监听状态比 ping 更可靠。5.4 测速结果奇低或双向不一致这是压测时最高频的问题。先排除最简单的原因两端 MTU 不一致导致数据包分片或丢弃。先把 MTU 统一设成 1500 对照测试。客户端和服务端的-l不一致。Iperf3 默认情况下服务端会参考客户端的 buffer 大小但如果你在服务端手动指定了不同的-l行为可能不符合预期。没有设置 RPS/XPS所有软中断都打在一个 CPU 上单核成为瓶颈。CPU 调频策略限制。用cpupower frequency-info检查当前 governor如果是powersave高负载下 CPU 频率可能被压得很低。我自己的测试机上跑出 600 Mbps 这种离谱数据时基本都是这个原因用cpupower frequency-set -g performance切到性能模式后数据立刻恢复正常。双向结果不一致也很常见。-R方向的数据流会经过对端的发送路径和本端的接收路径由于 RPS 只优化了接收侧如果你只设置了 veth-a 的 RPS 而没有设置 veth-b 的反向测试就可能因为接收侧软中断集中到单核而掉速。处理方法是把两个命名空间里的 RPS 都配置上保持对称。5.5 清理环境测试完记得清理命名空间和 vethsudo ip netns del neptune-a sudo ip netns del neptune-b删除命名空间会自动把里面的 veth 虚拟网卡也删掉不需要额外手动删除。如果你创建了 bridge 或 vlan 子接口建议用ip link del单独清干净避免留垃圾配置影响后续测试。最后分享一点经验如果你只是临时想验证一下 Iperf 参数效果netns 自环是成本最低的方案。但我个人实际用下来发现它最大的价值不止是测出“多少 Gbps”这个数字而是能帮你形成一套可复现的网络基准测试方法论。物理环境里的变量太多交换机、驱动、光模块、小包冲击都会干扰判断而 netns 环境里变量的控制权完全在你手里。一个小技巧收尾创建好 veth 并配完 IP 后我习惯第一时间写一个test.sh脚本把从iperf3 -s到iperf3 -c、再到清理环境的命令全部固化下来。下次换内核版本、换系统、调参数直接一键跑完所有输出存到带时间戳的日志目录里。网络调优是个重复劳动密度很高的活自动化能帮你省下大把时间也能让测试结论更有说服力。