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 Qualified Name (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 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
}
修改 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
如果这没有帮助,可能需要重新启动。