KubeVirt RWO 云盘 Volume Migration 实测
KubeVirt RWO 云盘 Volume Migration 实测
本文验证 KubeVirt 的 Volume Migration:VM 运行时,把数据盘从一个静态 RWO PVC 迁移到另一个静态 RWO PVC。
这里没有把 NFS 伪装成 Ceph。测试集群没有 Ceph CSI/RBD,因此用两个 NFS 导出目录模拟源、目标后端,重点验证 KubeVirt 的卷迁移流程,以及迁移后数据和 PVC 状态是否正确。
结论先放在前面:迁移成功,目标 PVC 仍然是 RWO。Volume Migration 复制数据并切换 VM 的卷引用,不会把 RWO 自动变成 RWX。迁移完成后,VM 仍不能进行普通的共享存储热迁移。
Volume Migration 做什么
KubeVirt 的 Volume Migration 用于在 VM 运行时更换存储。源卷和目标卷需要提前准备好,KubeVirt 负责复制数据并更新 VM 的卷引用,不负责创建目标 PVC 或目标存储。
本次实验的关系是:
flowchart LR
A["VM 运行<br/>source PVC: RWO"] --> B["更新 datadisk<br/>claimName"]
B --> C["KubeVirt Volume Migration<br/>VMIM Succeeded"]
C --> D["target PVC: RWO"]
D --> E["disk.img 哈希一致"]
D -.-> F["普通 Live Migration 仍受限"]
两种 RWO Volume Migration 方式
RWO 卷迁移有两种资源模型,区别不在 KubeVirt 是否创建 VMIM,而在 VM 的卷引用和 PVC 的管理者:
| 对比项 | 直接使用 PVC | 使用 DataVolume |
|---|---|---|
| VM 的卷引用 | persistentVolumeClaim.claimName | dataVolume.name |
| 目标资源 | 预先创建并绑定目标 PVC | 预先绑定目标 PVC,再创建同名 DV 接管 |
| PVC 管理者 | 没有 DV OwnerReference | PVC 的 controller OwnerReference 指向同名 DV |
| 数据复制者 | KubeVirt Volume Migration | 仍然是 KubeVirt Volume Migration |
| CDI 的作用 | 不参与 | 管理 DV/PVC 的归属和就绪状态 |
| 迁移前检查 | PVC Bound、PV/PVC 是 RWO | PVC Bound、DV Succeeded、OwnerReference 正确、没有 importer Pod |
两种方式最后都不会把目标 RWO 变成 RWX。DataVolume 不是另一种数据复制引擎,它解决的是 PVC 的资源生命周期和归属问题。
测试环境
只记录本次用到的软件版本:
| 软件 | 版本 |
|---|---|
| kind | v0.31.0 |
| Kubernetes | v1.35.0 |
| KubeVirt | v1.9.0 |
| CDI | v1.66.0 |
| Kube-OVN | v1.14.3 |
源、目标 PV 都是静态 PV,配置如下:
accessModes: ReadWriteOncevolumeMode: FilesystempersistentVolumeReclaimPolicy: Retain- 源、目标 PVC 分别绑定固定的 PV
- 源、目标 PV 使用不同的 NFS 导出路径
NFS Deployment 使用 emptyDir 保存后端数据,只适合实验。它证明的是 KubeVirt 能否完成这条 Volume Migration 控制链,不证明 Ceph CSI/RBD 的 attach、detach 或 RBD image 语义。真实云盘环境还需要用实际 CSI 驱动单独验证。
实验文件已经提交到 k8sdev 0337aacd。
DataVolume 版本使用独立的实验目录,提交为 5aea5d4。
前置配置
Volume Migration 依赖 KubeVirt 的 LiveUpdate 和 LiveMigrate 配置。实验配置如下:
这个配置是 KubeVirt CR 的集群级配置,只应在专用实验集群或确认影响范围后应用。
相对于没有启用卷在线更新的 KubeVirt CR,关键新增项如下:
| |
这是迁移前置条件,不是源 PVC 到目标 PVC 的数据切换本身。
先准备 NFS 模拟后端:
准备静态 RWO PV/PVC:
在目标集群中执行:
| |
确认两组对象都是 Bound、ReadWriteOnce、Filesystem 后再启动 VM。不要只看 PVC 是不是 Bound,还要核对 PV/PVC 的 access mode、volume mode 和 reclaim policy。
方式一:KubeVirt 直接切换 PVC
这一种方式里,VM 直接引用源 PVC 和目标 PVC。目标 PVC 必须提前创建并绑定,KubeVirt 发现 VM 的 PVC 引用变化后,负责执行 Volume Migration。
真正改了什么
这次不是修改 PVC 的 accessModes,也不是修改 PV。真正触发 Volume Migration 的改动只有 VM 数据盘的 PVC 引用:
| |
这段差异里,- 是迁移前的源 PVC,+ 是迁移后的目标 PVC。updateVolumesStrategy: Migration 在两个 VM 文件中都存在,并不是这次切换时才新增的字段。
可以按下面三层理解:
| 配置位置 | 作用 | 是否是本次切换的差异 |
|---|---|---|
| KubeVirt CR | 开启 LiveUpdate 和 LiveMigrate | 前置配置 |
VM updateVolumesStrategy | 指定卷引用变化使用 Volume Migration | 源、目标文件都必须保留 |
VM datadisk.claimName | 从源 PVC 改为目标 PVC | 真正触发迁移的改动 |
因此,04-vm-source.yaml 和 05-vm-target.yaml 大部分内容相同,重点只看 datadisk 对应的 claimName。目标 PVC 必须提前创建并处于 Bound,KubeVirt 不会因为这个字段变化自动创建目标卷。
先验证源 PVC
源 PVC 先由一次性 Pod 挂载并写入一个文件:
| |
这个 Pod 的作用是提前确认静态 PVC 能被节点挂载和写入,并不是给 VM 生成业务数据。RWO PVC 不能长期被这个 Pod 占用,所以检查完成后要删除它。
还有一个容易误判的地方:这里的 PVC 是 Filesystem,KubeVirt 会在挂载目录中使用 disk.img 作为 VM 数据盘。上面写入的 sentinel.txt 位于挂载目录根部,不等于 guest 文件系统里的文件,因此不能直接拿它作为迁移后的 VM 数据证明。最终校验比较 disk.img 的哈希。
启动 VM 并迁移数据盘
源卷版本的 VM:
| |
此时 VM 使用 volume-migration-source。把 VM 清单中的数据盘改为目标 PVC:
| |
需要看到 VMIM 进入 Succeeded,并检查 VM.status.volumeUpdateState:
| |
本次实测结果:
- VMIM
kubevirt-workload-update-xbp7h进入Succeeded。 - VM 仍在运行,VMI 从源节点切换到目标节点。
VM.status.volumeUpdateState.volumeMigrationState.migratedVolumes记录了volume-migration-source到volume-migration-target的切换。- 源、目标 PVC 的
accessModes都是ReadWriteOnce,volumeMode都是Filesystem。
不要把“生成了 VMIM”当成完成证据。VMIM 还要等到 Succeeded,并且要继续检查目标卷中的数据。
校验目标数据
先停止 VM,让目标 RWO PVC 可以被检查 Pod 单独挂载:
| |
目标校验 Pod:
| |
输出中的目标文件为 disk.img,本次 SHA-256 是:
| |
源、目标 disk.img 的哈希一致,说明这次迁移复制出的数据与源端一致。
方式二:DataVolume 管理 PVC
如果业务侧不是直接管理 PVC,而是通过 CDI 的 DataVolume 管理磁盘,就不能只把 VM 的 persistentVolumeClaim.claimName 换成另一个 PVC。VM 应该引用 DataVolume,PVC 的归属也要先交给对应的 DataVolume。
这里使用 CDI 的 DataVolume Claim Adoption:先创建并绑定静态目标 PVC,再创建同名 DataVolume,并加上 cdi.kubevirt.io/allowClaimAdoption: "true"。CDI 接管这个 PVC 后,会给 PVC 增加指向 DataVolume 的 controller OwnerReference,并把 DataVolume 标记为 Succeeded。这个过程不会创建 importer Pod,也不会初始化或覆盖已有数据。
因此,这种方式仍然分成两步:
- CDI 负责把已经存在的 PVC 纳入 DataVolume 的生命周期。
- KubeVirt 发现 VM 的
dataVolume.name从源 DV 变成目标 DV 后,负责执行 Volume Migration。
先准备静态 PVC
源、目标 PV/PVC 的配置与前面的实验相同,都是 ReadWriteOnce、Filesystem、Retain。区别是这里的 PVC 会被同名 DataVolume 接管:
执行准备命令:
| |
先确认两个 PVC 都是 Bound,并且 PV/PVC 仍然是 ReadWriteOnce 和 Filesystem。
让 CDI 接管 PVC
源、目标 DataVolume 都必须与对应 PVC 使用相同的 namespace 和 name。source.blank: {} 在这里不是“清空磁盘”,而是让 DataVolume 在 Claim Adoption 场景下不执行导入;真正的数据仍然来自已经绑定的 PVC。
源 DataVolume:
目标 DataVolume:
先让 CDI 接管源 PVC,再启动 VM:
| |
输出中应该能看到 PVC 的 controller OwnerReference 指向 DataVolume/volume-migration-dv-source。再检查 CDI 没有启动 importer:
| |
这个查询没有 Pod 是预期结果。Claim Adoption 只建立资源管理关系,不负责复制数据。
真正触发迁移的改动
启动引用源 DataVolume 的 VM:
| |
接着创建目标 DataVolume。目标 PVC 必须已经是 Bound,并且没有被其他对象控制:
| |
目标 PVC 的 OwnerReference 指向目标 DataVolume,且没有 importer Pod 后,再把 VM 的数据盘从源 DV 改为目标 DV:
这里的实际 diff 只有 DataVolume 引用变化:
| |
应用目标 VM 并等待 VMIM 完成:
| |
DataVolume 方式的验证结果
本次实测结果如下:
- 源、目标 DataVolume 都进入
Succeeded。 - 目标 PVC 的 controller OwnerReference 指向同名目标 DataVolume。
- 两个 DataVolume 都没有启动 importer Pod。
- VMIM 进入
Succeeded,VM 的migratedVolumes记录了源 PVC 到目标 PVC 的切换。 - 停止 VM 后挂载目标 PVC,
disk.img的 SHA-256 仍为49bc20df15e412a64472421e13fe86ff1c5165e18b2afccf160d4dc19fe68a14。 - 目标 PV/PVC 仍然是
ReadWriteOnce,并没有因为使用 DataVolume 变成ReadWriteMany。
停止 VM 后可以用下面的清单检查目标磁盘:
| |
两种方式怎么选
直接使用 PVC 的方式更简单,适合控制器本身就管理 PV/PVC 的场景;DataVolume 方式适合已经把磁盘生命周期交给 CDI 的场景。两者的迁移主体都是 KubeVirt,区别只是 VM 引用的对象和 PVC 的管理者不同。
DataVolume 方式需要特别注意:
- DataVolume 和要接管的 PVC 必须同 namespace、同 name。
- PVC 必须先绑定,并且不能已经被其他对象控制。
- DataVolume 的存储参数要与 PVC/PV 对齐,至少要核对 access mode、volume mode、storage class 和容量。
- Claim Adoption 只接管 PVC,不复制数据;数据复制仍由 KubeVirt Volume Migration 完成。
- 生产控制器不能只看到 DataVolume
Succeeded或 VMIM 创建就认为完成,还要等 VMIMSucceeded、VM 状态收敛,并在目标卷上做数据校验。 - 失败处理时不要直接删除目标 PVC。先保留 PV、PVC、DataVolume 和 VMIM 的状态,确认目标卷是否已经写入,再决定回滚或清理。
迁移后的访问模式
最后再次检查 PV/PVC:
| |
结果仍然是:
| |
VM 的 LiveMigratable 条件为 False,原因是目标 PVC 仍然不是共享卷:
| |
所以,Volume Migration 和 RWO 转 RWX 是两件事:
- Volume Migration:运行中的 VM 从旧 PVC 迁移到已准备好的新 PVC。
- RWO 转 RWX:需要后端数据复用和 PV/PVC 对象重建,访问模式由
ReadWriteOnce改为ReadWriteMany。
如果目标是让 VM 后续支持普通热迁移,目标存储必须真正支持共享访问,并且目标 PV/PVC 要按 RWX 创建。只修改 PVC 的 accessModes 不会改变后端存储能力。
这次实验的注意事项
- 目标 PVC 必须先存在。Volume Migration 不会替你创建目标卷,也不会替你把 RWO 改成 RWX。
LiveUpdate和LiveMigrate是必要前置条件。缺少时,更新 VM 的卷引用可能只停留在VolumesChange=True,看不到有效的 VMIM。- 如果一次更新没有生成 VMIM,先把 VM 恢复到源 PVC,等待
VolumesChangeCancellation完成,再重新更新到目标 PVC。不要在状态未收敛时连续覆盖 VM 清单。 - NFS 后端在这个实验中可以被多个节点访问,但 PV 仍声明为 RWO。因此它不能验证真实云盘的独占挂载、CSI attach/detach 或 RBD image 行为。
- 生产环境删除 PV 前,要确认
Retain和后端资产的关系,并保存volumeHandle、存储池、集群标识、volumeMode、挂载参数及 Secret 引用。重建对象时漏掉 CSI 字段,PVC 可能仍是Bound,实际挂载却会失败。 - 验证结果至少要同时包含 VMIM 终态、VMI/VM 的卷迁移状态、目标卷数据和 PV/PVC 状态。
PVC Bound、HTTP 成功或 VMIM 创建都只是中间状态。