笔者的实验室于近期购置了8块nvme硬盘,通过pcie转接卡转接至一台服务器,并用ZFS组了RAID。为了能让实验室的其他服务器也能快速访问主机上的存储池,笔者去闲鱼套了两块ConnectX-4 CX4121A 10Gbe 的万兆网卡用来连接两台服务器,并配置了NFS Over RDMA。

闲鱼的卖家没有附带光模块,笔者随意买了两个华为的10G模块,大概15-20元一个。

安装驱动

前往nvidia的官网下载NVIDIA Firmware Tools (MFT),根据自己的系统选安装包,笔者实验室用的都是ubuntu,就下载了mft-4.25.0-62-x86_64-deb.tgz

下载完后运行安装包内的install.sh即可。

安装完后通过mst start启动MST,然后用mst status就能看到自己的卡了,此时电脑上应该也会多出光卡对应的NIC。

# mst status
MST modules:
------------
    MST PCI module is not loaded
    MST PCI configuration module loaded

MST devices:
------------
/dev/mst/mt4117_pciconf0         - PCI configuration cycles access.
                                   domain:bus:dev.fn=0000:04:00.0 addr.reg=88 data.reg=92 cr_bar.gw_offset=-1
                                   Chip revision is: 00

如果网卡只用做服务器之间的互联,可以分别为两段的服务器配置静态IP和路由。

更新固件

前往nvidia的固件下载页下载对应的zip(注意区分自己的卡是以太网卡还是IB卡)。

找到自己的设备(/dev/mst开头的),用flint -d <device_name> -i <binary image> burn刷入固件。

安装MLNX_OFED

虽然笔者买的是以太网卡,但Mellanox的RDMA内核模块需要去给IB卡提供的MLNX_OFED包里安装。MLNX_OFED也可以在Nvidia官网下载。笔者直接下了最新的MLNX_OFED_LINUX-23.07-0.5.0.0-ubuntu22.04-x86_64.tgz,没有下LTS版本。

下载完解压后,运行里面的

./mlnxofedinstall

即可。

随后还要手动安装里面的NFS-RDMA内核模块,一般这个包位于./DEBS/mlnx-**nfs**rdma-dkms_23.04-OFED.23.04.0.5.3.1_all.deb的位置,也可以用find . | grep nfs | grep .deb找到。随后dpkg -i ./DEBS/mlnx-**nfs**rdma-dkms_23.04-OFED.23.04.0.5.3.1_all.deb即可。期间需要DKMS需要build内核模块,请耐心等待。

安装NFS Server

在服务器一端安装NFS Server

apt install nfs-kernel-server
systemctl start nfs-kernel-server.service

在启动NFS之前需要mount相应的内核模块,以及把RDMA用的端口加进NFS的portlist:

/sbin/modprobe rpcrdma
echo 'rdma 20049' | tee /proc/fs/nfsd/portlist

如果想之后自动进行可以直接修改/lib/systemd/system/nfs-kernel-server.service(主要是加ExecStartPreExecStartPost

 [Unit]
Description=NFS server and services
DefaultDependencies=no
Requires=network.target proc-fs-nfsd.mount
Requires=nfs-mountd.service
Wants=rpcbind.socket network-online.target
Wants=rpc-statd.service nfs-idmapd.service
Wants=rpc-statd-notify.service
Wants=nfsdcld.service

After=network-online.target local-fs.target
After=proc-fs-nfsd.mount rpcbind.socket nfs-mountd.service
After=nfs-idmapd.service rpc-statd.service
After=nfsdcld.service
Before=rpc-statd-notify.service

# GSS services dependencies and ordering
Wants=auth-rpcgss-module.service
After=rpc-gssd.service gssproxy.service rpc-svcgssd.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/sbin/exportfs -r
ExecStartPre=/sbin/modprobe rpcrdma
ExecStart=/usr/sbin/rpc.nfsd
ExecStartPost=/bin/bash -c "sleep 3 && echo 'rdma 20049' | tee /proc/fs/nfsd/portlist"
ExecStop=/usr/sbin/rpc.nfsd 0
ExecStopPost=/usr/sbin/exportfs -au
ExecStopPost=/usr/sbin/exportfs -f

ExecReload=-/usr/sbin/exportfs -r

[Install]
WantedBy=multi-user.target

随后systemctl daemon-reload并重启nfs服务。此时在NFS的监听端口里应该能看到普通nfs用的2049和RDMA的20049了:

# cat /proc/fs/nfsd/portlist

rdma 20049
rdma 20049
tcp 2049
tcp 2049

添加暴露的挂载目录

编辑/etc/exports,如果对于ZFS文件系统,也可以直接执行如下命令(意思是允许7.0.115.0/24访问pool-name这个pool):

zfs set sharenfs="[email protected]/24,no_root_squash,async" pool-name

随后通过exportfs -v检查,可以看到相应的目录已经被暴露了:

# exportfs -v
/data/pool-name 7.0.115.0/24(async,wdelay,hide,no_subtree_check,mountpoint,sec=sys,rw,secure,no_root_squash,no_all_squash)

客户端操作

客户端在挂载前同样需要挂载rpcrdma,这个命令也可以写在systemctl的ExecStartPre=里:

modprobe rpcrdma

随后进行挂载:

mount 7.0.115.1:/data/pool-name /data/pool-name -o rdma,port=20049,async,noatime,nodiratime -vvvv

如果一切没有问题则可以在/etc/fstab里添加:

7.0.115.1:/data/pool-name /data/pool-name nfs rdma,port=20049,async,noatime,nodiratime 0 0

测速

用fio测试顺序写入速度:

fio --name=testfile --directory=/data/pool-name/speedtest --size=2G --numjobs=10 --rw=write --bs=1000M --ioengine=libaio --fdatasync=1 --runtime=60 --time_based --group_reporting --eta-newline=1s

可以看到写入速度能够跑满10G网卡(1078MiB/s),同时测速时通过iftop在网卡上看不到任何流量,说明NFS的流量已经直接经过RDMA传输了。

testfile: (groupid=0, jobs=10): err= 0: pid=3968057: Thu Aug 24 08:00:00 2023
  write: IOPS=1, BW=1078MiB/s (1130MB/s)(67.4GiB/64006msec); 0 zone resets
    slat (msec): min=328, max=6801, avg=3973.96, stdev=1633.37
    clat (nsec): min=1780, max=12590, avg=3690.00, stdev=1745.87
     lat (msec): min=328, max=6801, avg=3973.96, stdev=1633.37
    clat percentiles (nsec):
     |  1.00th=[ 1784],  5.00th=[ 2064], 10.00th=[ 2192], 20.00th=[ 2800],
     | 30.00th=[ 2928], 40.00th=[ 3088], 50.00th=[ 3280], 60.00th=[ 3408],
     | 70.00th=[ 3824], 80.00th=[ 4576], 90.00th=[ 4896], 95.00th=[ 6688],
     | 99.00th=[12608], 99.50th=[12608], 99.90th=[12608], 99.95th=[12608],
     | 99.99th=[12608]
   bw (  MiB/s): min=19984, max=20000, per=100.00%, avg=19997.62, stdev= 0.93, samples=64
   iops        : min=   16, max=   20, avg=19.40, stdev= 0.23, samples=64
  lat (usec)   : 2=2.90%, 4=71.01%, 10=24.64%, 20=1.45%
  fsync/fdatasync/sync_file_range:
    sync (nsec): min=20, max=9970, avg=597.50, stdev=1058.05
    sync percentiles (nsec):
     |  1.00th=[   20],  5.00th=[   50], 10.00th=[  110], 20.00th=[  161],
     | 30.00th=[  231], 40.00th=[  382], 50.00th=[  470], 60.00th=[  532],
     | 70.00th=[  612], 80.00th=[  708], 90.00th=[  948], 95.00th=[ 1464],
     | 99.00th=[ 9920], 99.50th=[ 9920], 99.90th=[ 9920], 99.95th=[ 9920],
     | 99.99th=[ 9920]
  cpu          : usr=1.00%, sys=7.62%, ctx=1122377, majf=0, minf=141
  IO depths    : 1=233.3%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwts: total=0,69,0,0 short=92,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
  WRITE: bw=1078MiB/s (1130MB/s), 1078MiB/s-1078MiB/s (1130MB/s-1130MB/s), io=67.4GiB (72.4GB), run=64006-64006msec

Reference