jimyag's Blog

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 仍受限"]

测试环境

只记录本次用到的软件版本:

软件版本
kindv0.31.0
Kubernetesv1.35.0
KubeVirtv1.9.0
CDIv1.66.0
Kube-OVNv1.14.3

源、目标 PV 都是静态 PV,配置如下:

  • accessModes: ReadWriteOnce
  • volumeMode: Filesystem
  • persistentVolumeReclaimPolicy: Retain
  • 源、目标 PVC 分别绑定固定的 PV
  • 源、目标 PV 使用不同的 NFS 导出路径,并通过节点亲和性固定到不同 worker

NFS Deployment 使用 emptyDir 保存后端数据,只适合实验。它证明的是 KubeVirt 能否完成这条 Volume Migration 控制链,不证明 Ceph CSI/RBD 的 attach、detach 或 RBD image 语义。真实云盘环境还需要用实际 CSI 驱动单独验证。

实验文件已经提交到 k8sdev 0337aacd

前置配置

Volume Migration 依赖 KubeVirt 的 LiveUpdateLiveMigrate 配置。实验配置如下:

Loading GitHub file…

这个配置是 KubeVirt CR 的集群级配置,只应在专用实验集群或确认影响范围后应用。

相对于没有启用卷在线更新的 KubeVirt CR,关键新增项如下:

1
2
3
4
5
6
7
8
diff --git a/KubeVirt CR b/KubeVirt CR
@@
+spec:
+  configuration:
+    vmRolloutStrategy: LiveUpdate
+  workloadUpdateStrategy:
+    workloadUpdateMethods:
+    - LiveMigrate

这是迁移前置条件,不是源 PVC 到目标 PVC 的数据切换本身。

先准备 NFS 模拟后端:

Loading GitHub file…

准备静态 RWO PV/PVC:

Loading GitHub file…

在目标集群中执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
export EXPERIMENT_ROOT=/tmp/rwo-volume-migration-nfs
git clone https://github.com/jimyag/k8sdev.git "$EXPERIMENT_ROOT"
git -C "$EXPERIMENT_ROOT" checkout 0337aacd803045fa21080702057a14894d328e9f
cd "$EXPERIMENT_ROOT/rwo-volume-migration-nfs"

kubectl --context kind-rwo-rwx-lab apply -f 00-kubevirt-config.yaml
kubectl --context kind-rwo-rwx-lab apply -f 01-nfs.yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  rollout status deployment/nfs-server --timeout=180s
kubectl --context kind-rwo-rwx-lab apply -f 02-static-rwo-pv-pvc.yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  wait --for=jsonpath='{.status.phase}'=Bound \
  pvc/volume-migration-source pvc/volume-migration-target --timeout=180s

确认两组对象都是 BoundReadWriteOnceFilesystem 后再启动 VM。不要只看 PVC 是不是 Bound,还要核对 PV/PVC 的 access mode、volume mode 和 reclaim policy。

真正改了什么

这次不是修改 PVC 的 accessModes,也不是修改 PV。真正触发 Volume Migration 的改动只有 VM 数据盘的 PVC 引用:

1
2
3
4
5
6
7
8
diff --git a/rwo-volume-migration-nfs/04-vm-source.yaml b/rwo-volume-migration-nfs/05-vm-target.yaml
--- a/rwo-volume-migration-nfs/04-vm-source.yaml
+++ b/rwo-volume-migration-nfs/05-vm-target.yaml
@@ -44,7 +44,7 @@ spec:
       - name: datadisk
         persistentVolumeClaim:
-          claimName: volume-migration-source
+          claimName: volume-migration-target

这段差异里,- 是迁移前的源 PVC,+ 是迁移后的目标 PVC。updateVolumesStrategy: Migration 在两个 VM 文件中都存在,并不是这次切换时才新增的字段。

可以按下面三层理解:

配置位置作用是否是本次切换的差异
KubeVirt CR开启 LiveUpdateLiveMigrate前置配置
VM updateVolumesStrategy指定卷引用变化使用 Volume Migration源、目标文件都必须保留
VM datadisk.claimName从源 PVC 改为目标 PVC真正触发迁移的改动

因此,04-vm-source.yaml05-vm-target.yaml 大部分内容相同,重点只看 datadisk 对应的 claimName。目标 PVC 必须提前创建并处于 Bound,KubeVirt 不会因为这个字段变化自动创建目标卷。

先验证源 PVC

源 PVC 先由一次性 Pod 挂载并写入一个文件:

Loading GitHub file…
1
2
3
4
5
6
kubectl --context kind-rwo-rwx-lab apply -f 03-seed-source.yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  wait --for=jsonpath='{.status.phase}'=Succeeded pod/seed-source --timeout=180s
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration logs pod/seed-source
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  delete pod/seed-source --wait=true

这个 Pod 的作用是提前确认静态 PVC 能被节点挂载和写入,并不是给 VM 生成业务数据。RWO PVC 不能长期被这个 Pod 占用,所以检查完成后要删除它。

还有一个容易误判的地方:这里的 PVC 是 Filesystem,KubeVirt 会在挂载目录中使用 disk.img 作为 VM 数据盘。上面写入的 sentinel.txt 位于挂载目录根部,不等于 guest 文件系统里的文件,因此不能直接拿它作为迁移后的 VM 数据证明。最终校验比较 disk.img 的哈希。

启动 VM 并迁移数据盘

源卷版本的 VM:

Loading GitHub file…
1
2
3
4
kubectl --context kind-rwo-rwx-lab apply -f 04-vm-source.yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  wait --for=condition=Ready vmi/volume-migration-vm --timeout=180s
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration get vmi volume-migration-vm -o wide

此时 VM 使用 volume-migration-source。把 VM 清单中的数据盘改为目标 PVC:

Loading GitHub file…
1
2
3
kubectl --context kind-rwo-rwx-lab apply -f 05-vm-target.yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  get vmim -l kubevirt.io/volume-update-migration=volume-migration-vm -w

需要看到 VMIM 进入 Succeeded,并检查 VM.status.volumeUpdateState

1
2
3
4
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  get vmim -l kubevirt.io/volume-update-migration=volume-migration-vm -o yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  get vm volume-migration-vm -o yaml

本次实测结果:

  • VMIM kubevirt-workload-update-xbp7h 进入 Succeeded
  • VM 仍在运行,VMI 从源节点切换到目标节点。
  • VM.status.volumeUpdateState.volumeMigrationState.migratedVolumes 记录了 volume-migration-sourcevolume-migration-target 的切换。
  • 源、目标 PVC 的 accessModes 都是 ReadWriteOncevolumeMode 都是 Filesystem

不要把“生成了 VMIM”当成完成证据。VMIM 还要等到 Succeeded,并且要继续检查目标卷中的数据。

校验目标数据

先停止 VM,让目标 RWO PVC 可以被检查 Pod 单独挂载:

1
2
3
4
5
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  patch vm volume-migration-vm --type=merge \
  -p '{"spec":{"runStrategy":"Halted"}}'
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  wait --for=delete vmi/volume-migration-vm --timeout=180s

目标校验 Pod:

Loading GitHub file…
1
2
3
4
kubectl --context kind-rwo-rwx-lab apply -f 06-check-target.yaml
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration \
  wait --for=jsonpath='{.status.phase}'=Succeeded pod/check-target --timeout=180s
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration logs pod/check-target

输出中的目标文件为 disk.img,本次 SHA-256 是:

1
49bc20df15e412a64472421e13fe86ff1c5165e18b2afccf160d4dc19fe68a14

源、目标 disk.img 的哈希一致,说明这次迁移复制出的数据与源端一致。

迁移后的访问模式

最后再次检查 PV/PVC:

1
2
3
4
kubectl --context kind-rwo-rwx-lab get pv \
  volume-migration-source-pv volume-migration-target-pv
kubectl --context kind-rwo-rwx-lab -n rwo-volume-migration get pvc \
  volume-migration-source volume-migration-target

结果仍然是:

1
2
source PV/PVC   Bound   ReadWriteOnce   Filesystem   Retain
target PV/PVC   Bound   ReadWriteOnce   Filesystem   Retain

VM 的 LiveMigratable 条件为 False,原因是目标 PVC 仍然不是共享卷:

1
2
reason: DisksNotLiveMigratable
message: PVC volume-migration-target is not shared, live migration requires that all PVCs must be shared (using ReadWriteMany access mode)

所以,Volume Migration 和 RWO 转 RWX 是两件事:

  • Volume Migration:运行中的 VM 从旧 PVC 迁移到已准备好的新 PVC。
  • RWO 转 RWX:需要后端数据复用和 PV/PVC 对象重建,访问模式由 ReadWriteOnce 改为 ReadWriteMany

如果目标是让 VM 后续支持普通热迁移,目标存储必须真正支持共享访问,并且目标 PV/PVC 要按 RWX 创建。只修改 PVC 的 accessModes 不会改变后端存储能力。

这次实验的注意事项

  1. 目标 PVC 必须先存在。Volume Migration 不会替你创建目标卷,也不会替你把 RWO 改成 RWX。
  2. LiveUpdateLiveMigrate 是必要前置条件。缺少时,更新 VM 的卷引用可能只停留在 VolumesChange=True,看不到有效的 VMIM。
  3. 如果一次更新没有生成 VMIM,先把 VM 恢复到源 PVC,等待 VolumesChangeCancellation 完成,再重新更新到目标 PVC。不要在状态未收敛时连续覆盖 VM 清单。
  4. NFS 后端在这个实验中可以被多个节点访问,但 PV 仍声明为 RWO。因此它不能验证真实云盘的独占挂载、CSI attach/detach 或 RBD image 行为。
  5. 生产环境删除 PV 前,要确认 Retain 和后端资产的关系,并保存 volumeHandle、存储池、集群标识、volumeMode、挂载参数及 Secret 引用。重建对象时漏掉 CSI 字段,PVC 可能仍是 Bound,实际挂载却会失败。
  6. 验证结果至少要同时包含 VMIM 终态、VMI/VM 的卷迁移状态、目标卷数据和 PV/PVC 状态。PVC Bound、HTTP 成功或 VMIM 创建都只是中间状态。

#Kubernetes #Kubevirt #存储