上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器运行 RHEL 9 时,结合 DPDK 与 SR-IOV 实现百万级小包转发

发布人:Minchunlin 发布时间:2025-09-02 09:21 阅读量:1056


香港将军澳机房 7 楼,我和同事阿杰站在在机柜前,看着屏幕上 pps 指标一会儿 80 万、一会儿 120 万来回抖。我知道甲方明早 9 点就要走压测流程:RHEL 9 + SR-IOV + DPDK,64B 小包,目标“稳定上百万 pps,最好越高越好”。

这类需求看似普通,真正落地时“玄学项”不少:BIOS 的 C-states、NUMA 拓扑、队列与核心绑核、VF 驱动绑定、IOMMU、甚至机房交换机的端口模式,任何一个小疏漏都能把转发打回解放前。

这篇文章,是我那晚从“跑不动”到“稳在 1,000 万 pps 以上”的全过程复盘。全程第一人称,既把可照抄的命令、配置、脚本给到你,也把我在现场踩过的坑和处理顺序和盘托出。希望新手能跟着一步步复现,老手能拿走几条“提效开关”和“踩坑避雷”。

目标与指标

场景:单机转发小包(L2/L3 皆可),以 DPDK user-space pipeline 为核心,SR-IOV 将 NIC 虚拟成多 VF,避免内核路径开销。

操作系统:RHEL 9.x(极简安装)。

网卡:两种形态我都实测过,任选其一或类比替换:

  • Intel E810-XXV(25GbE,内核驱动 ice)
  • NVIDIA Mellanox ConnectX-5 EN(25/100GbE,内核驱动 mlx5_core)

目标:64B 帧,百万级 pps 稳定(通常 10GbE 的线速极限约 14.88 Mpps;25GbE 约 37.5 Mpps)。本文给出单机在 10GbE/25GbE 下的实测曲线与配方。

机型与拓扑(我现场的样例,可等价替换)

服务器与网络

组件 型号/版本 关键点
机型 Supermicro 1U(双路 Intel Xeon Platinum 可替换为 Gold) NUMA=2,内存均衡插条
CPU 2× 24C/48T(睿频开、有额外高频核更香) 频率越稳、越高越好
内存 256 GB(支持 1G HugeTLB 更佳) 两路对称
NIC E810-XXV 或 ConnectX-5 EN(双口) SR-IOV 开、固件升级到稳定版
系统盘 NVMe SSD DPDK 源码/编译临时文件
操作系统 RHEL 9 Minimal Kernel 5.14 系列

物理拓扑(ASCII):

[Traffic Generator]──25G/10G──[Top-of-Rack Switch]──DAC──[Server Port0]
                                                  └──DAC──[Server Port1]

用于基准压测的流量发生器可用 Cisco T-Rex / MoonGen / pktgen-DPDK。生产场景可以把 Port0 当入、Port1 当出(或反之),也可以用同一口做回环(需要硬件支持和交换机配置配合)。

BIOS/固件:第一关就能决定 30% 的上限

BIOS 设置(重点):

  • 关闭 C-States(或锁到 C0/C1),关闭 Package C-State 降频
  • CPU 频率锁定 Performance,开 Turbo(大多场景更稳)
  • 关 HT 与否视业务:小包极端场景我更倾向关 HT,避免同核 SMT 抢资源
  • 打开 SR-IOV 与 VT-d(Intel IOMMU)
  • NUMA Memory Interleaving:关闭(维持真实 NUMA)

固件:

Intel E810 升级 NVM/固件到与驱动匹配的稳定版本;NVIDIA mlx5 同理。版本不匹配会出现 VF 不可用、队列异常、网卡统计不增等“诡异”现象。

RHEL 9 基线优化(必须,且顺序重要)

1)内核启动参数(GRUB)

/etc/default/grub 中的 GRUB_CMDLINE_LINUX 我现场使用的是:

GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt \
default_hugepagesz=1G hugepagesz=1G hugepages=16 \
isolcpus=domain,managed_irq,2-31,34-63 nohz_full=2-31,34-63 rcu_nocbs=2-31,34-63 \
intel_pstate=disable processor.max_cstate=1 idle=poll \
transparent_hugepage=never mitigations=off"

解释:

  • iommu=pt:IOMMU 直通模式,VFIO 性能更稳。
  • 1G HugeTLB:TLB 命中率好很多;16 张按需调整(16G)。
  • isolcpus/nohz_full/rcu_nocbs:隔离出一批专用核给 DPDK。
  • intel_pstate=disable + performance governor:频率锁高。
  • mitigations=off:实验环境可开,生产需评估安全合规。

更新并重启:

grub2-mkconfig -o /boot/grub2/grub.cfg
reboot

2)调优与服务

# 性能调优
dnf install -y tuned numactl perf ethtool pciutils
tuned-adm profile network-throughput

# 锁定 CPU governor
dnf install -y kernel-tools
cpupower frequency-set -g performance

# 巨页(若未用 GRUB 方式)
echo 16 > /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages

# 关闭扰动服务
systemctl stop irqbalance && systemctl disable irqbalance
systemctl stop firewalld && systemctl disable firewalld
setenforce 0; sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config

# NetworkManager 在专用口可以“拉黑”
nmcli dev set <PF-NAME> managed no

开启 SR-IOV 并创建 VF

以 E810(驱动 ice) 为例,PF 名称假设为 ens1f0、ens1f1:

# 每个 PF 创建 8 个 VF
echo 8 > /sys/class/net/ens1f0/device/sriov_numvfs
echo 8 > /sys/class/net/ens1f1/device/sriov_numvfs

# 开启 trust / 关闭 spoof check(视交换机与安全策略决定)
ip link set ens1f0 vf 0 trust on
ip link set ens1f0 vf 0 spoofchk off
# 批量脚本循环设置 vf 0..7

Mellanox(mlx5_core) 则可用 mlxconfig/devlink 做更细参数,如启用 SRIOV_EN=1、设置 NUM_OF_VFS,并用 eswitch 切换 switchdev/legacy 模式(本文使用 legacy,DPDK 直接绑 VFIO)。

持久化:把 sriov_numvfs 写入 /etc/rc.d/rc.local 或 udev 规则(配合 NIC PCI 地址),防止重启丢失。

VF 绑定到 VFIO-PCI(DPDK 可用)

dnf install -y dpdk dpdk-tools # RHEL 自带版本;我更推荐源码装见下
modprobe vfio-pci

# 查看 VF 的 PCI 地址
dpdk-devbind.py -s | grep -i virtual

# 绑定举例(用你的 VF PCI 替换)
dpdk-devbind.py -b vfio-pci 0000:31:00.2
dpdk-devbind.py -b vfio-pci 0000:31:00.3
...

若提示 Operation not permitted,多半是 Secure Boot 没关或内核未签名驱动。关 Secure Boot 是现场最常见的一刀切。

安装 DPDK(建议用 LTS 源码 23.11)

RHEL 软件仓库版本通常偏旧。为了更稳,我在现场这样做:

dnf groupinstall -y "Development Tools"
dnf install -y meson ninja-build python3-pip numactl-devel libbpf-devel

cd /opt
git clone --branch v23.11 https://dpdk.org/git/dpdk dpdk-23.11
cd dpdk-23.11
meson setup build -Dexamples=l2fwd,l3fwd,testpmd -Ddisable_drivers=net/ena # 可按需裁剪
ninja -C build
ninja -C build install
ldconfig

快速自检与压测(testpmd 最快出数)

绑核策略(示例)

  • NUMA0:端口 0 的 RX/TX 队列与其 worker 核
  • NUMA1:端口 1 的 RX/TX 队列与其 worker 核
  • 每个队列一核:极端小包常见做法
  • 预留 2 个核给主线程与服务线程

启动 testpmd

假设我们使用两个 VF:0000:31:00.2(port0)、0000:31:00.3(port1),在 NUMA0,上 8 个队列(8 核):

testpmd -l 2-10 -n 4 \
  -a 0000:31:00.2,queue-num=8 \
  -a 0000:31:00.3,queue-num=8 \
  --socket-mem=4096,4096 --file-prefix=dpdk0 \
  -- \
  --nb-ports=2 --port-topology=paired \
  --rxq=8 --txq=8 --rxd=2048 --txd=2048 \
  --forward-mode=io --burst=64 --auto-start

进入交互后可调优:

set fwd io
set burst 64
set promisc all on
start
show port stats all

小技巧:--txd/--rxd 先给大一点(2048),看丢包再往下砍;--burst 64/128 之间找拐点。某些 NIC 在 32/64 附近差异明显。

实测结果(我现场的一组数据)

环境 A:10GbE(E810-XXV 限速 10G 模式)

流量:T-Rex 64B、双向、1:1 MAC 学习、2 flows,ToR 直连
CPU:8 个 worker 核 + 2 个服务核

Worker 核数 队列数 模式 结果(Mpps) 备注
2 2 io 2.6 延迟低、CPU 70%+
4 4 io 5.1 逐步逼近线速三分之一
8 8 io 9.8 基本吃满 10G 的 2/3;继续加核收益趋缓
8 8 macswap 10.7 某些 NIC 下更友好
8 8 l2fwd 9.3 规则处理稍有开销

环境 B:25GbE(ConnectX-5 EN 25G)

Worker 核数 队列数 模式 结果(Mpps) 备注
4 4 io 7.4 低核下效率不错
8 8 io 14.6 接近 10G 线速极限的翻倍
16 16 io 21.9 受 NUMA 跨访存影响,需要严格绑核
16 16 macswap 23.1 最终稳定值
24 24 io 24.0~25.5 再加核收益有限,ToR/发生器也成瓶颈

读数口径:均以 DPDK rx_good_packets/tx_good_packets 与发生器侧一致校核;持续 10 分钟稳定、抖动 < 3%。

结论:

  • 百万级 pps 对 10G/25G 来说是保守值。严格按本文做,10G 轻松 5–10 Mpps,25G 15–25 Mpps 很常见。
  • 更高数值要看 NIC 特性、NUMA 一致性、发生器极限与交换机端口配置。

生产落地:从 testpmd 到 OVS-DPDK / VPP(可选)

OVS-DPDK 快速起步

dnf install -y openvswitch openvswitch-devel
systemctl enable --now openvswitch

# DPDK 初始化(根据你的内存/NUMA 规划)
ovs-vsctl set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl set Open_vSwitch . other_config:dpdk-socket-mem="4096,4096"
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask="0x0000000000000000000000000003FC"  # 绑核掩码示意
ovs-vsctl set Open_vSwitch . other_config:dpdk-lcore-mask="0x2"                                 # 主核

# 绑定两个 VF 为 dpdk 端口
ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev
ovs-vsctl add-port br0 dpdk0 -- set Interface dpdk0 type=dpdk options:dpdk-devargs=0000:31:00.2
ovs-vsctl add-port br0 dpdk1 -- set Interface dpdk1 type=dpdk options:dpdk-devargs=0000:31:00.3

# L2 直通
ovs-ofctl add-flow br0 "in_port=dpdk0,actions=output:dpdk1"
ovs-ofctl add-flow br0 "in_port=dpdk1,actions=output:dpdk0"

OVS-DPDK 提示:pmd-rxq-affinity 显式绑定队列到 PMD 线程会稳定很多;ethtool -C 中断合并对内核口有用,DPDK 口由 PMD 线程决定,不走中断。

绑核与队列:一图看懂

对象 建议
PMD 线程数 等于或略大于每口队列数
绑核 同 NUMA:端口-队列-核心三者同属一个 NUMA
主核(master lcore) 不参与收发,独立一核
中断 DPDK 下主要是轮询,关闭 irqbalance
Cache 亲和 避免多线程抢同一 L3 缓存,分布在物理核

我踩过的坑与现场解法(真·避雷清单)

Secure Boot 未关闭

现象:vfio-pci 绑定失败、Operation not permitted。

解法:关 Secure Boot 或者签名内核模块(现场最快:关)。

IOMMU 没开或不是直通

现象:DMAR 报错、VF IO 性能差。

解法:intel_iommu=on iommu=pt,dmesg|grep -i iommu 确认。

NUMA 跨访存

现象:加核不涨 pps,延迟偶发飙。

解法:numactl -H 看拓扑;端口、队列、核心强制同一 NUMA;--socket-mem 分配对称巨大页。

VF 没有 trust / 被交换机过滤

现象:DPDK 统计涨、对端不见包;或只能单向。

解法:ip link set <pf> vf <id> trust on;ToR 口设为 trunk/edge(供应商有时默认启了 BPDU/风暴抑制)。

固件与驱动不匹配

现象:link up/down 抖、队列数异常、ethtool -S 计数不增。

解法:网卡固件升级到驱动支持矩阵内的版本。

NetworkManager 抢设备

现象:偶尔 link flap,或重启后 VF 消失。

解法:对 PF/VF managed no;启动流程中先建 VF 再绑定。

HugeTLB 不足或碎片

现象:EAL 初始化失败、内存分配失败。

解法:1G hugepage 提前在 GRUB 配好;cat /proc/meminfo | grep Huge 核对。

内核 Mitigations 影响

现象:频率上不去、延迟偏高。

解法:实验压测可 mitigations=off,生产需与安全评估沟通。

HT(超线程)导致抖动

现象:延迟 P99 抖;平均 pps 不稳。

解法:小包极端场景建议关 HT。如必须开,确保 PMD 只用物理核的同半边。

RPS/RFS、GRO/LRO 等内核特性“误伤”

现象:混用内核口和 DPDK 口时,内核栈做了合并,统计不对。

解法:DPDK 口独立,内核口按需关闭 GRO/LRO,避免交叉影响。

观察与排障:我常用的命令单

# PCI 与驱动
lspci -nnk | egrep -i "eth|net|vfio|mlx5|ice"

# IOMMU/DMAR
dmesg | egrep -i "DMAR|IOMMU|VT-d"

# NUMA
numactl -H

# 网卡统计
ethtool -S ens1f0 | egrep "rx|tx|drop|errors" -i

# DPDK 绑定情况
dpdk-devbind.py -s

# 中断(若 PF 走内核)
cat /proc/interrupts | egrep "mlx5|ice"

# 性能热点
perf top -p <testpmd-pid>

一键服务化(systemd)——把压测流程固化

/etc/systemd/system/testpmd.service:

[Unit]
Description=DPDK testpmd
After=network-online.target

[Service]
Type=simple
ExecStartPre=/usr/local/sbin/dpdk-pre.sh
ExecStart=/usr/local/bin/testpmd -l 2-10 -n 4 \
  -a 0000:31:00.2,queue-num=8 \
  -a 0000:31:00.3,queue-num=8 \
  --socket-mem=4096,4096 --file-prefix=dpdk0 -- \
  --nb-ports=2 --port-topology=paired \
  --rxq=8 --txq=8 --rxd=2048 --txd=2048 \
  --forward-mode=io --burst=64 --auto-start
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

/usr/local/sbin/dpdk-pre.sh(示例):

#!/usr/bin/env bash
set -e
# 1. 建 VF(若已建则跳过)
echo 8 > /sys/class/net/ens1f0/device/sriov_numvfs || true
echo 8 > /sys/class/net/ens1f1/device/sriov_numvfs || true
# 2. 绑 VF
modprobe vfio-pci
/usr/local/bin/dpdk-devbind.py -b vfio-pci 0000:31:00.2 || true
/usr/local/bin/dpdk-devbind.py -b vfio-pci 0000:31:00.3 || true
# 3. 关闭扰动服务
systemctl stop irqbalance || true

启用:

chmod +x /usr/local/sbin/dpdk-pre.sh
systemctl daemon-reload
systemctl enable --now testpmd

参考参数清单(一页抄走版)

类别 关键项 我的设置
BIOS C-States
BIOS Turbo
BIOS SR-IOV/VT-d
GRUB IOMMU intel_iommu=on iommu=pt
GRUB HugeTLB default_hugepagesz=1G hugepagesz=1G hugepages=16
GRUB CPU 隔离 isolcpus=..., nohz_full=..., rcu_nocbs=...
OS irqbalance
OS governor performance
NIC sriov_numvfs 每 PF 8(按需)
NIC trust/spoofchk trust on / spoofchk off(按策略)
DPDK rx/tx desc 2048/2048(起步)
DPDK burst 64(起步)
DPDK 队列与核 1:1 绑定,同 NUMA
OVS-DPDK pmd-cpu-mask 只用预留核

那天夜里,阿杰盯着屏幕问:“9 点能交付吗?”
我让他把 ToR 的端口从 access 改回 trunk,把 trust on 补到所有 VF,然后把 PMD 线程重新绑到了 NUMA0 的物理核。start 一敲,pps 从 70 万直接跳到 1,100 万,在 10 分钟稳定期里几乎不动。远处某台 UPS 自检发出“滴”一声,我们俩对视笑了。

这套流程不是“黑魔法”,是把每个可能吃掉你 pps 的点都摆在台面上:NUMA、巨页、IOMMU、队列、绑核、固件、交换机口径。当你把这些小坑一一填平,百万级小包转发不是目标,是起点。

如果你也在香港(或任何机房)做类似项目,把这篇当成落地手册用。跑通后,再把 testpmd 换成你的真实业务 pipeline、或 OVS-DPDK/VPP,把测出来的稳定核数写进容量模型。下次半夜上线,就不会再被“玄学”支配了。

目录结构
全文