ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu 18.04上NVMe-oF配置实战:RDMA与TCP传输详解

Ubuntu 18.04上NVMe-oF配置实战:RDMA与TCP传输详解

1. 为什么要在Ubuntu 18.04上折腾NVMe-oF

NVMe-oF,全称NVMe over Fabrics,说白了就是把本地那套NVMe协议栈从PCIe总线延伸到网络上。以前一块NVMe盘插在主板上,走的是PCIe通道,延迟低、队列深、IOPS高;现在通过RDMA或者TCP把NVMe命令封装到网络里发出去,远端机器上的NVMe盘就像本地盘一样被访问。这个技术解决的核心问题是存储解耦——计算节点不用再背着几十块盘跑,存储资源池化之后按需分配,扩容也不用停机插卡。

我第一次接触NVMe-oF是在一个模拟的分布式存储项目里,当时手头只有几台普通x86服务器和一张Mellanox ConnectX-4网卡,系统是Ubuntu 18.04。这个组合其实挺典型的:Ubuntu 18.04的内核是4.15,原生支持NVMe-oF的RDMA传输,但用户态工具和配置流程跟现在22.04/24.04差别不小,网上很多教程直接抄新版本的命令,在18.04上跑不起来。所以这篇内容我打算把整个配置过程从头到尾捋一遍,包括内核模块、RDMA环境、target端配置、host端连接,以及我踩过的那些坑。

适合谁看?如果你手上有支持RDMA的网卡(Mellanox、Broadcom、Intel都行),想在Ubuntu 18.04上搭一套NVMe-oF环境做测试或者小规模部署,这篇可以直接抄作业。如果你只是用TCP传输,流程也类似,我会在关键地方标注差异。小白也能看,但前提是你得会基本的Linux命令,知道怎么改配置文件、怎么看dmesg。

2. 整体方案设计与前置条件确认

2.1 传输层选型:RDMA还是TCP

NVMe-oF规范定义了多种传输层,常见的就是RDMA(RoCEv2或InfiniBand)和TCP。选哪个取决于你的硬件和性能要求。

RDMA的优势是零拷贝、内核旁路,延迟能压到微秒级,CPU占用极低。但前提是你得有支持RoCE的网卡,而且交换机要配好PFC和ECN,不然丢包重传会让性能断崖式下跌。TCP就简单多了,普通万兆网卡就能跑,配置门槛低,但延迟和CPU开销明显高出一截,适合对性能不敏感的场景或者纯功能验证。

我这次用的是RDMA over Converged Ethernet v2,也就是RoCEv2。网卡是Mellanox ConnectX-4 LX,交换机是普通的万兆交换机,没配PFC,所以实际跑下来性能有波动,但功能验证没问题。如果你只是想在实验室里跑通流程,TCP也够用,后面我会给出TCP的配置差异。

2.2 软件栈版本确认

Ubuntu 18.04默认内核是4.15,这个版本已经包含了nvme-rdma和nvme-tcp内核模块。但用户态工具nvme-cli的版本比较老,建议升级到1.16以上,不然有些子命令不支持。RDMA用户态库需要安装rdma-core和ibverbs-utils,Mellanox网卡还需要MLNX_OFED驱动或者内核自带的驱动。

先确认几个关键点:

uname -r # 输出应该是4.15.0-xx-generic modinfo nvme-rdma # 确认模块存在 modinfo nvme-tcp # 确认TCP传输模块存在 ibv_devinfo # 查看RDMA设备信息,如果命令不存在需要装ibverbs-utils

如果ibv_devinfo能看到你的网卡,状态是PORT_ACTIVE,说明RDMA环境基本OK。如果看不到,先解决驱动问题,别急着往下走。

2.3 网络规划与IP分配

我用了两台机器做测试,一台做target(存储端),一台做host(计算端)。IP规划如下:

角色主机名IP地址网卡
Targetstorage-node192.168.100.10enp1s0
Hostcompute-node192.168.100.20enp1s0

两台机器直连或者通过交换机互联都行。如果是RoCEv2,建议单独划一个VLAN或者用独立的物理口,避免和其他流量混在一起。MTU建议设成9000,也就是巨帧,能显著降低CPU开销。

注意:RoCEv2对网络质量很敏感,如果交换机不支持PFC,建议先跑TCP验证功能,再切RDMA调性能。

3. Target端配置全流程

3.1 安装必要软件包

Target端需要nvme-cli、rdma-core、ibverbs-utils,如果要用configfs配置target,还需要确保内核支持nvmet和nvmet-rdma模块。

sudo apt update sudo apt install -y nvme-cli rdma-core ibverbs-utils

装完之后检查模块:

sudo modprobe nvmet sudo modprobe nvmet-rdma lsmod | grep nvmet

如果nvmet-rdma加载失败,大概率是RDMA驱动没装好,先回去检查ibv_devinfo。

3.2 准备后端存储设备

NVMe-oF target可以把整个NVMe盘、一个分区、甚至一个普通文件作为后端。我测试的时候用了一块空闲的NVMe盘,/dev/nvme1n1,没有文件系统,直接裸设备导出。

警告:导出裸设备之前一定要确认盘里没有重要数据,host端连上来之后可以直接读写,误操作会丢数据。

如果想用文件做后端,可以这样:

sudo mkdir -p /srv/nvmeof sudo dd if=/dev/zero of=/srv/nvmeof/disk1.img bs=1M count=10240

这样就创建了一个10GB的文件作为后端。文件后端的好处是灵活,坏处是多了一层文件系统开销,性能不如裸设备。

3.3 通过configfs配置target

Ubuntu 18.04上配置NVMe-oF target的标准方式是configfs,挂载点在/sys/kernel/config/nvmet。整个配置过程分几步:创建subsystem、添加namespace、设置端口、绑定端口。

先确认configfs挂载:

mount | grep configfs # 应该能看到 /sys/kernel/config 类型为 configfs

如果没有挂载,手动挂:

sudo mount -t configfs none /sys/kernel/config

然后开始配置。假设subsystem名字叫nqn.2024-01.com.example:storage,NQN的格式是nqn.YYYY-MM.域名反写:标识,你可以自己编,但要保证全局唯一。

# 创建subsystem sudo mkdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:storage cd /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:storage # 允许任意host连接,生产环境建议设成0并配置allowed_hosts echo 1 | sudo tee attr_allow_any_host # 创建namespace 1 sudo mkdir namespaces/1 cd namespaces/1 # 指定后端设备 echo -n /dev/nvme1n1 | sudo tee device_path # 启用namespace echo 1 | sudo tee enable

接下来配置端口。端口代表target端的网络监听地址。

# 创建端口 sudo mkdir /sys/kernel/config/nvmet/ports/1 cd /sys/kernel/config/nvmet/ports/1 # 设置监听地址 echo -n 192.168.100.10 | sudo tee addr_trtype echo -n ipv4 | sudo tee addr_adrfam echo -n rdma | sudo tee addr_trtype echo -n 4420 | sudo tee addr_trsvcid

等等,这里有个容易搞混的地方。addr_trtype是传输类型,rdma或者tcp;addr_adrfam是地址族,ipv4或者ipv6;addr_traddr是监听地址;addr_trsvcid是端口号,默认4420。正确的顺序是:

echo -n rdma | sudo tee addr_trtype echo -n ipv4 | sudo tee addr_adrfam echo -n 192.168.100.10 | sudo tee addr_traddr echo -n 4420 | sudo tee addr_trsvcid

最后把subsystem绑定到端口:

sudo ln -s /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:storage \ /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2024-01.com.example:storage

绑定成功之后,target端就配置完了。可以用dmesg看看有没有报错:

dmesg | tail -20

如果看到nvmet_rdma相关的日志,说明端口已经起来监听了。

3.4 验证target状态

检查subsystem和端口的状态:

# 查看subsystem ls /sys/kernel/config/nvmet/subsystems/ # 查看端口 ls /sys/kernel/config/nvmet/ports/ # 查看namespace状态 cat /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:storage/namespaces/1/enable # 应该输出1

还可以用nvme命令查看target端的信息,不过target端一般不需要额外操作。

实操心得:configfs的配置是临时的,重启就没了。生产环境建议写个systemd服务或者脚本,开机自动配置。我一般会把所有命令写成一个setup_nvmeof_target.sh,放到/usr/local/bin下面,然后用systemd unit调用。

4. Host端连接与挂载

4.1 发现target

Host端首先要发现target上的subsystem。用nvme discover命令:

sudo nvme discover -t rdma -a 192.168.100.10 -s 4420

如果一切正常,会输出发现日志,包含target的NQN和端口信息。如果卡住或者报错,先检查网络连通性:

ping 192.168.100.10 ibv_rc_pingpong -d mlx4_0 -g 1 192.168.100.10

ibv_rc_pingpong是RDMA层的连通性测试,如果这个不通,nvme discover肯定也不通。常见原因是GID索引不对,RoCEv2一般用GID索引3或者4,具体看ibv_devinfo -v的输出。

4.2 连接target

发现成功之后,用nvme connect建立连接:

sudo nvme connect -t rdma -n nqn.2024-01.com.example:storage -a 192.168.100.10 -s 4420

连接成功后,系统会多出一个NVMe设备,通常是/dev/nvme2n1或者类似的。用lsblk确认:

lsblk # 应该能看到新的nvme设备

也可以用nvme list查看:

sudo nvme list

如果连接失败,dmesg里会有详细报错。常见的错误码和原因我整理了一下:

错误信息可能原因排查方向
Connection refusedtarget端端口没监听检查target的ports配置
No such deviceRDMA设备名不对确认ibv_devinfo输出
Invalid argumentNQN或地址格式错误检查命令参数
Timeout网络不通或防火墙拦截ping和ibv_rc_pingpong测试

4.3 挂载文件系统

连上之后,/dev/nvme2n1就是远端盘。可以直接格式化然后挂载:

sudo mkfs.ext4 /dev/nvme2n1 sudo mkdir -p /mnt/nvmeof sudo mount /dev/nvme2n1 /mnt/nvmeof

然后就能像本地盘一样读写了。测一下速度:

sudo dd if=/dev/zero of=/mnt/nvmeof/testfile bs=1M count=1024 oflag=direct

oflag=direct绕过页缓存,测出来的才是真实网络存储性能。我实测下来,RoCEv2单口能跑到大概1.2GB/s,延迟在200微秒左右,比本地NVMe差不少,但比iSCSI强太多了。

4.4 开机自动连接

手动连接重启就没了,需要配置自动连接。Ubuntu 18.04上可以用nvme-cli的discovery controller功能,或者写个systemd服务。

用nvme-cli的方式:

sudo nvme discover -t rdma -a 192.168.100.10 -s 4420 # 发现之后,系统会自动保存discovery log # 然后启用nvme-discovery服务 sudo systemctl enable nvme-discovery sudo systemctl start nvme-discovery

不过这个方式在18.04上不太稳定,我更喜欢写个简单的systemd unit:

[Unit] Description=Connect NVMe-oF target After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/sbin/nvme connect -t rdma -n nqn.2024-01.com.example:storage -a 192.168.100.10 -s 4420 RemainAfterExit=yes [Install] WantedBy=multi-user.target

保存成/etc/systemd/system/nvmeof-connect.service,然后enable就行。

5. 性能调优与常见问题排查

5.1 RDMA性能调优关键参数

RoCEv2的性能很大程度上取决于网络配置。如果交换机支持PFC和ECN,一定要开。如果不支持,至少把MTU调到9000:

sudo ip link set enp1s0 mtu 9000

然后调整内核参数:

# 增大接收缓冲区 sudo sysctl -w net.core.rmem_max=134217728 sudo sysctl -w net.core.wmem_max=134217728 sudo sysctl -w net.core.rmem_default=134217728 sudo sysctl -w net.core.wmem_default=134217728 # 调整RDMA相关的参数 sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728" sudo sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"

NVMe-oF的队列深度也可以调。默认队列深度是128,可以增加到1024:

# 在target端 echo 1024 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:storage/attr_queue_depth

不过队列深度不是越大越好,要看网卡和交换机的缓冲能力,盲目加大会导致丢包。

5.2 常见问题速查表

现象排查步骤解决方案
nvme discover超时1. ping测试 2. ibv_rc_pingpong测试 3. 检查防火墙开放4420端口,确认RDMA连通
连接后设备不出现dmesg查看内核日志检查target的namespace是否enable
读写性能差1. 检查MTU 2. 检查PFC 3. 测RDMA带宽开巨帧,配PFC,用ib_send_bw测带宽
重启后连接丢失检查systemd服务配置自动连接服务
多路径不生效检查nvme子系统多路径配置启用native multipath

5.3 我踩过的几个坑

第一个坑是GID索引。RoCEv2的GID索引跟网卡型号和驱动版本有关,ConnectX-4上一般是3,但有些版本是4。如果ibv_rc_pingpong不通,先试不同的GID索引:

ibv_rc_pingpong -d mlx4_0 -g 3 192.168.100.20 ibv_rc_pingpong -d mlx4_0 -g 4 192.168.100.20

第二个坑是configfs的attr_allow_any_host。测试的时候设成1很方便,但生产环境一定要设成0,然后配置allowed_hosts,不然任何知道NQN的机器都能连上来。

第三个坑是文件后端的性能。用文件做后端的时候,如果文件系统是ext4,性能会比裸设备差30%以上。如果非要文件后端,建议用xfs,并且关掉atime。

第四个坑是Ubuntu 18.04的nvme-cli版本太老,不支持某些参数。比如nvme connect的--hostnqn参数在老版本上可能没有,需要升级nvme-cli。升级方法是直接从源码编译:

git clone https://github.com/linux-nvme/nvme-cli.git cd nvme-cli make sudo make install

提示:升级nvme-cli之前先备份原来的二进制,万一新版本有问题可以回滚。

6. 从功能验证到小规模部署的扩展思路

功能跑通之后,下一步就是考虑怎么用到实际场景里。NVMe-oF最常见的用法是给计算节点提供共享存储,比如跑数据库或者容器持久化卷。这时候要考虑几个问题:多路径、高可用、性能隔离。

多路径方面,Linux内核从4.15开始支持NVMe native multipath。如果target端有两个端口,host端可以连两次,内核会自动做负载均衡和故障切换。配置方法是连接的时候指定不同的端口,然后内核会自动识别相同的NQN并合并成一条多路径设备。

高可用方面,target端可以做双机热备,host端配置两个target地址,一个主一个备。不过NVMe-oF本身没有原生的双活机制,需要上层软件配合,比如用DRBD做块设备复制,然后在上面跑NVMe-oF target。

性能隔离方面,如果多个host共享同一个target,可以用cgroup或者QoS限制每个host的带宽。NVMe-oF规范里有QoS相关的特性,但Ubuntu 18.04的内核支持不完整,需要升级内核或者打补丁。

我个人在实际操作中的体会是,NVMe-oF在Ubuntu 18.04上跑通不难,难的是调优和稳定性。如果只是做功能验证,按照上面的步骤一步步来,半天就能搞定。如果要上生产,建议至少升级到Ubuntu 20.04或者22.04,内核更新,nvme-cli更完善,社区支持也更好。18.04毕竟已经比较老了,很多新特性用不上,遇到问题查资料也麻烦。

最后再分享一个小技巧:调试的时候把nvme的日志级别调高,能看到更详细的连接过程:

echo 1 | sudo tee /sys/module/nvme_core/parameters/debug dmesg -w

这样连接的时候就能实时看到内核的握手过程,排查问题效率高很多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进