ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

TRex结合DPDK:用Python实现网络转发性能自动化测试

2026/9/8 14:04:49 拓冰建站 浏览量
TRex结合DPDK:用Python实现网络转发性能自动化测试 1. 为什么网络转发测试的“自动化”绕不开TRex1.1 先想清楚这次要自动化的是“哪一个平面”这几年一聊Python自动化很多人默认就是指Selenium、Appium、Playwright这类UI驱动再延伸一点就是接口自动化、Jenkins部署、测试平台搭建。这些方向都是围绕“业务请求”走的浏览器里点一个按钮后端调一个接口整个自动化链路都在应用层打转。可我做网络设备测试的看到的需求完全是另一回事。换了一个软件版本后一台交换机在64字节小包场景下还能不能保持线速转发混合包长下丢包率会不会超过百万分之一长时间高压运行后CPU队列是否稳定这些问题跟网页元素定位一点关系都没有我真正需要的是一个能在物理端口上按指定速率、指定包长、指定拓扑关系持续制造真实流量的“发报机”并且这个发报机必须能被脚本控制、能返回统计指标、能接入日常回归流程。这时候TRex就进来了。它不是一个通用自动化测试框架而是一套开源的高性能流量生成与控制工具。所谓“Python自动化”放在TRex语境里指的是用Python作为控制面去驱动数据面在物理网卡上发包和收包。数据面依然由DPDK这样的高性能用户态网络框架处理Python主要负责下发指令、收集结果、做断言和组织用例。理解了这一层后面看脚本逻辑就会顺很多。1.2 为什么不是Scapy不是iperf也不是商用仪表很多第一次接触网络测试自动化的同学会问我明明有Scapy能构造任意报文也能封包发送为什么还要换TRex我当初也试过用Scapy跑小包压力结论是它非常适合做功能用例和异常报文验证但不适合做线速性能测试。Scapy的默认发包路径在用户态逐包处理每分钟发个几万包还可以接受一旦到每秒百万级小包CPU开销立刻暴露瓶颈性能表现也不稳定。再看iperf它擅长测TCP/UDP吞吐但它是基于“流”和“连接”的模型很难精确控制每个报文的间隔、包长分布、突发行为也没法像打流仪表那样方便地构造一条64字节小包流持续打向被测设备。至于硬件测试仪表单端口成本很高带外控制接口、厂商私有CLI、license授权都是长期维护成本。TRex最实在的地方在于它把数据面放到了Linux服务器的网卡上用DPDK驱动网卡绕过内核收发数据又提供了一套Python控制接口相当于给我一台能跑线速、能写代码的“软件打流仪”。引入TRex还有一个很现实的理由它可以和现有DevOps流程拼在一起。常规硬件仪表想跑在Jenkins里或者做一个自动回归平台在接口开放程度和远程调用上总是别扭。TRex的服务端只要在测试机上跑起来Python客户端通过网络连接它脚本路径、用例组织、报告输出完全复用我们熟悉的pytest体系。1.3 什么项目真正适合把TRex接进Python自动化不是所有网络测试场景都需要上来就上TRex。我的判断标准有两条第一测试场景需要持续、高频、高吞吐的数据面流量而不是单纯发几个包验证连通性第二这项工作会被反复执行频率高到值得为它建立自动化用例。比如设备每个版本都要做转发性能回归这非常适合。再比如接口板卡变更后的系统级回归每天或每周要跑好几轮把TRex的Python调用封装成稳定的测试步骤后省下来的不只是手工操作时间更重要的是不同人操作时的口径差异被抹平了。如果只是临时抓个包、验证某个字段是否正确那用Scapy反而更轻。工具没有绝对好坏核心是让数据面流量可控可观测再把控制逻辑交给Python。2. 环境部署时的服务端-客户端边界2.1 先分清TRex服务端的Python环境和客户端API环境第一次搭TRex环境最大的误判就是以为TRex所有的Python依赖都集中在同一套环境里。实际上你至少会遇到两种“Python”一是TRex服务端启动时内部使用的运行环境它与数据面报文的处理调度有关二是用来跑自动化脚本的客户端环境这个环境需要独立安装Python3并导入TRex的Python库。我习惯把服务端放在专门的物理测试机上这台机器要有能被DPDK接管的网口。下载好TRex官方release包后解压到固定目录不需要做传统意义的“install”直接在里面启动服务端。关键点在配置文件和PCI地址识别这些做对了服务端就像一台安静的流量引擎监听4500和4501端口等待客户端连接。客户端环境可以放在同一台机器上也可以放在另外的跳板机或CI服务器上。我的建议是无论如何都用虚拟环境隔离避免污染系统Python。如果TRex包解压在/opt/trex/v3.04下客户端脚本通常要把/opt/trex/v3.04/automation/trex_control_plane/interactive加入PYTHONPATH或者是把该目录安装到当前Python环境。很多教程会直接写from trex_stl_lib.api import STLClient你要是没配好路径第一行就报ModuleNotFoundError还误以为这个库需要从PyPI另装实际上它就在TRex包的交互控制面里。2.2 配置文件、HugePages和网卡绑定这一步是环境里最容易劝退人的地方。TRex依赖DPDK启动前至少要让系统满足三个条件预留大页内存、把物理网卡从内核驱动解绑并交给DPDK接管、CPU预留出足够的处理核。先说HugePages。TRex在高速收发时需要减少TLB miss普通4KB分页对大规模内存池不友好。典型做法是给2MB大页预留几GB空间。比如一台16GB内存的测试机预留4GB给HugePages通常够跑两个10GbE端口的常规测试。命令大致是sudo sysctl -w vm.nr_hugepages2048这个值是2MB大页的数量2048个刚好约4GB。想让配置重启后仍然生效需要把它写进/etc/sysctl.conf。分配完之后用grep HugePages_Total /proc/meminfo确认如果Total是0优先检查系统内存是否充足以及是否有其他进程占用了大页。再说网卡绑定。TRex包内自带dpdk_setup_ports.py这类辅助脚本可以查看当前网卡状态并把指定的PCI设备绑定到vfio-pci驱动。我的经验是优先用vfio-pci而不是老文档里常见的igb_uio后者需要单独编译内核模块在较新的内核上很容易因为工具链问题失败。绑定前一定要确认被绑定的网口不是你的管理口否则你会瞬间失去SSH连接。查看PCI地址可以用lspci | grep Ethernet最后会出现类似0000:03:00.0这样的地址。绑定命令粗略如下cd /opt/trex/v3.04 sudo ./dpdk_setup_ports.py -s sudo ./dpdk_setup_ports.py -b vfio-pci -i 0000:03:00.0 -i 0000:03:00.1绑定成功后系统里看不到对应的普通网卡接口名这是正常的说明它已被用户态驱动接管。接下来需要写/etc/trex_cfg.yaml告诉TRex哪个PCI端口参与测试、需要多少个CPU核。不同TRex版本的配置字段有差别下面是一个常见的精简示例实际以官方包里的样例配置为准port_limit: 2 version: 2 interfaces: - 0000:03:00.0 - 0000:03:00.1 platform: master_thread_id: 2 latency_thread_id: 1启动服务端时在TRex目录下执行sudo ./t-rex-64 -i。等日志稳定出现端口UP之类的信息后再打开另一个终端从客户端连接测试。这样“服务端跑在物理机上、客户端跑在脚本环境里”的边界就清晰了。2.3 第一个“自证清白”的验收脚本环境到位后我习惯先跑一个最小的Python脚本验证数据通路。这个脚本不做复杂断言只做三件事连接服务端、获取端口信息、向某个端口发一小段流量并读取统计。import sys sys.path.insert(0, /opt/trex/v3.04/automation/trex_control_plane/interactive) from trex_stl_lib.api import STLClient, STLStream, STLTXSingleBurst, STLPktBuilder from trex_stl_lib.api import Ether, IP, UDP, RawLoad client STLClient(server127.0.0.1) client.connect() client.acquire(ports[0, 1], forceTrue) print(port info:, client.get_port_info(ports[0, 1])) pkt ( Ether(dst00:11:22:33:44:55) / IP(src10.0.0.1, dst10.0.0.2) / UDP(sport1234, dport5678) / RawLoad(bTRex python smoke) ) stream STLStream( packetSTLPktBuilder(pktpkt), modeSTLTXSingleBurst(pps1000, total_pkts10000), ) client.clear_stats(ports[0]) client.add_streams(stream, ports[0]) client.start(ports[0]) client.wait_on_traffic(ports[0]) results client.get_stats(ports[0]) print(opackets:, results[0][opackets]) client.reset(ports[0, 1]) client.release() client.disconnect()这个脚本里的pps1000只是验证控制通路和统计通路是否正常不要把它当成真实性能。如果你看到端口0的opackets为10000说明TRex服务端成功接收了客户端指令报文已经从物理口发出。到这一步Python自动化环境就算彻底打通了。3. Python控制面核心把“打流”拆成可控对象3.1 连接、端口、流、统计四个层面要理清TRex的Python库从设计上看并不复杂但代码里出现的高频对象特别多STLClient、STLStream、STLPktBuilder、STLTXSingleBurst、STLFlowLatencyStats还有一整套ports和stats的概念。我第一次写脚本时也转过弯到底哪个对象对应“发什么包”哪个对应“怎么发”哪个对应“发完怎么看结果”。后来我总结成四层通信层STLClient负责与TRex服务端通信建立连接、获取端口、获取统计都从它出发。端口层TRex服务端上TCP/IP栈之外的数据端口通常对应物理网卡上的一个口。所有流量都绑定在端口上。流层STLStream是核心概念它由报文内容STLPktBuilder和发送模式如STLTXSingleBurst组成。一个端口上可以叠加多个流也可以一个流打满所有带宽。统计层get_stats()返回每个端口的收发包数、字节数、错误数等测试用例的断言基本都基于这里的数据。想真正理解这套自动化不能只停留在“调用API”的层面。TRex借鉴了传统网络测试仪的设计思路控制面和数据面彻底分离。Python客户端是控制面负责编排“何时在哪个端口发什么流”TRex服务端和DPDK驱动的网卡是数据面负责实际报文处理。中间用TCP连接交互所以你的Python脚本完全可以跑在远端机器上只要网络能访问TRex服务端的控制端口。3.2 直连回环场景下的完整代码走读假设被测设备是一台交换机TRex的端口0接交换机入口端口1接交换机出口想验证交换机在100Mpps转发下没有丢包。这里“两个端口”不是回环连到同一台TRex的两个口而是TRex负责制造流量和回收流量被测设备负责转发。用Python表达这个场景步骤大概是from trex_stl_lib.api import ( STLClient, STLStream, STLTXSingleBurst, STLPktBuilder, Ether, IP, UDP, RawLoad ) LEN_64 b\x00 * 26 def build_stream(): pkt ( Ether(dst00:aa:bb:cc:dd:ee) / IP(src192.168.1.1, dst192.168.1.2) / UDP(sport1024, dport2048) / RawLoad(LEN_64) ) # 这里实际包长会超过64字节真实场景要按线卡要求精确填充 return STLStream( packetSTLPktBuilder(pktpkt), modeSTLTXSingleBurst(pps1000000, total_pkts100000000), ) client STLClient(server192.168.10.20) client.connect() client.acquire(ports[0, 1], forceTrue) client.reset(ports[0, 1]) client.clear_stats(ports[0, 1]) client.add_streams(build_stream(), ports[0]) client.start(ports[0]) client.wait_on_traffic(ports[0]) stats client.get_stats(ports[0, 1]) port0_sent stats[0][opackets] port1_recv stats[1][ipackets] print(fport0 sent: {port0_sent}) print(fport1 recv: {port1_recv}) print(floss ratio: {1 - (port1_recv / port0_sent):.6f}) client.reset(ports[0, 1]) client.release() client.disconnect()这段代码要说明两个容易被忽略的点。第一wait_on_traffic一定要放在start之后它会让Python客户端阻塞到指定端口的流量全部发完。如果不等待就去读统计可能只读到一部分数据。第二reset放在脚本末尾很关键它清空端口上的流配置和统计避免影响下一次用例执行。至于丢包率我通常按“端口1收到数除以端口0发出数”来算。现实中还要考虑被测设备是否在转发过程中主动改动了MAC或VLAN以及返程流量会不会也走到TRex。如果你的拓扑是单向转发上面这个算法没问题如果流量会回来必须画清楚收发包计数对应哪一段链路。3.3 不要等测试跑完才看延迟延迟统计流要单独叠加很多网络设备测试既要看吞吐丢包也要看转发时延。TRex默认统计不会给出每个报文的时延除非你在这个流上挂了延迟统计组件。我的做法是在后台压测流之外额外叠加一条小流的STLFlowLatencyStats让它生成带时间戳的探针包从而观测在高负载状态下报文的时延变化。示例片段如下from trex_stl_lib.api import STLFlowLatencyStats latency_stream STLStream( packetSTLPktBuilder( pktEther(dst00:aa:bb:cc:dd:ee) / IP(src10.0.0.1, dst10.0.0.2) / UDP(sport1234, dport4321) ), modeSTLTXSingleBurst(pps1000, total_pkts10000), flow_statsSTLFlowLatencyStats(pps1000), )执行完流量后再单独调get_latency_stats或get_stats中与延迟相关的字段。注意延迟流本身的pps不宜太高否则探针流量占比过大会影响被测设备缓冲行为结果反而失真。延迟测试最怕的是“只在压力结束后测一下”。一个网络设备在队列空闲时延迟很低不代表在队列堆满时仍然低。所以延迟统计流必须和后台压力流同时运行用后台流量把被测设备打到高负载再用低速率探针测延迟这样才能看出真实转发能力。4. 把调试脚本“升格”为用例套件4.1 用conftest管理TRex连接连接成本放在哪一层写自动化用例最忌讳的就是每个测试函数都自己STLClient(...)连接一次。TRex服务端的连接建立本身不算太重但如果用例量一多反复连接、断开、申请端口会让整个回归跑得拖沓还容易因为端口未释放而相互干扰。我用pytest组织用例时会写一个conftest.py把连接管理做成session级别的fixture。比如这样import pytest from trex_stl_lib.api import STLClient def pytest_addoption(parser): parser.addoption(--trex-server, actionstore, default127.0.0.1) pytest.fixture(scopesession) def trex(request): server request.config.getoption(--trex-server) client STLClient(serverserver) client.connect() client.acquire(ports[0, 1], forceTrue) client.reset(ports[0, 1]) yield client client.reset(ports[0, 1]) client.disconnect()这样整个测试会话只建立一次控制连接每个用例都复用trex这个fixture。但要注意acquire使用forceTrue是有风险的。如果另一套自动化也在使用同一个TRex服务端强占端口会把别人的任务打断。更稳妥的方式是forceFalse抢不到锁就让用例失败避免多个任务互相踩踏。实际上我们会把TRex测试机作为专用节点不在上面同时跑多个不相干的任务这时forceTrue可以接受。4.2 在用例层断言什么才有效写自动化用例时我见过很多人把所有信息都打印出来然后在日志里肉眼判断通过失败。这是得不偿失的。Python自动化最大的价值之一就是可以把判定条件写进代码里让结果清晰可读。针对网络转发测试我习惯把断言分成几类丢包率断言比如转发后丢包率必须低于0.001%。吞吐断言比如在指定包长下转发速率必须高于某条pps阈值。延迟断言比如平均延迟不超过100微秒或P99延迟不超过某个值。错误计数断言端口统计里的CRC错误、超长包、短包数量必须为0。下面是一个简单的用例示例def test_forward_no_loss_on_64_byte(trex, build_burst_stream): port_sent 0 port_recv 1 trex.clear_stats(ports[port_sent, port_recv]) stream build_burst_stream(pkt_size64, pps1000000, total_pkts10000000) trex.add_streams(stream, ports[port_sent]) trex.start(ports[port_sent]) trex.wait_on_traffic(ports[port_sent]) stats trex.get_stats(ports[port_sent, port_recv]) sent stats[port_sent][opackets] received stats[port_recv][ipackets] loss_ratio 1 - (received / sent) assert received sent, floss detected: {sent - received} pkts assert loss_ratio 0.00001注意这里packet的内容要由build_burst_stream根据包长参数动态构造实际中不能为了凑64字节而随便填充不合法字段。被测设备收到错误的二层校验或长度不对的报文计数结果会有很大偏差。4.3 把单个“打流”扩成可回归的套件单条流能验证“通不通”验证不了“稳不稳”。真正有价值的回归套件需要覆盖不同包长、不同速率模型、不同突发行为之间的组合。我的设计思路是用参数化用例把“64字节小包线速”、“128字节混合包长”、“512字节突发”、“1518字节大包”都变成独立测试项让pytest可以单独执行某一个场景也可以全部执行。举一个简化的参数化思路import pytest PACKET_SIZES [64, 128, 512, 1518] pytest.mark.parametrize(pkt_size, PACKET_SIZES) def test_no_loss_under_line_rate(trex, build_burst_stream, pkt_size): ...参数化之后报告里会自动列出每个包长场景的通过情况。哪个包长出了问题一眼就能定位。不要小看这种“笨功夫”网络性能回归里最常见的问题恰恰是某个特定包长在某种速率模型下触发设备内部队列异常不是所有流量都会失败的。5. 把TRex自动化塞进无人值守的流水线5.1 TRex专用机应该作为自托管节点而不是跑在普通CI容器里第一次把TRex自动化接到CI时我踩过一个很大的坑想用共享的Jenkins节点跑结果每次构建的宿主机不固定有的机器根本没有DPDK网卡。后来才把测试拓扑收敛到一台固定物理机上然后在Jenkins里给这台机器打上专用标签只有带这个标签的任务才会调度过去。在流水线脚本里可以这样约束pipeline { agent { label trex-dpdk-host } stages { stage(network regression) { steps { sh cd ${WORKSPACE} python3 -m venv .venv . .venv/bin/activate pip install -r automation/requirements.txt pytest tests/network \ --trex-server 192.168.10.20 \ --junitxmlreports/junit.xml \ --htmlreports/report.html } } } post { always { junit reports/junit.xml publishHTML target: [ reportDir: reports, reportFiles: report.html, reportName: TRex Regression Report ] } } }还有一个很容易被忽略的问题TRex服务端启动时需要root权限绑定DPDK网卡也需要root但流水线里的测试进程不应该一直用root跑。折中方案是让服务端和网卡绑定都提前以系统服务方式启动好自动化脚本只通过网络客户端访问这样只需要普通权限就能执行pytest。5.2 超时、异常和现场保留网络回归测试一旦跑起来经常需要很长时间有些拓扑下的长稳场景一跑就是几小时。CI里的构建超时、无人值守时的异常处理都要提前设计。我比较常用的做法是给pytest加超时插件给单个用例设一个明显大于正常执行时间但又能阻止无限挂起的阈值。执行完毕后不管成功失败都要把TRex端口的状态和统计信息收集下来作为后续排查依据。pytest tests/network --timeout 600 --trex-server 192.168.10.20另外真实场景里DUT也可能异常比如设备死机、链路断掉。只靠TRex的丢包断言可能半天才报错导致流水线长时间空转。更稳的办法是给用例设置前置检查在每条测试执行前发一小段探测流量确认链路还是通的不通就快速失败而不是执行一个小时的压测后才发现被测设备已经挂了。5.3 报告别只贴“通过/失败”要留原始指标CI报告如果只有红绿状态对网络测试的排障帮助有限。我建议把TRex返回的关键统计字段写入一个机器可读的JSON或CSV再拼到HTML报告里。比如端口发送包数、接收包数、丢包率、延迟平均值、P99值、运行时长这些数据在判定失败后能直接告诉你“瓶颈是丢包还是时延”不用重新跑一遍。import json def dump_stat_report(results, path): with open(path, w) as f: json.dump(results, f, indent2, ensure_asciiFalse)在Jenkins的post阶段把这些JSON作为构建附件保留。多次构建之间的趋势可以后续接入时序数据库做展示。很多人觉得网络测试自动化只要把“测试跑起来”就够了实际上可观测性才是无人值守能不能落地的关键。没有人会盯着每一个构建报告必须自己会说话。6. 实际运行一段时间后我最想提醒的几个坑6.1 多轮用例后端口状态异常多半是reset不够彻底用pytest跑大量流模型用例后偶尔会遇到某个用例开始时报端口被占用或流表添加失败。最常见的根因是上一条用例没有把端口上的流清干净。start发出的流虽然结束了但流定义还残留在端口配置里。所以我在每个用例的teardown和fixture的结束部分都强调reset(ports)甚至在同一次测试会话里多次调用也没有问题。它不会把DPDK网卡搞挂只是把端口上所有流配置、统计信息清空让下一个用例有一个干净的起点。另外还有一种情况是服务端在长时间跑批后出现core占用过高或端口假死。如果服务端是用./t-rex-64 -i这种交互模式启动的长时间挂在那里容易和终端会话绑定。后来我改成用--no-watchdog之类参数或者干脆做systemd服务托管避免终端断开导致服务端被SIGHUP干掉。具体参数以你所用TRex版本的帮助为准核心思路就是让它成为一个常驻后台服务。6.2 “假丢包”先