卷在线扩容技术指南:SPDK RAID1 扩容流程、容错策略与验证方案)
云原生存储高可用容器编排【免费下载链接】longhornCloud-Native distributed storage built on and for Kubernetes项目地址https://gitcode.com/gh_mirrors/lo/longhorn点击查看免费下载导读本文围绕 Longhorn 针对 V2 Data Engine基于 SPDK 的引擎前端为 NVMe-oF推出的**卷在线扩容Volume Expansion**能力展开系统讲解其设计动机、gRPC API 变更、基于 SPDK RAID1 bdev 的引擎/副本双层扩容实现、部分副本失败时的容错策略以及 Live Expansion、Snapshot 行为、重建扩容等验证方案。读完本文你将理解 V2 卷扩容为何必须先删 RAID、如何利用 NVMe-oF 宽限期实现用户无感的透明扩容并掌握从准备环境到逐条验证的完整实操路径。一、背景与动机让 V2 Data Engine 走向生产可用Longhorn V2 Data Engine 是基于 SPDKStorage Performance Development Kit重新实现的第二代引擎。与 V1 引擎不同V2 引擎的数据通路完全由 SPDK 接管引擎与副本不再是直接参与数据搬运的独立进程而是一组由spdk_tgt管理的 SPDK 组件详见 SPDK Engine 增强文档。数据盘被抽象为 aio bdev lvstore每个快照或卷头文件是 lvstore 内的一个 lvol远端副本以 NVMe-oF subsystem 形式暴露引擎后端则是由多个副本 bdev 组成的 RAID1 bdev引擎前端通过 NVMe-oF initiator 加 subsystem 对外提供块设备。本次扩容增强的直接动机是将 V2 Data Engine 从 experimental 状态推向生产可用补齐缺失的功能、提升稳定性并实现与 V1 引擎的功能对齐functional parity。在该方向下V2 卷在线扩容对应 Issue #8022是关键的里程碑能力之一。从仓库的发布记录看该特性随 Longhorn 1.10.0 落地发布说明明确写道Longhorn 现已支持对 V2 Data Engine 卷进行扩容可通过 UI 或修改 PVC 清单触发见 CHANGELOG-1.10.0.md。目标与非目标类型内容目标允许用户对使用NVMe-oFblockdev前端的 V2 卷执行在线扩容目标确保扩容工作流与逻辑与 V1 引擎保持一致非目标不支持ublk前端的 V2 卷在线扩容也就是说本次扩容能力仅面向 NVMe-oF 前端SPDK TCPUBLK 前端不在本期范围内——这与 UBLK Frontend 增强文档中 UBLK 独立演进的定位一致。二、API 变更新增两个 SPDK gRPC RPC扩容能力通过 SPDK 引擎服务的 gRPC 接口对外暴露共新增两个 RPC引擎级扩容EngineExpand与副本级扩容ReplicaExpand。二者输入一致均为副本/引擎名称与目标大小rpc EngineExpand(EngineExpandRequest) returns (google.protobuf.Empty); message EngineExpandRequest{ string name 1; uint64 size 2; }rpc ReplicaExpand(ReplicaExpandRequest) returns (google.protobuf.Empty); message ReplicaExpandRequest{ string name 1; uint64 size 2; }其中size为目标卷大小字节。上层调用链为Longhorn Manager 卷控制器通过 instance manager 代理转发至 SPDK 引擎服务longhorn-spdk-engine引擎服务再分别驱动引擎与各副本完成扩容。从 SPDK Engine 增强文档的 API 表格可看到引擎/副本层所有操作Create、Delete、Snapshot、ReplicaAdd 等均由 Instance Manager 代理调用本次新增的EngineExpand/ReplicaExpand遵循同一模式。三、总体设计为什么扩容必须先拆 RAIDV2 引擎后端是一个 SPDK RAID1 bdev其基础成员base bdev是各副本的 lvol本地副本为 lvstore 内 lvol远端副本为通过 NVMe-oF attach 的远端 lvol。扩容实现依赖以下关键约束与设计决策RAID 删除是扩容前置条件SPDK RAID1要求所有基础 bdev 大小一致。要改变成员大小必须先删除 RAID1 bdev逐个 resize 成员 lvol再以新大小重建 RAID。如果扩容前不先 detach 前端控制器bdev_lvol_resize期间 SPDK 的同步 RPC 可能因存在活跃前端引用而挂起。因此扩容流程的第一步必然是断开前端、停止暴露并删除 RAID。扩容期间的 I/O 悬挂扩容期间引擎被锁定engine lockI/O 被暂停扩容完成后恢复 I/O。由于bdev_lvol_resize对 thin-provisioned 卷执行很快悬挂窗口极小。部分副本扩容失败的处理若只有部分副本扩容成功失败的副本会被标记为ERR随后仅用成功扩容的副本重建 RAIDRAID 要求成员同大小混入旧大小成员会失败。扩容流程仍然继续以最小化对用户的影响冗余则由 Longhorn 的副本重建机制在之后恢复。用户无感借助 NVMe-oF 宽限期NVMe-oF 允许一段宽限期grace period在此期间设备可以暂时消失而不触发主机侧断开相关参数为--ctrl-loss-tmocontroller loss timeout当前为 30 秒。实际操作中thin-provisioned 卷的bdev_lvol_resize很快因此从用户视角看设备始终在线扩容是透明的——用户感知不到底层 bdev 被重建并重连。前端重建失败重试如果重建前端失败Longhorn Manager 会持续重试扩容流程在下一个 reconciliation loop 中引擎服务会再次尝试建立前端连接。副本状态感知若任一副本处于rebuilding或expanding状态则跳过扩容。快照尺寸语义快照创建时保留原始大小即使其父 lvol 随后被 resize。因此从快照执行bdev_lvol_clone得到的新 lvol 大小为快照本身的大小而非扩容后的父 lvol 大小。示例当前卷 1GiB先打快照snapshot-11GiB扩容到 2GiB 后再打快照snapshot-22GiB。四、引擎侧扩容流程Engine Expand引擎级扩容是编排者它负责检查前置条件、拆除 RAID、并行驱动所有副本扩容、再重建 RAID 并重连前端。完整流程如下对应设计文档中的伪代码以下为展开详解Expand() │ ├── Lock engine ├── Log Expanding engine ├── getReplicaClients() │ └── defer closeReplicaClients() │ ├── requireExpansion(size, replicaClients) │ ├── Check: isExpanding → error │ ├── Check: IsRestoring → error │ ├── Check: SpecSize size → error │ ├── Check: SpecSize size → return false │ ├── RoundUp size to MiB → mismatch → error │ ├── Check ReplicaStatusMap is not empty │ ├── Check: all replicas are RW and same size │ ├── Check: currentReplicaSize size → error or skip │ └── Mark e.isExpanding true │ ├── Check: requireExpansion false │ └── Log and return nil │ ├── prepareRaidForExpansion(spdkClient) │ ├── BdevRaidGet() │ ├── If RAID exists: │ │ ├── If Frontend SPDKTCP Endpoint ! : │ │ │ └── initiator.Suspend() → defer Resume() │ │ ├── Else if Frontend ublk → return error │ │ ├── disconnectTarget(currentTargetAddress) │ │ └── StopExposeBdev() │ │ └── BdevRaidDelete() │ │ ├── NoSuchDevice → log and continue │ │ └── If !deleted → return error │ └── Return suspendFrontend, bdevUUID │ ├── expandReplicas(replicaClients, spdkClient, size) │ ├── For each replica in goroutine: │ │ ├── ReplicaGet() │ │ ├── If already at size → return │ │ ├── disconnectNVMfBdev() │ │ ├── ReplicaExpand() │ │ └── connectNVMfBdev() │ │ └── On any error → mark in failedReplica │ ├── wg.Wait() │ ├── If all replicas failed → return error │ ├── If some failed → mark as ModeERR │ └── Log partial failure, return nil │ ├── reconnectFrontend(spdkClient, bdevUUID, allocator) │ ├── Build replicaBdevList (only healthy ModeRW replicas) │ ├── If empty → return error │ ├── BdevRaidCreate() │ ├── retry.BdevRaidGet() with backoff │ ├── If Frontend SPDKTCP: │ │ ├── get pod IP │ │ ├── checkInitiatorAndTargetCreationRequirements() │ │ └── handleNvmeTcpFrontend() │ └── Else if ublk → return unsupported error │ ├── defer: │ ├── If retErr: │ │ ├── Log error │ │ ├── Set lastExpansionError, lastExpansionFailedAt │ │ ├── Set e.State Error, e.ErrorMsg │ │ └── UpdateLogger(replicaStatusMap) │ └── finishExpansion(expanded, size, err) │ ├── If expanded: │ │ ├── Log success │ │ └── Update SpecSize │ └── Else: │ └── Log failure │ └── return nil or wrapped error流程要点解读前置检查requireExpansion统一把关所有非法扩容请求——卷正在扩容isExpanding、正在恢复IsRestoring、目标小于当前 SpecSize、目标大小与当前一致幂等返回 false、目标未按 MiB 对齐round-up 后不一致、无副本、副本非全部 RW 或大小不一致、副本当前大小已不小于目标等均会在进入实际扩容前被拦截。通过检查后置e.isExpanding true防止并发扩容。拆 RAID 前置prepareRaidForExpansion若检测到 RAID 存在对 SPDKTCP 前端先initiator.Suspend()挂起前端并在流程末尾 deferResume()对 UBLK 前端则直接报错本期不支持随后断开 target、停止暴露 bdev 并BdevRaidDelete()。若 RAID 本就不存在NoSuchDevice记录日志并继续。并行副本扩容expandReplicas为每个副本启动 goroutine先ReplicaGet()确认当前大小对未达目标的副本依次执行disconnectNVMfBdev()→ReplicaExpand()→connectNVMfBdev()任一环节出错即记入failedReplica。全部失败则返回错误部分失败则将失败副本标记为ModeERR记录部分失败日志后继续。重建前端reconnectFrontend仅选取健康ModeRW副本的 bdev 构成成员列表——这正是 RAID1 等大小约束下容纳部分扩容成功的关键设计列表为空则报错。随后BdevRaidCreate()并以带退避的重试BdevRaidGet()确认 RAID 就绪最后走handleNvmeTcpFrontend()重建 NVMe-oF 前端。收尾与状态机出错时记录lastExpansionError/lastExpansionFailedAt将引擎置为Error状态并给出ErrorMsg成功时通过finishExpansion更新SpecSize。这些字段与 UBLK Frontend 增强文档 中展示的 Engine CR 状态字段isExpanding、lastExpansionError、lastExpansionFailedAt等一一对应。五、副本侧扩容流程Replica Expand每个副本独立执行 lvol resize是引擎扩容编排下的执行单元Expand() │ ├── Lock replica │ ├── fetchClusterSize(spdkClient) │ └── If error → return wrapped error │ ├── RoundUp(size, clusterSize) │ └── If roundedSize ≠ size → return error │ ├── Check SpecSize: │ ├── If SpecSize size → return error │ ├── If SpecSize size → log and return │ ├── If IsExposed: │ ├── StopExposeBdev(NQN) │ ├── If error ≠ NoSuchDevice → return error │ ├── Set IsExposed false │ └── reExposeBdev true │ ├── BdevLvolResize(alias, size) │ └── If !resized || err → verify actual lvol size │ ├── BdevLvolGetByName(alias) │ │ └── If error → return wrapped error │ ├── If lvol.SpecSize ≠ size: │ │ ├── If err → return wrapped error │ │ └── If !resized → return error │ └── Else → log success (despite earlier error) │ ├── If reExposeBdev: │ ├── Generate random NGUID │ ├── StartExposeBdev(NQN, UUID, NGUID, IP, Port) │ └── If error → return │ └── Set IsExposed true │ ├── Cleanup: │ ├── Set Head nil │ └── If last ActiveChain r.Name → remove from chain │ ├── updateHeadCache(spdkClient) │ └── If error → return │ ├── Log Expanding replica complete ├── Set SpecSize size └── Return nil流程要点解读对齐与幂等先fetchClusterSize获取 lvstore 簇大小将目标大小按簇大小 round-up若 round 后与请求不一致则报错即要求目标大小对齐簇大小SpecSize size报错、SpecSize size幂等返回。先停后扩若副本当前处于暴露IsExposed状态先StopExposeBdev(NQN)停止暴露并记reExposeBdev trueNoSuchDevice视为已停止随后执行BdevLvolResize(alias, size)。实际大小核验若 resize 返回未成功或出错通过BdevLvolGetByName(alias)读取 lvol 实际大小若实际SpecSize已等于目标说明底层已生效记录成功日志而非报错容忍 RPC 返回与实际情况不一致的边界场景否则按错误来源返回 wrapped error。重新暴露需要时生成随机 NGUID以StartExposeBdev(NQN, UUID, NGUID, IP, Port)重新暴露成功后置IsExposed true。元数据更新清理 head 缓存引用链、updateHeadCache刷新缓存最后更新SpecSize size。六、扩容结果语义V1 与 V2 对比设计文档给出了 V1 与 V2 两套扩容结果矩阵。V2 的实现有意与之对齐但在全部失败场景下行为更温和V1 扩容结果场景expansionSuccessoutOfSyncErrs行为全部副本成功truenil扩容前端部分副本成功truepresent将失败副本置为ERR继续前端扩容全部失败但回滚成功falsenil不扩容前端无副本被标记ERR全部失败且部分回滚失败falsepresent部分副本被标记ERR不扩容前端V2 扩容结果场景expansionSuccessoutOfSyncErrs行为全部副本成功truenil扩容前端部分副本成功truepresent将失败副本置为ERR继续前端扩容重建 RAID 时仅包含已扩容副本全部失败falsenil恢复前端无副本被标记ERR注意引擎使用 RAID1要求所有基础 lvol 大小一致。为容纳部分副本扩容成功引擎在重建 RAID bdev 时会排除任何失败的副本。对比可见V2 在全部失败场景下会尽力恢复前端RAID 重建回退且不标记任何副本为ERR降低了对数据路径的影响而部分失败时ERR副本之后可通过 Longhorn 的副本重建机制恢复冗余。七、测试计划从环境准备到三类验证准备通过 Longhorn UI 创建 V2 卷副本数至少 2建议 3选择V2 Data Engine前端选择Block DeviceNVMe-oF将卷 attach 到某个节点仓库提供了可直接参考的 V2 卷配置样例StorageClass 示例dataEngine: v2、dataLayout.mode: raid1、allowVolumeExpansion: true与 Pod PVC 示例。1. Live Expansion在线扩容在卷上执行 I/O直接用 fio 压到 device-mapperdm设备上在 I/O 持续进行时触发在线扩容通过 Longhorn UI 或 API 将卷扩容到更大尺寸确保扩容发生在 I/O 运行期间验证预期行为扩容期间及之后无 I/O 中断、无文件系统错误客户机内卷大小正确更新所有健康副本保持在线在容器中使用go-spdk-helper校验 bdev 信息2. Snapshot 行为快照尺寸语义假设原始大小 1GiB扩容至 2GiB扩容前打一个快照执行扩容扩容后再打一个快照从两个快照分别创建克隆第一个克隆应为 1GiB第二个克隆应为 2GiB3. Rebuilding Replica Expand重建 扩容联动对 device-mapperdm卷执行一些 I/O强制触发一次副本重建重建完成后执行扩容再对卷执行额外 I/O对引擎打快照触发底层 bdev lvol 快照计算并比对这些快照的 checksum——它们应当一致即各副本数据在新大小下保持一致性八、版本状态与升级策略该特性随 Longhorn1.10.0正式发布可通过 UI 或修改 PVC 清单触发 V2 卷扩容见 CHANGELOG-1.10.0.md。后续版本仍在持续打磨例如 1.11.0 修复了test_expansion_basic在 v2 引擎上的偶发问题、1.12.1 修复了扩容报告成功但引擎仍停留在旧大小的问题见 CHANGELOG-1.12.1.md使用时应以当前安装版本的实际行为为准。设计文档明确本增强无需升级策略No upgrade strategy is needed即该能力随引擎镜像一起提供不涉及存量数据的在线迁移或兼容性处理。九、注意事项与边界仅限 NVMe-oF 前端使用 UBLK 前端的 V2 卷不支持在线扩容扩容请求会在引擎侧直接报unsupported错误如需扩容请使用 Block DeviceNVMe-oF前端。副本状态约束任一副本处于rebuilding或expanding时引擎会跳过扩容请等待副本就绪后再触发。快照尺寸语义快照保留创建时刻的卷大小从旧快照克隆得到的是旧尺寸的 lvol规划克隆与恢复时需注意。部分失败会降级部分副本扩容失败时卷会降级运行失败副本置ERR依赖副本重建恢复冗余建议保持至少 2 个健康副本以支撑重建。大小对齐目标大小需按 MiB引擎侧与 lvstore 簇大小副本侧对齐非法值会在前置检查中被拒绝。数据一致性验证扩容尤其结合快照/重建场景后务必按测试计划比对各副本快照 checksum确认数据在新旧尺寸边界上保持一致。参考资源仓库内设计文档enhancements/20250721-v2-volume-expansion.mdV2 引擎架构背景enhancements/20230619-spdk-engine.mdUBLK 前端非目标说明enhancements/20250313-ublk-frontend-for-v2-engine.mdV2 卷配置样例examples/v2/replication/storageclass.yaml、examples/v2/replication/pod_with_pvc.yaml版本发布记录CHANGELOG/CHANGELOG-1.10.0.md、CHANGELOG/CHANGELOG-1.11.0.md、CHANGELOG/CHANGELOG-1.12.1.md相关设置项v2DataEngine、dataEngineHugepageEnabled、dataEngineMemorySize、dataEngineInterruptModeEnabled等见 chart/values.yaml赞分享云原生存储高可用容器编排【免费下载链接】longhornCloud-Native distributed storage built on and for Kubernetes项目地址https://gitcode.com/gh_mirrors/lo/longhorn点击查看免费下载相关推荐Longhorn存储卷扩容终极指南5分钟学会在线扩展存储空间 Longhorn存储卷扩容终极指南5分钟学会在线扩展存储空间 Longhorn作为Kubernetes生态中最受欢迎的 分布式存储卷管理器 其强大的云原生存储高可用容器编排Longhorn RWX 卷自动在线扩容share-manager FilesystemResize RPC 的设计与实现Longhorn RWX 卷自动在线扩容share manager FilesystemResize RPC 的设计与实现 本文以 Longhorn 增强提案云原生存储高可用容器编排KubeSphere存储卷扩容终极指南PVC在线扩容与文件系统调整完整教程KubeSphere存储卷扩容终极指南PVC在线扩容与文件系统调整完整教程 想要在Kubernetes集群中实现存储卷的无缝扩容吗KubeSphere作为企云原生容器编排后端微服务多集群DevOps可观测性AI 技能上一篇pnpm严格模式终极指南如何彻底解决幽灵依赖问题下一篇如何快速安装CaffeUbuntu/Windows/macOS详细教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考