土法炼钢 · 系统与基础设施

【SPDK 用户态存储】NVMe-oF Target:subsystem、listener 与 namespace→bdev

文章导航

分类入口
storagelinux
标签入口
#spdk#nvme-of#nvmf#target#subsystem#namespace#bdev#v26.01

目录

本机 bdev 就绪之后,要把块设备送给远端主机,SPDK 走的是 NVMe over Fabrics target:规范里的 subsystem / namespace / 传输,落到库 lib/nvmf 与示例应用 app/nvmf_tgt。协议背景见站内 storage/03 的 NVMe-oF 节;本文只钉 SPDK 对象如何咬合 bdev,不重写规范全书。

本文是「SPDK / 用户态存储栈」系列第 8 篇(共 16 篇)。→ 系列目录

篇目 核心内容
第 7 篇 · bdev 模块 叶子与叠层
第 8 篇 · NVMe-oF target subsystem / listener / NS→bdev
第 9 篇 · 传输层 RDMA / TCP 与中断模式

上一篇bdev 模块边界 · 下一篇传输层

版本锚定:SPDK v26.01NVMe over Fabrics TargetNVMe-oF Target Programming Guide;源码 lib/nvmf/app/nvmf_tgt/ @ v26.01。官方写明与 Linux 内核 NVMe-oF target/host 做过互通测试。无伪造 discover/connect 输出。

本篇不展开:RDMA vs TCP 选型与 interrupt mode(第 9 篇)、initiator 与多路径(第 10 篇)、TLS/DH-HMAC-CHAP 完整安全菜谱(仅作边界提及)。


一、Target 在栈上的位置

官方 NVMe-oF Target Getting Started:SPDK NVMe-oF target 是用户态应用,把块设备经 Ethernet / InfiniBand / Fibre Channel 等 fabric 导出;当前文档主线支持 RDMA 与 TCP(FC 依赖厂商 LLD,另议)。

术语对齐(官方刻意贴近规范、并借用 iSCSI 习惯语):

用语 含义
target 导出 subsystem 的一侧(SPDK 文档用语;规范侧重 subsystem 等对象)
host 连接过来的客户端(规范用语)
initiator 与 host 同义的常见叫法(iSCSI 遗留)
flowchart LR
  host["Host / initiator"] --> xport["Transport RDMA/TCP"]
  xport --> lis["listener"]
  lis --> sub["subsystem"]
  sub --> ns["namespace"]
  ns --> bdev["bdev"]
  bdev --> leaf["nvme/aio/malloc/..."]

关键不变量:namespace 就是 bdevProgramming Guide 原文:Namespaces are bdevs)。映射做错时,远端看到的是「空 subsystem」或错误容量,而不是「传输配错」——后者是 listener / transport 问题。


二、lib/nvmf 原始对象

NVMe-oF Target Programming Guide 列出库级原语(开发嵌入 lib/nvmf 时必须认全):

对象 规范/SPDK 职责
spdk_nvmf_tgt SPDK 引入 一组 subsystem + 传输与连接的集合
spdk_nvmf_subsystem 规范 subsystem 含 namespace 与 controller,做访问控制
spdk_nvmf_ns 规范 namespace 绑定一个 bdev
spdk_nvmf_transport 规范 fabric 抽象 RDMA/TCP/… 插件
spdk_nvmf_listener 监听地址 接受新连接
spdk_nvmf_qpair 规范 queue pair 与网络连接 1:1
spdk_nvmf_poll_group SPDK 引入 可一起轮询的一组连接
spdk_nvmf_host host NQN 访问控制条目

库与示例应用的分工:

嵌入式产品可以只链库、自建控制面;运维排障时仍建议先用 nvmf_tgt + RPC 建立心智模型,再映射到自有封装。


三、配置顺序:transport → subsystem → NS → listener

官方 Getting Started 用 RPC 给出最小闭环(语义级,不粘贴本机输出):

  1. 启动 nvmf_tgt(注意信号:文档要求尽量用 SIGTERM 释放共享内存;SIGKILL 可能导致手工清理)。
  2. nvmf_create_transport:按传输初始化(RDMA/TCP 参数如 I/O unit、max I/O、in-capsule data 等)。
  3. 准备 bdev(例如 bdev_malloc_createbdev_nvme_attach_controller)。
  4. nvmf_create_subsystem:NQN、序列号、是否 allow-any-host 等。
  5. nvmf_subsystem_add_ns把 bdev 名挂进 subsystem
  6. nvmf_subsystem_add_listener:传输类型、地址、服务端口(示例常用 4420)。

Programming Guide 对库 API 的状态机更严:subsystem 以 inactive 创建,经 spdk_nvmf_subsystem_start() 激活;运行中修改通常要 pause → 改 NS/host/listener → resume。RPC 封装会隐藏部分步骤,但 「先有 bdev,再 add_ns;先有 transport,再 add_listener」 这一依赖不变。

NQN 校验:官方按规范形式化 EBNF,并强调最大长度等约束;UUID 形式 NQN 按字节比较、不分大小写折叠——文档举例大小写不同的 UUID NQN 在 target 侧不相等。配置生成器应固定小写十六进制。

Discovery subsystem 会随 target 自动创建;discover 流量与普通 I/O subsystem 分流,但都走同一套 poll group 接纳模型。

配置完成后应用「读回核对」:用 RPC 列出 transport、subsystem、NS、listener,确认 NQN、bdev 名、地址端口与设计文档一致,而不是只看进程还在。NQN 字节级比较与大小写陷阱(官方对 UUID NQN 的警告)特别适合在这一步抓出来。多租户场景再核对 host 允许列表是否最小权限——allow-any-host 能省事,也能把发现面扩到不可接受的范围。


四、数据面:poll group 与无锁 I/O

Programming Guide 对路径的描述可以直接当排障坐标系:

  1. 新连接被 listener 接纳后,自动分配到某个 poll group
  2. poll group 在创建它的那条线程上轮询;也只应在该线程访问。
  3. 普通 NVM 命令(READ/WRITE 等)从收包到提交给 backing bdev,在同一线程走完;完成亦由该线程轮询 bdev/后端后回包。热路径不加跨线程锁
  4. Admin / 管理类命令可能改 subsystem 全局状态:通过向各线程发 message,让 subsystem 短暂 pause,期间新 I/O 排队,变更后再 resume。管理命令稀少,因此宁可用 pause 协调,也不给每条 I/O 加锁。
flowchart TB
  lis["listener accept"] --> assign["assign qpair to poll group"]
  assign --> pg["poll group on thread N"]
  pg --> nvm["NVM READ/WRITE"]
  nvm --> bdev["spdk_bdev submit"]
  bdev --> cpl["poll completion"]
  cpl --> rsp["fabric response"]
  pg --> adm["Admin / mgmt"]
  adm --> pause["pause subsystem via messages"]
  pause --> change["state change"]
  change --> resume["resume + drain queued I/O"]

零拷贝:官方写明 RDMA 传输下数据在 NIC 与主机内存、主机内存与 SSD 之间移动,不在主机内存内再拷一次;其他传输未来(或实现)可能需要拷贝。TCP 路径的缓冲策略见第 9 篇,不在此用未测数字对比 RDMA。

CPU 与 NUMA:Getting Started 建议 NIC 与 NVMe 同 NUMA;-m core mask 决定 reactor 可用核。target 性能首先是 poll group 线程是否被同核杂务拖死,其次才是单条 RPC 参数。

4.1 和「把盘 export 出去」相关的失败分层

把一次远端 READ 拆开,失败可能落在完全不同的层:

症状直觉 先查什么
传输 / listener 连不上、握手失败 IP、端口、nvmf_create_transport、防火墙、RDMA 模块
访问控制 连上被拒 / 看不见 NS host NQN、allow-any-host、listener 是否挂到该 subsystem
namespace 映射 容量为 0 或 I/O 全错 add_ns 的 bdev 名是否存在、bdev 是否被其他消费者 claim
bdev / 叶子 部分 I/O 失败、reset 第 6–7 篇的队列、后端健康、介质错误

官方还提醒:用信号杀进程时优先 SIGTERM,让应用释放共享内存;SIGKILL 可能导致资源残留需手工清理。这与「target 挂了再拉起来」的运维剧本直接相关——第 12 篇会谈状态保留边界,此处先把信号选择当成数据面纪律。

嵌入自有控制面时,Programming Guide 的状态机比 RPC 示例更值得当成规范:inactive / paused / running 的切换决定何时允许改 NS 与 host 表。把「线上直接改」理解成「任意时刻 mutate」,会在 I/O 仍涌入时撞上 pause 排队与管理命令稀有但昂贵的现实。产品若提供热加卷,应把 pause 窗口暴露成可观测事件,而不是藏进 RPC 成功率。


五、访问控制与安全边界(机制级)

默认:subsystem 不接受任意 host,也不在任意已建立 listen 地址上自动放行——需 nvmf_subsystem_add_host / add_listener(库 API 同名语义)。allow-any-host 是显式开关,与安全加固选项可能互斥(例如官方 TLS 节:--secure-channel--allow-any-host 互斥)。

v26.01 起,changelog 写明 target 支持 NVMe 2.0 所需特性集合,并增强 Identify CNS、可选命令掩码等;安全相关还有 TCP TLS(文档标 experimental)、DH-HMAC-CHAP 带内认证等。本篇只强调:认证/加密挂在 host 与 listener 配置上,不改变 NS→bdev 映射本身

NSSR(NVM Subsystem Reset)与固件更新在 Getting Started 后半有单独章节:NSSR 默认关闭,开启后会影响整个 subsystem 乃至共享同一底层 NVMe 的其他导出;固件 download/commit 后设备不会自动从系统消失,复位策略要运维显式设计。这些不是日常 I/O 热路径,但属于「target 角色」的管理面边界——把它们误当成「只影响单个 namespace」会在共享叶子 bdev 时放大爆炸半径。


子系统创建时的序列号、型号字符串与最大命名空间数等字段,决定了 initiator 侧 Identify 看到的身份信息。它们不改变 I/O 热路径,却影响自动化发现与监控标签是否稳定。生产上应把这些字符串当成 API 契约:随意改 SN 会导致监控系统把同一导出认成新设备。v26.01 对 NVMe 2.0 Identify 相关 CNS 的补齐,也意味着旧脚本若只解析部分 Identify 页面,升级后应回归一次发现与容量核对,而不是假设「能 connect 就够了」。

把 bdev 名写进 namespace 之后,容量、块大小与 unmap 能力都随该 bdev 走。换底层叶子(例如从 malloc 换成 nvme)时,若仍复用同一 bdev 名,initiator 可能只看到「同一设备变慢或变大」;若改名,则表现为新设备。命名策略应在控制面设计阶段定死,避免把调试期的临时名带进生产导出表。

六、谱系、争论与开放问题

谱系:NVMe-oF 把 NVMe 队列模型绑到多种 fabric(见 storage/03);SPDK 用用户态 poll group + bdev 实现 target,与内核 nvmet 形成两条实现线。官方明确做过与内核 host/target 的互通测试。用户态路线的卖点不是「多一个 export 工具」,而是与 reactor、bdev、可选 vhost 组成同一进程内的数据面——这是本系列 08–11 连读的原因。

争论:控制面用 JSON-RPC 热改 subsystem,还是像装置固件一样静态配置。RPC 灵活,但 pause 窗口与「配置与数据面谁先谁后」是事故高发区;静态配置可复现,却不利于多租户热加卷。另一条争论是「每个租户一个 subsystem」还是「大 subsystem 多 namespace」:前者隔离清晰、listener/NQN 管理成本高;后者省对象、blast radius 更大。SPDK 原语两种都允许,产品必须二选一写进规范。

开放问题

  1. Admin pause 与长尾:大规模连接下 subsystem pause 的扇出延迟如何度量与限流,仍缺少与业务 SLO 绑定的标准做法。
  2. NSSR / 固件升级与多 subsystem 共享同一 bdev_nvme:官方 NSSR 文档警告跨 subsystem 副作用——生产编排是否允许共享叶子 bdev,值得写成显式策略。
  3. Discovery 过滤与多租户暴露面:v26.01 增加 spdk_nvmf_set_custom_discovery_filter();策略引擎与审计模型仍属产品层空白。
  4. poll group 与 bdev channel 的亲和策略:连接哈希到 poll group 之后,如何保证其提交的 bdev channel 仍落在同核、同 NUMA,缺少一键式官方配方,常靠部署约定。

七、参考资料

规范 / 官方文档(A)

源码(A)

站内

实验台账


以上机制足以支撑「把本地 bdev 正确导出」;剩下的延迟与 CPU 形态,取决于传输与轮询策略,而不是再改一次 NQN 字符串。

八、小结

  1. lib/nvmf 实现 target;nvmf_tgt 是 RPC 化示例进程。
  2. namespace = bdev;listener 管「从哪进」,subsystem 管「谁能看哪些 NS」。
  3. 普通 I/O 在单 poll group 线程无锁走完;管理命令用 subsystem pause 协调。
  4. 传输怎么选、低负载如何少空转——见 第 9 篇

附:和内核 nvmet 对照时该问的三句话

读内核文档或发行版「NVMe-oF target」howto 时,用三句话对齐 SPDK,避免概念错位:

  1. 配置面在哪? 内核多在 configfs/nvmetcli;SPDK 在进程内 JSON-RPC 与 lib/nvmf 状态机。
  2. 数据面线程模型? 内核走内核线程与中断/软中断习惯;SPDK 默认 reactor 轮询,可选 interrupt mode(第 9 篇)。
  3. 块设备从哪来? 内核 target 挂内核块设备;SPDK namespace 必须是 bdev——这是本系列 06–08 的接缝。

互通测试证明「能连」,不证明「运维模型相同」。把内核 runbook 原样贴到 nvmf_tgt 上,是最常见的跨实现误迁移。

系列目录 · 上一篇:bdev 模块边界 · 下一篇:传输层

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .