NVMe over Fabrics
NVMe over Fabrics (NVMe-oF) 允许通过 以太网 或 光纤通道 发送 NVMe 命令。这可以用于远程访问块设备,类似于 iSCSI。NVMe-oF 主机 可以访问 NVMe-oF 控制器 暴露的设备。这不限于 NVMe 设备,您还可以暴露 块设备,例如 ZFS zvols。
主机配置
要访问远程 NVMe-oF 控制器暴露的块设备,安装 nvme-cli 软件包。
首先,确保必要的内核模块已加载
# modprobe nvme-fabrics
现在你可以使用 CLI 连接到设备
# nvme connect --transport=tcp --traddr=192.168.0.5 --trsvcid=4420 --nqn=nqn.2024-08.com.example:my-disk
- --transport=tcp
- 指定传输协议。TCP 是最容易设置的。
- --traddr=192.168.0.5
- NVMe-oF 目标的地址,即暴露块设备的服务器。
- --trsvcid=4420
- 目标监听的端口。4420 是推荐端口(IANA 分配)。
- --nqn=nqn.2024-08.com.examplemy-disk
- 要连接的设备的 NVMe 限定名 (NQN)。设备必须在控制器上配置。
运行此命令后,该设备将作为普通的 NVMe 块设备 可用,例如在 /dev/nvme0n1
下,分区在 /dev/nvme0n1p1
下。建议通过 UUID 引用这些设备。
控制器配置
首先,确保必要的内核模块已加载
# modprobe nvmet
控制器配置通过位于 /sys/kernel/config/nvmet
的文件系统进行。nvmetcli 提供了修改该文件系统的便捷界面。它还提供了加载和保存设置的方法。nvmetcli save
将把当前状态保存到 /etc/nvmet/config.json
,而 nvmetcli restore
将从该位置加载。您可以 启用 nvmet.service
单元以自动从该文件加载配置。
在 nvmetcli
中,您可以使用 cd
导航,并使用 ls
显示配置树。有三个顶层目录。
hosts
- 包含在树的其他部分引用的主机的 NQN,例如用于访问控制。您可以在此处添加一个 NQN,然后使客户端使用
nvme connect --hostnqn=...
参数使用该 NQN。 ports
- 配置用于访问设备的协议,例如 TCP 端口。
subsystems
- 配置各个可访问的设备。
添加设备
首先您需要创建一个要暴露的设备
/> cd subsystems /subsystems> create nqn.2024-08.com.example:my-device /subsystems> cd nqn.2024-08.com.example:my-device /subsystems/n...ple:my-device>
配置设备的访问权限
/subsystems/n...ple:my-device> set attr allow_any_host=1
为设备配置命名空间并设置后备块设备
/subsystems/n...ple:my-device> cd namespaces /subsystems/n...ce/namespaces> create 1 /subsystems/n...ce/namespaces> cd 1 /subsystems/n.../namespaces/1> set device path=/dev/path/to/block/device /subsystems/n.../namespaces/1> enable
配置端口
接下来,创建一个端口。使用 cd /
导航回顶部。
/> cd ports /ports> create 1 /ports> cd 1 /ports/1> set addr trtype=tcp traddr=192.168.0.5 trsvcid=4420 adrfam=ipv4
- trtype
- 传输协议
- traddr
- 监听地址
- trsvcid
- 监听 TCP 端口(“服务 ID”)
将您上面创建的设备添加到网络端口
/ports/1> cd subsystems /ports/1/subsystems> create nqn.2024-08.com.example:my-device
就是这样!现在主机可以使用上述说明访问该设备。要使其永久生效,请执行 saveconfig
命令并确保 nvmet.service
已启用/启动。
使用 DHCHAP 认证
谨慎的做法是不允许任何设备访问暴露的块设备。为了验证主机身份,NVMe-oF 提供了 DHCHAP 握手,使用主机和控制器之间共享的密钥。不幸的是,截至撰写本文时,nvmetcli 不支持开箱即用,因此您必须手动修改配置 FS。主机 nvme
命令确实支持 DHCHAP。
首先,使用 nvmetcli
将对设备的访问限制为仅单个主机,通过 NQN 标识。
/> cd hosts /hosts> create nqn.2024-08.com.example.host /hosts> cd /subsystems/nqn.2024-08.com.example:my-device /subsystems/n...ple:my-device> set attr allow_any_host=0 /subsystems/n...ple:my-device> cd allowed_hosts /subsystems/n...allowed_hosts> create nqn.2024-08.com.example.host
现在 NVMe 控制器知道只有 NQN 为 nqn.2024-08.com.example.host
的主机可以访问该设备。您可以使用 nvme connect --hostnqn=nqn.2024-08.com.example.host
(暂不使用 DHCHAP) 进行测试。使用错误的 NQN,连接应该失败;使用正确的 NQN,连接应该成功。
下一步是配置 DHCHAP。首先您需要生成一个密钥,如下所示:
$ nvme gen-dhchap-key --nqn=nqn.2024-08.com.example.host DHHC-1:00:znDcb37R200FNlZkIOkv37idpu/notvalid!!si1VQ09KhKv2g:
因为 nvmetcli 不支持 DHCHAP,您必须手动配置它
# echo 'DHHC-1:00:znDcb37R200FNlZkIOkv37idpu/notvalid!!si1VQ09KhKv2g:' > /sys/kernel/config/nvmet/hosts/nqn.2024-08.com.example.host/dhchap_key
现在,您可以使用 DHCHAP 密钥连接
# nvme connect --transport=tcp --traddr=192.168.0.5 --trsvcid=4420 --nqn=nqn.2024-08.com.example:my-disk --hostnqn=nqn.2024-08.com.example.host --dhchap-secret="DHHC-1:00:znDcb37R200FNlZkIOkv37idpu/notvalid!!si1VQ09KhKv2g:"
从 NVMe-oF 启动
可以通过在 initramfs 中的启动过程中连接到 NVMe-oF 设备,并将该设备用于根文件系统。该过程与 iSCSI 的过程非常相似。此处仅列出 NVMe 特有的部分。
对于 NVMe-oF,initcpio
钩子是不同的。安装脚本添加了不同的模块,并添加了 nvme
二进制文件而不是 iscsistart
。
/etc/initcpio/install/nvme-of
build () { map add_module nvme-fabrics nvme-tcp nvme-keyring add_checked_modules "/drivers/net" add_binary nvme add_runscript } help () { cat <<HELPEOF This hook allows you to boot from an NVMe-oF target. HELPEOF }
实际的钩子使用 CLI 连接到设备
/etc/initcpio/hooks/nvme-of
run_hook () { msg "Mounting NVMe-oF target" nvme connect --transport=tcp --nqn=nqn.2024-08.com.example:my-device --traddr=192.168.0.5 --trsvcid=4420 }
修改 mkinitcpio
配置
/etc/mkinitcpio.conf
MODULES=(... nvme-fabrics) HOOKS=(... net nvme-of block ...)
故障排除
页面分配失败
如果您在主机上看到此失败
nvme nvme0: Connect command failed, error wo/DNR bit: 6 nvme nvme0: failed to connect queue: 3 ret=6 could not add new controller: failed to write to nvme-fabrics device
检查控制器上的 dmesg 以查找此错误
kworker/5:1H: page allocation failure: order:6, mode:0x40dc0(GFP_KERNEL|__GFP_COMP|__GFP_ZERO), nodemask=(null),cpuset=/,mems_allowed=0 CPU: 5 PID: ... Comm: kworker/5:1H Tainted: P OE ... Hardware name: ... Workqueue: nvmet_tcp_wq nvmet_tcp_io_work [nvmet_tcp] Call Trace: <TASK> dump_stack_lvl+0x4d/0x70 warn_alloc+0x165/0x1e0 ? __alloc_pages_direct_compact+0x163/0x390 __alloc_pages_slowpath.constprop.0+0xce9/0xde0 __alloc_pages+0x320/0x340 ? nvmet_tcp_install_queue+0x50/0x140 [nvmet_tcp ...] __kmalloc_large_node+0x71/0x130 __kmalloc+0xc4/0x130 nvmet_tcp_install_queue+0x50/0x140 [nvmet_tcp ...] nvmet_install_queue+0xa6/0x1f0 [nvmet ...] nvmet_execute_io_connect+0xd1/0x1a0 [nvmet ...] nvmet_tcp_io_work+0x811/0x880 [nvmet_tcp ...] process_one_work+0x180/0x350 worker_thread+0x315/0x450 ? __pfx_worker_thread+0x10/0x10 kthread+0xe8/0x120 ? __pfx_kthread+0x10/0x10 ret_from_fork+0x34/0x50 ? __pfx_kthread+0x10/0x10 ret_from_fork_asm+0x1b/0x30 </TASK>
如果堆栈跟踪匹配,您遇到了 这个错误。为了服务新的 TCP 连接,NVMe 目标/控制器在内核空间中分配一个相对较大的连续缓冲区。如果内核内存已满(或碎片化),则可能无法使用如此大的连续区域。这曾经导致内核崩溃,这在最近的内核中已得到修复,但截至 2025 年 2 月,这仍然阻止了与控制器的新 TCP 连接。该补丁正在等待中,但 仍处于审查中。
为了解决此问题,您可以使用以下命令在连接之前释放控制器上的内核内存
echo 3 | sudo tee /proc/sys/vm/drop_caches
如果这没有帮助,可能需要重启。