不买交换机也能组 40G 局域网:PVE 环形网络实战

random-pic-api

背景

40G 交换机不便宜,二手也要大几百。我手上有 4 台 PVE 节点(3 台 M920x + 1 台 NUC),每台装了一块迈络思(Mellanox,现属 NVIDIA)双口 40G 网卡,没买交换机,用光缆把 4 张卡串成一个环,给 Ceph 集群提供高速存储网络:

  1. Alpha port1 → Bravo port2
  2. Bravo port1 → Cobar port2
  3. Cobar port1 → NUC port2
  4. NUC port1 → Alpha port2

这样一来,每台节点都直连两台邻居。但问题是,Alpha 和 Cobar 之间没有直连,怎么通信?某台节点挂了,剩下的怎么绕路?

这篇把整个搭建过程走一遍,包括桥接网络配置、OSPF 动态路由、以及踩过的坑。

硬件清单

节点机器CPU内存40G 网卡
AlphaM920xi7-8700T64GBMellanox ConnectX-3 Pro MCX354A-FCBT
BravoM920xi7-8700T64GB同上
CobarM920xi7-8700T64GB同上
NUCIntel NUCi7-10710U64GBHP 544+FLR-QSFP

3 张迈络思 ConnectX-3 Pro MCX354A-FCBT 和 1 张 HP 544+FLR-QSFP,都是双口 QSFP+ 接口,支持 40/56GbE 以太网和 FDR InfiniBand。M920x 机身虽小,但有一个 PCIe x16 插槽(实际走 x8),装迈络思的半高卡没问题。ConnectX-3 是比较老的型号了,但 40G 跑满够用,二手价格也便宜。

连接用的是 QSFP+ DAC 直连线缆,两台之间直接插,不用经过交换机。

从 IB 到以太网

最开始玩的是 InfiniBand 模式,直连能达到 56Gbps。关于 ConnectX-3 Pro 的 IB 模式配置,可以参考 lzghzr 的这篇 Gist,里面有完整的固件刷写、驱动安装和 IB 网络配置步骤。

后来为了组以太网局域网,让 Ceph OSD 能走标准 TCP/IP 协议通信,我把所有网卡都切换到了以太网模式(Ethernet mode)。IB 模式虽然延迟更低、带宽更大,但上层软件生态支持有限,Ceph 在以太网下配置和排查都更方便。

网络拓扑

下面这张图是整个环形网络的逻辑结构。每台节点有两张 40G 口,分别连到左右两个邻居,形成一个闭合环。

40G 网络.drawio

物理连接确定后,逻辑上分成了 4 个点对点链路,每个链路用一个 /30 子网:

链路子网端点 A端点 B
Alpha ↔ Bravo10.0.1.0/30Alpha: 10.0.1.1Bravo: 10.0.1.2
Bravo ↔ Cobar10.0.2.0/30Bravo: 10.0.2.1Cobar: 10.0.2.2
Cobar ↔ NUC10.0.3.0/30Cobar: 10.0.3.1NUC: 10.0.3.2
NUC ↔ Alpha10.0.4.0/30NUC: 10.0.4.1Alpha: 10.0.4.2

为什么用桥接而不是直接给网卡配 IP

如果直接在物理网卡上配 IP,这些网卡就被 PVE 主机独占了,LXC 容器和 VM 用不了 40G 带宽。我的需求是容器和虚拟机也能跑在 40G 网络上,所以必须用 Linux Bridge。

每台节点创建两个桥接接口,每个桥接挂一个物理网口,IP 配在桥接上而不是物理网卡上。这样物理网卡对 LXC/VM 透明可用。

配置桥接网络

以 Alpha 节点为例,编辑 /etc/network/interfaces

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 物理网卡不配 IP,作为桥接成员
auto enp5s0f0
iface enp5s0f0 inet manual

auto enp5s0f1
iface enp5s0f1 inet manual

# vmbr0: 连接到 Bravo
auto vmbr0
iface vmbr0 inet static
address 10.0.1.1/30
bridge-ports enp5s0f0
bridge-stp off
bridge-fd 0

# vmbr1: 连接到 NUC
auto vmbr1
iface vmbr1 inet static
address 10.0.4.2/30
bridge-ports enp5s0f1
bridge-stp off
bridge-fd 0

bridge-stp off 是关闭生成树协议。环形拓扑里开 STP 会阻塞一个端口,浪费一半带宽。环路问题交给三层的 OSPF 去处理,不靠二层 STP。

其他节点的配置逻辑一样,区别只是 IP 和物理接口:

节点vmbr0(连接)IPvmbr1(连接)IP
Alphaenp5s0f0 → Bravo10.0.1.1/30enp5s0f1 → NUC10.0.4.2/30
Bravoenp5s0f0 → Cobar10.0.2.1/30enp5s0f1 → Alpha10.0.1.2/30
Cobarenp5s0f0 → NUC10.0.3.1/30enp5s0f1 → Bravo10.0.2.2/30
NUCenp5s0f0 → Alpha10.0.4.1/30enp5s0f1 → Cobar10.0.3.2/30

配置好后重启网络:

1
systemctl restart networking

启用 IP 转发

每个节点都要开启,否则无法为其他子网转发数据包:

1
2
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

为什么需要动态路由

环形拓扑里,Alpha 到 Cobar 有两条路:经过 Bravo(1 跳)或经过 NUC(1 跳)。静态路由只能写死一条路,而且每个节点要手动写到其他所有子网的路由,4 个节点就是一堆条目,换一个拓扑就得全改。

OSPF 能自动发现邻居、交换路由信息、在链路断开时重新收敛。FRRouting 是 PVE(Debian)上最常用的开源路由套件,直接 apt 装就行。

安装和配置 FRRouting

安装

1
apt update && apt install frr

启用 OSPF 守护进程

编辑 /etc/frr/daemons,找到 ospfd=no 改成:

1
ospfd=yes

配置 FRR

每台节点编辑 /etc/frr/frr.conf。以 Alpha 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
log syslog informational

router ospf
ospf router-id 10.0.0.1
network 10.0.1.0/30 area 0
network 10.0.4.0/30 area 0

interface vmbr0
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

interface vmbr1
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

几个关键点:

  • ospf router-id:每个节点必须不同,随便选一个不会冲突的 IP。不手动指定的话,OSPF 会自动选,但选出来的可能是你管理网段的 IP(比如 192.168.31.x),在邻居表里看起来很乱。
  • network ... area 0:宣告这个子网参与 OSPF。只宣告直连的子网,不要多也不要少。
  • ip ospf network point-to-point:把接口类型强制设为点对点。默认是广播模式,会搞 DR/BDR 选举,在点对点链路上完全没必要,还会拖慢收敛速度。

其他节点配置类似,区别只是 router-id 和宣告的子网:

Bravo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
log syslog informational

router ospf
ospf router-id 10.0.0.2
network 10.0.1.0/30 area 0
network 10.0.2.0/30 area 0

interface vmbr0
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

interface vmbr1
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

Cobar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
log syslog informational

router ospf
ospf router-id 10.0.0.3
network 10.0.2.0/30 area 0
network 10.0.3.0/30 area 0

interface vmbr0
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

interface vmbr1
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

NUC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
log syslog informational

router ospf
ospf router-id 10.0.0.4
network 10.0.3.0/30 area 0
network 10.0.4.0/30 area 0

interface vmbr0
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

interface vmbr1
ip ospf network point-to-point
ip ospf hello-interval 10
ip ospf dead-interval 40

启动 FRR

1
systemctl restart frr

验证

检查 OSPF 邻居

1
vtysh -c "show ip ospf neighbor"

点对点模式下,邻居状态应该直接是 Full/-,没有 DR/BDR 标记:

1
2
3
Neighbor ID     Pri State           Up Time         Dead Time Address         Interface
10.0.0.2 1 Full/- 10m28s 38.158s 10.0.1.2 vmbr0:10.0.1.1
10.0.0.4 1 Full/- 10m59s 38.858s 10.0.4.1 vmbr1:10.0.4.2

每台节点应该看到两个邻居(左邻右舍)。

检查路由表

1
ip route

Alpha 节点上应该能看到通过 OSPF 学到的路由:

1
2
3
4
10.0.1.0/30 dev vmbr0 proto kernel scope link src 10.0.1.1
10.0.2.0/30 via 10.0.1.2 dev vmbr0 proto ospf metric 20
10.0.3.0/30 via 10.0.4.1 dev vmbr1 proto ospf metric 20
10.0.4.0/30 dev vmbr1 proto kernel scope link src 10.0.4.2

注意 10.0.2.0/30(Bravo↔Cobar)和 10.0.3.0/30(Cobar↔NUC)都是通过 OSPF 学到的。Alpha 到 Cobar 的两条路同时存在,OSPF cost 相同,默认走等价多路径。

测试连通性

1
2
3
4
5
# Alpha 到 Cobar(没有直连,经 Bravo 或 NUC 中转)
ping 10.0.2.2

# 带 traceroute 看走哪条路
traceroute 10.0.2.2

带宽测试

直连链路和跨节点链路都测了一下。在 Cobar 上启动 iperf3 服务端,从 Alpha 发起测试,8 个并发流跑 60 秒:

1
2
3
4
5
# 在 Cobar 上启动 iperf3 服务端
iperf3 -s

# 从 Alpha 测试到 Cobar(经 Bravo 中转),8 并发流,60 秒
iperf3 -c 10.0.2.2 -P 8 -t 60

实测结果,Alpha 经 Bravo 到 Cobar 能跑到 36Gbps 左右:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Connecting to host 10.0.2.2, port 5201
[ 5] local 10.0.1.1 port 49152 connected to 10.0.2.2 port 5201
[ 7] local 10.0.1.1 port 49153 connected to 10.0.2.2 port 5201
[ 9] local 10.0.1.1 port 49154 connected to 10.0.2.2 port 5201
[ 11] local 10.0.1.1 port 49155 connected to 10.0.2.2 port 5201
[ 13] local 10.0.1.1 port 49156 connected to 10.0.2.2 port 5201
[ 15] local 10.0.1.1 port 49157 connected to 10.0.2.2 port 5201
[ 17] local 10.0.1.1 port 49158 connected to 10.0.2.2 port 5201
[ 19] local 10.0.1.1 port 49159 connected to 10.0.2.2 port 5201
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec 0 sender
[ 5] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec receiver
[ 7] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec 0 sender
[ 7] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec receiver
[ 9] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec 0 sender
[ 9] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec receiver
[ 11] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec 0 sender
[ 11] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec receiver
[ 13] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec 0 sender
[ 13] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec receiver
[ 15] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec 0 sender
[ 15] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec receiver
[ 17] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec 0 sender
[ 17] 0.00-60.00 sec 26.0 GBytes 3.72 Gbits/sec receiver
[ 19] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec 0 sender
[ 19] 0.00-60.00 sec 26.1 GBytes 3.73 Gbits/sec receiver
[SUM] 0.00-60.00 sec 208 GBytes 29.8 Gbits/sec 0 sender
[SUM] 0.00-60.00 sec 208 GBytes 29.8 Gbits/sec receiver

iperf Done.

8 流合计 29.8Gbps,这是经过一台中间节点转发的结果。直连链路(比如 Alpha 到 Bravo)能跑更高,单条 40G 链路实测峰值大约 36Gbps,已经接近 ConnectX-3 在 PCIe x8 3.0 下的理论上限。

故障切换验证

环形拓扑的好处是任意一台节点挂掉,剩下 3 台仍然能通过另一条路互相通信。

测试方法:关掉 Bravo,然后在 Alpha 上看路由表变化:

1
2
3
4
5
# Bravo 上
systemctl poweroff

# Alpha 上
ip route

Bravo 下线后,OSPF 在 Dead Interval(默认 40 秒)后检测到邻居丢失,自动重新收敛。Alpha 到 Cobar 的路由会从 via 10.0.1.2(经 Bravo)切换到 via 10.0.4.1(经 NUC)。

1
ping 10.0.2.2  # 应该仍然能通,只是多了一跳

解决 PVE 迁移网络冲突

我的 NUC 上除了 40G 网卡,还有一张 10G 双口网卡分别连到了管理网段(192.168.31.0/24 和 192.168.21.0/24)。这让 PVE 的迁移功能出了问题:NUC 上有两张网卡在同一个网段,执行 LXC 迁移时报 could not get migration ip: multiple, different ... 错误。

解决方法是修改 datacenter.cfg,把迁移网络指向 40G 环形网络的子网。比如给每台节点的环回口加一个 IP 别名,统一在一个新的 /24 网段里:

1
2
3
4
# 每台节点的 /etc/network/interfaces 里给 lo 加别名
auto lo:1
iface lo:1 inet static
address 1.0.0.119/32 # Alpha 用 .119, Bravo .118, Cobar .117, NUC .116

然后修改 /etc/pve/datacenter.cfg

1
migration: secure,network=1.0.0.0/24

这样 PVE 迁移流量会走 40G 环形网络。不过这个方案有个前提:环回口的 IP 别名需要通过 OSPF 宣告出去,或者在每台节点上把别名网段加到 OSPF 的 network 语句里。如果没有做这一步,其他节点到不了这个别名 IP,迁移会失败。

如果不想用 IP 别名,也可以直接把迁移网络指定为某个 40G 子网,比如 migration: secure,network=10.0.1.0/30,但这样只有直连该子网的两台节点能迁移,范围太窄。折中方案是用一个覆盖所有 40G 子网的大网段宣告,或者直接用每台节点的 OSPF router-id 所在网段。

FRR 常用排查命令

命令用途
vtysh -c "show ip ospf neighbor"查看 OSPF 邻居状态
vtysh -c "show ip ospf interface"查看接口的 OSPF 配置
vtysh -c "show ip route ospf"只看 OSPF 学到的路由
vtysh -c "show ip ospf database"查看链路状态数据库
vtysh -c "show running-config"查看当前生效的配置
vtysh -c "clear ip ospf process"重置 OSPF 进程,强制重新收敛

修改配置后记得保存:

1
vtysh -c "write memory"

踩坑记录

DR/BDR 选举导致邻居状态异常:没设 ip ospf network point-to-point 之前,邻居状态里会出现 Full/DRFull/Backup 这些角色标记。在点对点链路上搞 DR/BDR 选举没意义,还会拖慢邻居建立速度。加上 point-to-point 后邻居直接进 Full/-,干净利落。

Router ID 混乱:没手动指定 ospf router-id 时,OSPF 自动选了管理网段的 IP(192.168.31.x),在邻居表里和 40G 子网的 IP 混在一起,排查问题时很困惑。手动指定后统一用 10.0.0.x,一眼能认出是哪台节点。

mlx4 驱动兼容性:ConnectX-3 用的是 mlx4_en 驱动,内核 6.7 开始被标记为废弃,6.8 可能被移除。如果你的 PVE 内核版本比较新,用 modinfo mlx4_en 确认一下驱动还在不在。不在的话要么降内核,要么手动编译驱动。

MTU 设置:40G 网卡建议开 jumbo frame(MTU 9000),但桥接接口和物理网卡的 MTU 要一致,否则会丢包。在 /etc/network/interfaces 里给物理网卡和桥接都加上 mtu 9000

最终效果

  • 4 台 PVE 节点通过 QSFP+ 光缆组成环形局域网,任意两台之间都能互通
  • LXC 和 VM 可以挂载桥接接口直接使用 40G 带宽
  • Ceph OSD 的公有和集群网络都跑在 40G 环形网络上
  • 任意一台节点宕机,OSPF 自动收敛,剩下的节点在 40 秒内恢复互通
  • 不需要 40G 交换机,省了一笔钱

结局:拆了卖了

这套环境跑了几个月,做了几轮 iperf3 测试,也发了朋友圈炫耀了一把。但说实话,对于家用场景来说,实际意义不大。

40G 网络给 Ceph 用,理论上听起来很美,但实际瓶颈不在网络带宽。Ceph 集群里每台 M920x 只有两块 SATA SSD 做 OSD,单盘顺序写入也就 500MB/s 左右,两块加起来不到 10Gbps,40G 网络根本跑不满。存储性能的天花板是磁盘,不是网络。

另一方面,3 台 M920x 全天候运行,功耗加起来也不少。NUC 倒是一直留着用。

最终除了 NUC 之外,Alpha、Bravo、Cobar 三台 M920x 全部拆了卖了,网卡也挂了闲鱼。这个实验验证了环形拓扑 + OSPF 在小规模 Homelab 里是可行的,但 40G 带宽对家用来说确实过剩了。如果只跑 Ceph,万兆网络绑个 bond 就够用了。