NVMe over Fabrics

来自 ArchWiki

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 引用这些设备。

控制器配置

安装 nvmetcli 软件包。

首先,确保必要的内核模块已加载

# modprobe nvmet

控制器配置通过位于 /sys/kernel/config/nvmet 的文件系统进行。nvmetcli 提供了修改该文件系统的便捷界面。它还提供了加载和保存设置的方法。nvmetcli save 将把当前状态保存到 /etc/nvmet/config.json,而 nvmetcli restore 将从该位置加载。您可以 启用 nvmet.service 单元以自动从该文件加载配置。

警告: nvmetcli 只会保存、加载和编辑它识别的设置。如果您配置了不支持的设置(例如 DHCHAP),它们将被静默丢弃。您将必须手动将它们写入上述文件系统。

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:"
警告: DHCHAP 将验证主机身份,但不会加密网络上的块设备数据。

从 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
}
提示: 如果您配置了 DHCHAP,请确保在此处添加所需的参数。

修改 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

如果这没有帮助,可能需要重启。