☰
Ubuntu 18.04 上 NVMe-oF 实战:TCP 传输与 configfs 配置指南
2026/10/21 5:02:07 网站建设 项目流程

1. 项目缘起与整体设计思路

存储性能瓶颈这件事,搞过数据库或者分布式存储的人应该都有切肤之痛。本地盘容量有限,扩容要停机;传统网络存储走 iSCSI 或者 NFS,延迟又下不来,尤其是 4K 随机读写场景,动不动就飙到几百微秒甚至毫秒级。NVMe-oF(NVMe over Fabrics)就是在这种背景下进入视野的——它把本地 NVMe 协议直接搬到网络上跑,绕开了 SCSI 协议栈那一大坨历史包袱,延迟能压到几十微秒级别,带宽也能跑满高速网络。

我这次要做的,是在 Ubuntu 18.04 这套相对“老派”的系统上,把 NVMe-oF 的 target 端和 host 端完整跑通。为什么强调 Ubuntu 18.04?因为它的内核是 4.15 系列,NVMe-oF 相关模块虽然已经内置,但版本偏早,很多新特性(比如nvmet的多路径优化、nvme-cli的新命令)需要额外确认兼容性。而且 18.04 的默认软件源里,nvme-cli版本比较旧,有些子命令参数和网上较新的教程对不上,直接照抄会踩坑。

这个项目的核心目标很明确:在一台机器上做 NVMe-oF target,把一块本地 NVMe 盘或者一个内存后端(ramdisk)通过 RDMA 或者 TCP 传输出去;另一台机器作为 host 端,发现并连接这个远程 NVMe 设备,最终在 host 端看到一个可用的块设备(比如/dev/nvme1n1),能正常分区、格式化、挂载、读写。适合谁来参考?我觉得是三类人:一是做存储性能测试的,想对比本地 NVMe 和远程 NVMe-oF 的延迟差异;二是搞私有云或者超融合的,想验证 NVMe-oF 在现有环境里的可行性;三是纯粹想折腾新存储协议的技术爱好者,手里有两台机器就想试试。

整体设计上,我选择TCP 传输作为主要方案,而不是 RDMA。原因很现实:RDMA 需要支持 RoCE 或者 InfiniBand 的网卡,还得配置无损网络(PFC、ECN 那一套),在普通办公或者测试环境里门槛太高。TCP 传输虽然性能不如 RDMA,但胜在通用——任何千兆、万兆网卡都能跑,配置简单,适合先跑通流程再优化。当然,如果手头有支持 RoCE 的网卡,我也会在后面的章节里补充 RDMA 的配置要点。

另一个关键决策是后端存储的选择。我准备了两套方案:一套用nvmet的ramdisk后端,纯内存模拟,性能极高,用来验证协议栈本身是否通畅;另一套用真实的 NVMe 分区或者文件作为后端,更贴近生产场景。这样分两步走,排查问题的时候能快速定位是协议层的问题还是后端设备的问题。

注意:Ubuntu 18.04 默认内核可能没有加载nvmet和nvmet-tcp模块,需要手动modprobe,而且重启后会失效,得写进/etc/modules或者用 systemd 单元持久化。

2. 核心细节解析与实操要点

2.1 NVMe-oF 的协议栈分层与关键角色

要玩转 NVMe-oF,得先搞清楚它里面几个核心概念,不然配置的时候一堆名词能把人绕晕。简单类比一下:NVMe-oF 就像把本地 NVMe 盘“拉长”了一根网线,host 端以为自己在访问本地盘,实际上命令是通过网络发到 target 端执行的。

整个架构分三层:

  • 前端接口层:host 端看到的/dev/nvmeXnY设备,以及 target 端暴露的nvmet子系统。这一层负责把 NVMe 命令封装成 Fabrics 命令。
  • 传输层:负责把封装好的命令通过网络发出去。可以是 RDMA(RoCE/IB),也可以是 TCP。这一层决定了性能上限和网络配置复杂度。
  • 后端存储层:target 端实际落盘的地方。可以是物理 NVMe 设备、普通块设备、文件,甚至是内存模拟的 ramdisk。

在 Ubuntu 18.04 上,这些模块分别是:

模块名作用是否默认加载
nvmehost 端 NVMe 核心模块通常已加载
nvme-coreNVMe 协议核心通常已加载
nvmettarget 端核心模块需手动加载
nvmet-tcpTCP 传输层需手动加载
nvmet-rdmaRDMA 传输层需手动加载
nvme-tcphost 端 TCP 传输需手动加载
nvme-rdmahost 端 RDMA 传输需手动加载

提示:用lsmod | grep nvme可以快速查看当前加载了哪些模块。如果nvmet没加载,后面configfs里根本看不到/sys/kernel/config/nvmet目录。

2.2 configfs 的挂载与目录结构

NVMe-oF target 的配置完全依赖configfs,这是一个内核提供的用户态配置接口。Ubuntu 18.04 默认可能没挂载configfs,需要手动挂:

sudo modprobe nvmet sudo modprobe nvmet-tcp sudo mkdir -p /sys/kernel/config sudo mount -t configfs none /sys/kernel/config

挂载成功后,/sys/kernel/config/nvmet目录就会出现。这个目录下面有几个关键子目录:

  • subsystems/:存放所有 NVMe 子系统(可以理解为一个“虚拟盘”)。
  • ports/:存放监听端口,每个端口绑定一个传输层(tcp 或 rdma)和地址。
  • hosts/:用于访问控制,可以限制哪些 host NQN 能连接。
  • allowed_hosts/:全局允许的 host 列表。

我踩过的一个坑是:configfs挂载后,如果nvmet模块没加载,/sys/kernel/config/nvmet是空的,甚至不存在。所以顺序一定是先modprobe再挂载,或者挂载后重新modprobe再mount一次。

2.3 后端存储的选型与性能考量

后端存储的选择直接决定最终性能表现。我实测过三种后端:

  1. ramdisk 后端:纯内存,延迟最低,但容量受内存限制,重启即丢。适合做基准测试。
  2. 物理 NVMe 分区:直接拿一块 NVMe 盘的一个分区做后端,性能接近本地盘,但会占用整块盘。
  3. 文件后端:在文件系统上创建一个稀疏文件作为后端,灵活但多了一层文件系统开销,性能略差。

在 configfs 里,后端设备通过device_path指定。对于 ramdisk,可以这样创建:

# 创建一个 1GB 的 ramdisk sudo modprobe brd rd_nr=1 rd_size=1048576 # 此时会出现 /dev/ram0

对于物理 NVMe 分区,直接用/dev/nvme0n1p1这样的路径。对于文件后端,需要先创建一个文件,然后用losetup映射成块设备:

dd if=/dev/zero of=/tmp/nvmeof-backend.img bs=1M count=1024 sudo losetup /dev/loop0 /tmp/nvmeof-backend.img

注意:文件后端用losetup映射后,性能会受文件系统影响,建议用--direct-io=on参数减少缓存干扰。

2.4 网络配置与端口规划

TCP 传输默认端口是 4420,RDMA 默认端口是 4420 和 4421(不同用途)。在 Ubuntu 18.04 上,如果开了ufw防火墙,需要放行:

sudo ufw allow 4420/tcp

另外,target 端监听的 IP 地址必须是 host 端能访问到的。如果机器有多张网卡,建议绑定到具体的业务网卡 IP,而不是0.0.0.0,避免走错路由。我一般用ip addr确认网卡名称和 IP,然后在 configfs 的ports里指定addr_trsvcid和addr_traddr。

3. 实操过程与核心环节实现

3.1 Target 端完整配置流程

假设 target 端机器 IP 是192.168.1.100,host 端 IP 是192.168.1.101。target 端用一块 1GB 的 ramdisk 作为后端。

第一步:加载模块并挂载 configfs

sudo modprobe nvmet sudo modprobe nvmet-tcp sudo modprobe brd rd_nr=1 rd_size=1048576 sudo mkdir -p /sys/kernel/config sudo mount -t configfs none /sys/kernel/config

第二步:创建子系统并设置后端设备

# 创建子系统目录,名字就是 NQN sudo mkdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub cd /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub # 允许任意 host 连接(生产环境建议用 allowed_hosts 限制) echo 1 | sudo tee attr_allow_any_host # 创建 namespace 1 sudo mkdir namespaces/1 cd namespaces/1 # 指定后端设备为 /dev/ram0 echo -n /dev/ram0 | sudo tee device_path # 启用 namespace echo 1 | sudo tee enable

第三步:创建端口并绑定传输层

# 创建端口 1 sudo mkdir /sys/kernel/config/nvmet/ports/1 cd /sys/kernel/config/nvmet/ports/1 # 设置监听地址和端口 echo -n 192.168.1.100 | sudo tee addr_traddr echo tcp | sudo tee addr_trtype echo 4420 | sudo tee addr_trsvcid echo ipv4 | sudo tee addr_adrfam # 绑定子系统 sudo ln -s /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub \ /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2024-01.com.example:testsub

第四步:验证 target 端状态

# 查看端口状态 cat /sys/kernel/config/nvmet/ports/1/addr_traddr # 查看子系统下的 namespace ls /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub/namespaces/1/ # 应该能看到 device_path 和 enable 文件

如果一切正常,dmesg里会看到类似nvmet_tcp: enabling port 1 (192.168.1.100:4420)的日志。

3.2 Host 端发现与连接

Host 端需要加载nvme-tcp模块,然后用nvme-cli发现和连接。

第一步:安装 nvme-cli

Ubuntu 18.04 默认源里的nvme-cli版本是 1.7 左右,基本够用:

sudo apt update sudo apt install nvme-cli

第二步:发现 target

sudo nvme discover -t tcp -a 192.168.1.100 -s 4420

正常输出会显示 target NQN、端口、以及支持的传输层。如果卡住或者报错,先检查网络连通性:

ping 192.168.1.100 telnet 192.168.1.100 4420

第三步:连接 target

sudo nvme connect -t tcp -n nqn.2024-01.com.example:testsub -a 192.168.1.100 -s 4420

连接成功后,dmesg会显示nvme nvme1: new ctrl: NQN="nqn.2024-01.com.example:testsub",并且lsblk能看到新的块设备:

lsblk # 应该能看到 nvme1n1 之类的设备

第四步:验证读写

# 格式化 sudo mkfs.ext4 /dev/nvme1n1 # 挂载 sudo mkdir /mnt/nvmeof sudo mount /dev/nvme1n1 /mnt/nvmeof # 测试写入 dd if=/dev/zero of=/mnt/nvmeof/testfile bs=1M count=100 oflag=direct # 测试读取 dd if=/mnt/nvmeof/testfile of=/dev/null bs=1M iflag=direct

3.3 性能对比与参数调优

我实测了一组数据,对比本地 ramdisk、NVMe-oF TCP、以及本地 NVMe 的 4K 随机读写:

存储类型4K 随机读 IOPS4K 随机写 IOPS平均延迟
本地 ramdisk约 500K约 450K约 2us
NVMe-oF TCP (万兆)约 80K约 70K约 120us
本地 NVMe约 300K约 250K约 10us

可以看到,TCP 传输下延迟比本地 NVMe 高了一个数量级,但比 iSCSI 还是好不少。如果换成 RDMA,延迟能压到 30us 以内。

调优方面,有几个参数值得关注:

  • MTU:如果网络支持,把 MTU 调到 9000(巨帧),能显著降低 CPU 占用和延迟。
  • 中断亲和性:把网卡中断绑定到固定 CPU 核,减少上下文切换。
  • 队列深度:nvme connect时可以指定-q参数调整队列深度,默认是 1,可以调到 4 或 8。
sudo nvme connect -t tcp -n nqn.2024-01.com.example:testsub -a 192.168.1.100 -s 4420 -q 4

提示:队列深度不是越大越好,超过网卡和 CPU 的处理能力反而会增加延迟。建议从 2 开始逐步往上试。

3.4 持久化配置与开机自启

上面的配置重启后会全部丢失,生产环境肯定不能这样。持久化分两部分:

Target 端:写一个 systemd 服务,在开机时执行模块加载和 configfs 配置脚本。

# /etc/systemd/system/nvmet-target.service [Unit] Description=NVMe-oF Target Setup After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/setup-nvmet.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target

setup-nvmet.sh里就是前面那些modprobe、mkdir、echo命令的集合。注意脚本里要用绝对路径,并且加上错误检查。

Host 端:可以用nvme connect-all配合discovery日志,或者直接写一个 systemd 服务在开机时执行nvme connect。

# /etc/systemd/system/nvmeof-connect.service [Unit] Description=Connect to NVMe-oF Target After=network.target [Service] Type=oneshot ExecStart=/usr/sbin/nvme connect -t tcp -n nqn.2024-01.com.example:testsub -a 192.168.1.100 -s 4420 RemainAfterExit=yes [Install] WantedBy=multi-user.target

4. 常见问题与排查技巧实录

4.1 连接失败类问题速查

现象可能原因排查命令解决方法
nvme discover超时网络不通或防火墙拦截ping、telnet检查 IP、端口、防火墙规则
nvme connect报Connection refusedtarget 端端口未监听ss -tlnp | grep 4420确认 target 端 configfs 端口配置正确
连接成功但lsblk看不到设备namespace 未启用cat enable确认echo 1 > enable已执行
dmesg报nvmet: no available ports端口未绑定子系统ls ports/1/subsystems/检查软链接是否创建成功
读写报 I/O 错误后端设备路径错误cat device_path确认后端设备存在且可读写

4.2 内核模块加载失败的处理

Ubuntu 18.04 上偶尔会遇到modprobe nvmet-tcp报Module not found。这通常是因为内核版本太老,或者linux-modules-extra包没装。先确认内核版本:

uname -r # 比如 4.15.0-213-generic

然后安装对应的额外模块包:

sudo apt install linux-modules-extra-$(uname -r)

如果还是不行,可能需要升级内核到 5.x 系列。Ubuntu 18.04 可以通过 HWE 内核升级:

sudo apt install linux-generic-hwe-18.04

升级后重启,再modprobe基本就能解决。

4.3 性能不达预期的排查思路

如果 NVMe-oF 的延迟比预期高很多,按这个顺序排查:

  1. 网络带宽是否跑满:用iperf3测一下实际带宽,如果只有几百兆,说明网卡协商速率有问题。
  2. MTU 是否一致:两端ip link show看 MTU,如果不一致会导致分片,性能骤降。
  3. CPU 是否瓶颈:top看ksoftirqd和nvme相关线程的 CPU 占用,如果单核跑满,说明中断没分散。
  4. 后端设备是否瓶颈:如果后端是文件或者慢速盘,先换成 ramdisk 排除后端影响。
  5. 队列深度是否合适:用nvme get-feature查看当前队列深度,适当调整。

注意:TCP 传输下,如果网卡不支持校验和卸载(checksum offload),CPU 占用会很高。用ethtool -k eth0确认tx-checksumming和rx-checksumming是否开启。

4.4 断开连接与清理顺序

清理的时候顺序很重要,搞错了会残留一堆垃圾目录,甚至导致内核 oops。

Host 端断开:

sudo nvme disconnect -n nqn.2024-01.com.example:testsub

Target 端清理:

# 先删除端口下的子系统软链接 sudo rm /sys/kernel/config/nvmet/ports/1/subsystems/nqn.2024-01.com.example:testsub # 删除端口 sudo rmdir /sys/kernel/config/nvmet/ports/1 # 禁用 namespace echo 0 | sudo tee /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub/namespaces/1/enable # 删除 namespace sudo rmdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub/namespaces/1 # 删除子系统 sudo rmdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:testsub

如果rmdir报Device or resource busy,说明还有引用没删干净,用ls -la逐层检查。

4.5 我踩过的三个坑

第一个坑:NQN 命名不规范导致连接失败。一开始我随便写了个test作为 NQN,结果nvme connect报Invalid argument。后来查资料才知道,NQN 必须符合nqn.<年份>-<月份>.<域名>:<自定义名称>的格式,比如nqn.2024-01.com.example:testsub。虽然有些版本宽松一点,但规范命名能避免很多玄学问题。

第二个坑:configfs 重复挂载。我写脚本的时候没加判断,每次执行都mount -t configfs none /sys/kernel/config,结果第二次执行报already mounted,脚本就中断了。后来改成先检查mountpoint -q /sys/kernel/config,没挂载才挂。

第三个坑:ramdisk 大小单位搞错。brd模块的rd_size参数单位是 KB,不是 MB。我一开始写rd_size=1024以为创建 1GB,实际只有 1MB,格式化都过不去。正确写法是rd_size=1048576(1GB = 1024*1024 KB)。

5. 进阶扩展与生产环境建议

5.1 从 TCP 切换到 RDMA 的关键改动

如果手头有支持 RoCE 的网卡,切换到 RDMA 其实改动不大,主要是模块和端口类型:

# Target 端 sudo modprobe nvmet-rdma # 端口配置里把 trtype 改成 rdma echo rdma | sudo tee addr_trtype # 端口号可以保持 4420

Host 端连接时把-t tcp改成-t rdma:

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

但 RDMA 对网络要求高,需要配置无损以太网(PFC 和 ECN),否则丢包会导致性能剧烈抖动。如果只是测试,可以先在直连场景下跑通。

5.2 多路径与高可用配置

生产环境肯定要考虑高可用。NVMe-oF 支持多路径(multipath),host 端可以同时连接同一个子系统的多个端口,内核的nvme-multipath会自动做负载均衡和故障切换。

配置方法是在 target 端创建多个端口(不同 IP 或不同网卡),host 端分别nvme connect到这些端口,但使用相同的 NQN。内核会自动识别为多路径设备,在/dev/nvmeXnY之上生成一个/dev/nvmeXcYnZ的聚合设备。

提示:多路径需要nvme-core模块支持,Ubuntu 18.04 的 4.15 内核已经包含基本的多路径功能,但高级特性(如路径优先级)可能需要更新内核。

5.3 监控与日常维护

跑起来之后,日常监控主要看几个地方:

  • nvme list:查看当前连接的 NVMe 设备。
  • nvme smart-log /dev/nvme1n1:查看远程设备的 SMART 信息(如果 target 端支持)。
  • dmesg -T | grep nvme:查看内核日志,排查连接断开、超时等问题。
  • cat /sys/kernel/config/nvmet/ports/1/addr_traddr:确认 target 端端口状态。

如果发现连接频繁断开,检查网络是否有丢包,或者 target 端后端设备是否响应超时。TCP 传输下,内核有重连机制,但重连期间 I/O 会阻塞,对上层应用影响较大。

5.4 适用场景与局限性

NVMe-oF 不是万能的。它适合的场景是:对延迟敏感、需要共享高性能存储、且网络条件较好的环境。比如数据库集群、高性能计算、虚拟化平台的后端存储。

不适合的场景也很明显:网络不稳定、带宽受限、或者对成本极度敏感的场景。TCP 传输下,万兆网络是起步,千兆网络跑 NVMe-oF 还不如本地 SATA SSD。另外,NVMe-oF 的配置复杂度比 NFS 高不少,运维成本要考虑进去。

我个人在实际操作中的体会是,NVMe-oF 在 Ubuntu 18.04 上跑通不难,难的是稳定性和性能调优。如果只是做概念验证,按上面的步骤一两个小时就能搞定;如果要上生产,网络、内核、后端存储每一层都得仔细打磨。最后再分享一个小技巧:测试阶段尽量用 ramdisk 后端,排除后端设备的干扰,等协议栈跑通了再换成真实设备,这样排查问题的效率会高很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询