跳转至内容

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 Qualified Name (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 hook 不同。安装脚本添加了不同的模块,并添加了 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
}

实际的 hook 使用 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>

如果堆栈跟踪匹配,则表示您遇到了一个 linux-nvme bug。为了服务新的 TCP 连接,NVMe 目标/控制器会在内核空间分配一个相对较大的连续缓冲区。如果内核内存已满 (或碎片化),则可能无法获得如此大的连续区域。这曾经会导致内核崩溃,但在最近的内核中已得到修复,但截至 2025 年 2 月,这仍然阻止了与控制器的新的 TCP 连接。一个补丁正在等待,但 卡在审查中

为了解决此问题,您可以在连接之前使用以下命令释放控制器上的内核内存

# echo 3 > /proc/sys/vm/drop_caches

另一个似乎有效的方法是创建和删除 tmpfs 中的一个大文件

# dd if=/dev/urandom of=/tmp/random-buffer bs=1M count=4096 status=progress; rm -f /tmp/random-buffer

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