如何在香港服务器运行 RHEL 9 时,结合 OpenShift 与 SR‑IOV 网络,提升容器化应用带宽?

凌晨1点40,香港将军澳机房的客户紧急反馈,说他们的容器化行情引擎在港区跨机柜吞吐打不满 25G,延迟还偶有抖动。既要 OpenShift 的调度弹性,又要接近裸金属的带宽与时延——只靠 OVN-Kubernetes 的 overlay 肯定不够。最后我们定了方案:在 RHEL 9 裸机上做 OpenShift(带 Multus),把关键业务网卡走 SR‑IOV,在容器里拿到近似直通的链路。
那晚我用 KVM 推着一箱 Intel XXV710、Mellanox ConnectX‑5 的备件穿过冷通道,想着两个问题:
OpenShift 的 SR‑IOV Operator 在 RHEL 9 Worker 上的边角如何抹平?
带 VLAN 的多租隔离、MTU、VF Trust 与 Spoof Check 在香港这边的交换机策略下会不会“打架”?
1. 目标与边界
目标
- 把关键容器网卡改走 SR‑IOV,单流吞吐接近线速(25G),P99 延迟低且稳定。
- 保持与 OpenShift/Multus 的生态兼容:命名空间隔离、弹性调度、YAML 即基础设施。
- 预留两种模式:netdevice(内核网络栈)与 vfio-pci(DPDK/用户态)。
边界
控制面仍走 OVN-Kubernetes/主机默认网卡;SR‑IOV 只承载东西向/南北向的高速业务面。
不引入专有硬件卸载(例如 SmartNIC 上的交换功能),尽量通用。
2. 现场环境与清单(BOM)
2.1 机柜/链路拓扑(简化)
[Leaf A]====(25G LACP)====[RHEL9 Worker-01]
[Leaf B]====(25G LACP)====[RHEL9 Worker-02]
...
交换机:Arista 7xxx(等价的 Cisco/Juniper 也没问题),端口为 25G,支持 LLDP、LACP、VLAN trunk。
上联到 HKIX 与专线边界,客户侧 VLAN:200/201/202。
2.2 服务器硬件
| 角色 | 型号 | CPU | 内存 | 系统盘 | 高速网卡(PF) | 驱动 | BIOS 要点 |
|---|---|---|---|---|---|---|---|
| Worker | Dell R650 | 2×Intel® Xeon® 6348 | 256GB | 2×960GB SSD | 2×Intel XXV710(25G)/ 1×Mellanox ConnectX‑5(25G) | i40e/mlx5_core |
SR‑IOV=Enabled, VT‑d/IOMMU=Enabled, NUMA=Enabled |
现场建议:XXV710 更“皮实”,Mellanox 在 DPDK 模式更强;香港机房里 XXV710 的备件也更好找。
2.3 软件版本
- OS:RHEL 9.3(kernel 5.14 系列)
- 容器平台:OpenShift 4.14(RHEL Worker 节点)
- CNI:OVN-Kubernetes + Multus;SR‑IOV Network Operator(openshift-sriov-network-operator)
- 测试工具:iperf3, qperf, pktgen, tcpdump, ethtool。
注:版本不是唯一解,只要 SR‑IOV Operator 与内核/驱动匹配即可。
3. 原理快述:为什么 SR‑IOV 能提速?
SR‑IOV 把一张物理功能(PF)的网卡划分成多个虚拟功能(VF),每个 VF 都有独立的队列和硬件资源,容器/Pod 直接拿到 VF 网口,绕开 overlay/OVS 的数据面,减少内核路径和转发开销。
在 OpenShift 里,Multus 让 Pod 可以拥有多张网卡,普通业务继续走 Pod 默认网(eth0),而大流量数据面走 SR‑IOV(net1)。
模式:
netdevice:VF 仍走内核网络栈,兼容性好,调试简单。
vfio-pci:把 VF 绑给 VFIO,用户态(DPDK)拉包,吞吐与时延更进一步。
4. RHEL 9 基础准备(裸机)
4.1 BIOS/固件
开启:SR-IOV、Intel VT-d/AMD-Vi、NUMA。
如有“PCIe ARI/ACS”,尽量打开,避免 IOMMU 分组过粗。
4.2 内核参数
# /etc/default/grub(追加或合并)
GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt spectre_v2=on mitigations=auto"
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
4.3 驱动与网络工具
yum -y install ethtool lshw pciutils tcpdump iproute tuned-profiles-cpu-partitioning
modprobe i40e || true
modprobe mlx5_core || true
4.4 验证 SR‑IOV 能力
lspci | egrep -i "(ethernet|network)"
ethtool -i ens1f0 | egrep "driver|version|firmware"
# 确认 Similar to: supports-sriov: yes
5. OpenShift 侧准备
5.1 安装 SR‑IOV Network Operator
oc get ns openshift-sriov-network-operator || oc create ns openshift-sriov-network-operator
# OperatorHub 安装(Web 控制台)或 CLI:
oc apply -f - <<'EOF'
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: sriov-network-operator-sub
namespace: openshift-sriov-network-operator
spec:
channel: stable
name: sriov-network-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
EOF
等待 sriov-network-operator、sriov-device-plugin、sriov-cni 等组件 Ready。
5.2 为节点打标(选择哪些节点开 SR‑IOV)
# 只让 Worker-01/02 参与 SR‑IOV
oc label node worker-01 sriov.node=true --overwrite
oc label node worker-02 sriov.node=true --overwrite
6. 划分 VF:SriovNetworkNodePolicy(关键)
小技巧:先把 PF 从 Bond/LACP 中解耦再划 VF;否则 echo sriov_numvfs 可能报忙。
提醒:如果你一开始只想验证,可以先划 4 个 VF;上线前再扩到 16/32。
# policy-intel25g.yaml
apiVersion: sriovnetwork.openshift.io/v1
kind: SriovNetworkNodePolicy
metadata:
name: policy-intel25g
namespace: openshift-sriov-network-operator
spec:
resourceName: intelnics # 将来 Pod 里用 openshift.io/intelnics 申请
nodeSelector:
sriov.node: "true" # 仅对打标节点生效
numVfs: 16 # 每个 PF 划 16 个 VF
mtu: 9000 # 建议 jumbo,具体看园区网络是否放通
linkType: ethernet
isRdma: false # 需要 RDMA 时改 true(Mellanox 常用)
deviceType: netdevice # 或 vfio-pci(用于 DPDK 模式)
nicSelector:
pfNames:
- ens1f0 # 按实际 PF 名称
- ens1f1
spoofChk: "off" # 可选:关闭防欺骗,配合 Trust 使用
trust: "on" # 可选:允许 VF 修改自身 MAC/VLAN 等
应用并观察状态:
oc apply -f policy-intel25g.yaml
watch -n1 oc get SriovNetworkNodeState -n openshift-sriov-network-operator
# 等待对应 node 的 status.interface 数组里出现 VF 列表与 MTU=9000
现场坑 1:MTU 不一致。交换机端口 trunk 的原生 VLAN + QOS 队列有时会把 MTU 限到 9200;你设 9000 没问题,但遇到 overlay 的 50-100 字节开销时,实际有效 MTU 会掉。建议机房侧端到端 9216 或更高,并用 ping -M do -s 8972 验证。
7. 定义 SR‑IOV 业务网络(SriovNetwork)
我们为每个租户/业务划一个 VLAN:
# sriov-net-a.yaml
apiVersion: sriovnetwork.openshift.io/v1
kind: SriovNetwork
metadata:
name: net-a
namespace: openshift-sriov-network-operator
spec:
resourceName: intelnics
networkNamespace: demo # 会在 demo 命名空间里创建 NAD
vlan: 200 # 交换机 trunk 放通 200
ipam: |
{
"type": "host-local",
"ranges": [[{"subnet": "10.200.0.0/24"}]],
"routes": [{"dst": "0.0.0.0/0"}],
"dataDir": "/var/lib/cni/networks"
}
创建业务命名空间并应用:
oc create ns demo || true
oc apply -f sriov-net-a.yaml
oc get network-attachment-definition -n demo
现场坑 2:VLAN 模式。有同事把 vlan 当“trunk 列表”填了多个值——SR‑IOV CNI 这里填的是单个接入 VLAN。要 trunk 多 VLAN,请在交换机与 VF Trust 配合下,用 DPDK/内核在容器里再做 VLAN 子接口。
8. 在 Pod 里消费 VF:两种模式
8.1 netdevice(内核栈)示例
# pod-iperf-server.yaml
apiVersion: v1
kind: Pod
metadata:
name: iperf-srv
namespace: demo
annotations:
k8s.v1.cni.cncf.io/networks: '[{"name":"net-a","namespace":"demo","interface":"net1"}]'
spec:
nodeSelector:
sriov.node: "true"
containers:
- name: srv
image: networkstatic/iperf3
command: ["/bin/sh","-c","ip a && iperf3 -s"]
resources:
requests:
openshift.io/intelnics: "1"
limits:
openshift.io/intelnics: "1"
---
# pod-iperf-client.yaml
apiVersion: v1
kind: Pod
metadata:
name: iperf-cli
namespace: demo
annotations:
k8s.v1.cni.cncf.io/networks: '[{"name":"net-a","namespace":"demo","interface":"net1"}]'
spec:
nodeSelector:
sriov.node: "true"
containers:
- name: cli
image: networkstatic/iperf3
command: ["/bin/sh","-c","sleep 3; ip -br a; iperf3 -c 10.200.0.10 -P 4 -t 60"]
resources:
requests:
openshift.io/intelnics: "1"
limits:
openshift.io/intelnics: "1"
部署前,把 server Pod 的 net1 IP(示例是 10.200.0.10)换成实测获得的地址。
8.2 vfio-pci(DPDK)示例
将 SriovNetworkNodePolicy 的 deviceType 改为 vfio-pci 并 oc apply,Operator 会自动短暂重启对应节点的 SR‑IOV 组件(生产上要先 drain)。Pod 里通过 dpdk-devbind.py 绑定 VF,应用自行跑 DPDK(如 VPP/DPDK App)。
# 示例:给 Pod 以 VFIO 直通的 VF
apiVersion: v1
kind: Pod
metadata:
name: dpdk-pod
namespace: demo
annotations:
k8s.v1.cni.cncf.io/networks: '[{"name":"net-a","namespace":"demo","interface":"net1"}]'
spec:
nodeSelector:
sriov.node: "true"
containers:
- name: app
image: quay.io/yourorg/dpdk:22.11
securityContext:
capabilities:
add: ["IPC_LOCK","SYS_RESOURCE","NET_ADMIN","SYS_RAWIO"]
command: ["/bin/sh","-c","dpdk-devbind.py --status; sleep infinity"]
resources:
requests:
openshift.io/intelnics: "1"
limits:
openshift.io/intelnics: "1"
现场坑 3:HugePages。DPDK/vfio-pci 模式通常需要 HugePages:
oc edit machineconfigpool worker
# 或者直接在节点:
echo 16384 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
9. 性能实测:SR‑IOV 之前与之后
测试方法:iperf3 多并发(4~8 流),MTU=9000,服务器与客户端分别落在 worker-01/02,同一租户 VLAN(200)。
| 场景 | 模式 | 单向吞吐(Gbps) | 双向(Gbps) | P99 时延(µs) | Pod CPU(单核%) |
| 基线:OVN-K + 默认 Pod 网 | overlay | 8.5–10.2 | 16–18 | 350–520 | 65–80 |
SR‑IOV(netdevice) |
直连 | 22.5–24.2 | 45–47 | 120–180 | 30–45 |
SR‑IOV(vfio-pci + DPDK) |
直连 | 24.8–25.0 | 49–50 | 70–110 | 20–30 |
解释:25G 环境里,netdevice 已可接近线速;DPDK 在小包与低时延场景更稳。
10. 运维细节:我在现场踩过的坑与解法
VF 划分失败(echo 报 Busy)
现象:echo 16 > /sys/class/net/ens1f0/device/sriov_numvfs 报 Device or resource busy。
原因:PF 处在 bond/LACP 或被 OS/OVS 占用。
解法:先 echo 0 > .../sriov_numvfs,解绑 bond,down 口,再划分;在 OpenShift 里交由 Operator 执行,事前把 PF 从其他网络对象中移除。
MTU 不一致导致间歇丢包
现象:iperf3 带宽波动,tcpdump 见 frag needed。
解法:统一交换机端口、PF、VF、容器内 net1 的 MTU(例如 9000),用 ping -M do -s 8972 端到端验证。
VLAN 与 Trust/SpoofChk 冲突
现象:容器里改 MAC/VLAN 不生效或被交换机挡下。
解法:在 SriovNetworkNodePolicy 开 trust: on、spoofChk: off,交换机端允许 VF 侧切换,严格计费环境则只在特定租户放开。
资源名与调度
现象:Pod 申请 openshift.io/intelnics 失败。
解法:确认 resourceName 与 Pod requests/limits 前缀一致(openshift.io/),并确认该节点 SriovNetworkNodeState 中数量足够。
DPDK 模式下的 NUMA 与 IRQ 亲和
现象:吞吐抖动,CPU 飙升。
解法:让 Pod 绑核到同一 NUMA,tuned 的 cpu-partitioning,irqbalance 关闭或调亲和;DPDK --lcores/--socket-mem 与 VF 所在 NUMA 对齐。
运维可观测性
建议:给 SR‑IOV 网卡打 Prometheus exporter(如 node_exporter Textfile 或 eBPF 指标),记录 per‑VF 的丢包/队列拥塞,配合 ethtool -S 与交换机 show int counters 交叉验证。
升级变更窗口
提醒:deviceType 从 netdevice → vfio-pci 会触发 VF 绑定变化,需要节点 drain;把策略分多份(每 PF 一份),逐步灰度。
11. 生产最佳实践(我总结的 10 条)
- 按租户/业务划 SriovNetwork,明确 VLAN,避免共享 VF 池导致抢占。
- Node 池分层:有 SR‑IOV 的专用计算池,调度关键业务;普通节点继续走 overlay。
- 端到端 MTU 设计:统一 9000/9126,不让任何一跳“掉队”。
- 双栈/IPv6:香港双栈越来越常见,IPAM 直接规划 v4/v6 双栈,避免二次返工。
- 链路冗余:PF 成对(ens1f0/ens1f1),每个 PF 划相同数量 VF;业务侧用 ECMP/多 IP 会话做容灾。
- 带宽配额:交换机对 VF 口做队列与 policer,OpenShift 侧在 Pod/Namespace 设 BandwidthLimit(TC)。
- 基准测试留档:每次变更前后都跑相同 iperf3、包长与并发参数,输出保存在 ConfigMap/对象存储。
- CrashDump 兜底:高频包场景建议开启 kdump,留存复盘。
- Audit:对 SR‑IOV CRD 的变更走 GitOps,YAML 走 MR/Code Review。
- 跨地域延迟:香港–深圳/上海跨域链路,SR‑IOV 无法改变物理 RTT,必要时上边界加 TCP 优化或 QUIC。
12. 完整样例(可直接套入实验环境)
假设 PF 为 ens1f0/ens1f1,VLAN=200,命名空间 demo。
# 1) 安装 SR‑IOV Operator(略,见上文)
# 2) 打标节点
oc label node worker-01 sriov.node=true --overwrite
oc label node worker-02 sriov.node=true --overwrite
# 3) 应用策略与网络
oc apply -f policy-intel25g.yaml
oc apply -f sriov-net-a.yaml
# 4) 部署测试 Pod
oc apply -f pod-iperf-server.yaml
oc apply -f pod-iperf-client.yaml
# 5) 验证网卡与带宽
oc rsh -n demo iperf-srv ip a
oc rsh -n demo iperf-cli iperf3 -c <server-ip> -P 4 -t 60
13. 运维复盘:配置与结果“对账表”
| 项 | 现场值 | 期望/标准 | 结果 |
| BIOS SR‑IOV/VT‑d | Enabled | Enabled | OK |
| MTU(端到端) | 9000 | ≥9000 | OK |
| PF→VF 数量 | 16/口 | 16/口 | OK |
| VLAN 放通 | 200 trunk | 200 trunk | OK |
| Pod 资源申请 | openshift.io/intelnics=1 | =1 | OK |
| 吞吐(单向) | 24.2 Gbps | ≥23 Gbps | OK |
| P99 延迟 | 120–180 µs | ≤200 µs | OK |
凌晨 3:20,机房空调还是那股熟悉的冷味。最后一轮 iperf3 跑完,曲线稳稳贴在 24+Gbps 上,Grafana 的 CPU 也安静了许多。我们把变更单签完,客户说“像把车道从城市支路并回了高速”。
我懂这句话的意思:OpenShift 负责交通规则与调度,SR‑IOV 负责让车道通畅。当你需要容器的弹性,又想要接近裸金属的网络性能,这条路,值得走一次。
如果你需要把这套方案适配到不同的 NIC(如 X710/XL710/ConnectX‑6)或不同的 VLAN/二层策略,改动的关键点只有三个:PF 名称、numVfs/MTU、VLAN/IPAM。其他,交给 Operator 与 YAML 即可。真有卡点,来机房吹一吹“海风”,我们把每个小按钮一一拨到位.