Skip to content

Demo 1b: live migration and storage migration (bonus)

Version 1.1.3 · draft · 2026-10-17 · class/demos/01b-live-and-storage-migration.md

What you will learn

Two moves on a running VM, with no reboot: a live migration moves its memory and CPU to another node, and a storage migration moves one of its disks to another storage class (ODF → NFS) while Windows and PostgreSQL keep running. You write a row before and after each move and read them back.

Before you start: finish Demo 1. Your VM runs on site A, and every command runs in the site A console's Web Terminal (>_).

⚠ The commands are an example for student-01-0024-012, VM win-pg-s01 at 10.101.23.101. Change them to your class ID, your VM and its address before you run them.

🎬 Demo 1b: live migration and storage migration: a walk-through of the whole demo in the console. Watch it, then do the same with the steps below.


1b.1 Inject, and check the VM can live-migrate

oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT * FROM ovedr_mark('student-01-0024-012', 'before-live-migration', 'running on one node');"
oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT id, ts, stage, payload FROM marks ORDER BY id;"   # read back: your rows so far, oldest first
oc -n student-01-0024-012 get vmi win-pg-s01 -o jsonpath='{.status.nodeName}  LiveMigratable={.status.conditions[?(@.type=="LiveMigratable")].status}{"\n"}'

✅ LiveMigratable=True. ✍️ Note the node it runs on now. A live migration needs the disks reachable from both nodes at once (ReadWriteMany): ODF's ocs-storagecluster-ceph-rbd-virtualization is. If it says False, the condition's message names the reason, usually a mounted CD.


1b.2 Live-migrate to another node

Console: Virtualization → VirtualMachines → win-pg-s01 → Actions → Migrate → Compute. Or:

virtctl -n student-01-0024-012 migrate win-pg-s01
oc -n student-01-0024-012 get vmim -w                                 # … Succeeded (Ctrl-C)
oc -n student-01-0024-012 get vmi win-pg-s01 -o jsonpath='{.status.nodeName}{"\n"}'

✅ The VM runs on a different node, and it never stopped: the console's VM Overview shows the new node and no restart.

oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT * FROM ovedr_mark('student-01-0024-012', 'after-live-migration', 'running on another node');"
oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT id, ts, stage, payload FROM marks ORDER BY id;"   # read back: your rows so far, oldest first

1b.3 Storage migration: move the data disk from ODF to NFS, while it runs

Your VM's disks are on ODF (ocs-storagecluster-ceph-rbd-virtualization). Move the 1 GB data disk (D:) to nfs-csi, with the VM running. OpenShift Virtualization copies the disk in the background, then switches the VM to the new copy with a live migration.

Proven on this cluster (0024-015, 2026-10-16): a 1 GiB disk moved from ODF (block) to nfs-csi (filesystem) in 46 seconds, the VM never restarted, and its data was intact. [verify: whether the console's Actions → Migrate → Storage offers the same]

First check the VM allows it:

oc -n student-01-0024-012 get vm win-pg-s01 -o jsonpath='StorageLiveMigratable={.status.conditions[?(@.type=="StorageLiveMigratable")].status}{"\n"}'

✅ StorageLiveMigratable=True.

Find the data disk's volume (the 1 GiB one):

oc -n student-01-0024-012 get vm win-pg-s01 -o json | jq -r '.spec.template.spec.volumes[] | "\(.name)  \(.persistentVolumeClaim.claimName // .dataVolume.name)"'
oc -n student-01-0024-012 get pvc -o custom-columns=NAME:.metadata.name,SIZE:.spec.resources.requests.storage,CLASS:.spec.storageClassName

Set the one value this step needs, from the list above:

# the data disk's current volume (the 1 GiB one on ODF): the disk you are moving
OLD='<the 1 GiB pvc from the list above>'

Create an empty 1Gi target volume on nfs-csi, the same size as the disk you move:

oc apply -f - <<EOF
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata: { name: win-pg-s01-data-nfs, namespace: student-01-0024-012 }
spec:
  source: { blank: {} }
  storage:
    storageClassName: nfs-csi
    accessModes: [ ReadWriteMany ]
    resources: { requests: { storage: 1Gi } }
EOF
oc -n student-01-0024-012 get dv win-pg-s01-data-nfs -w                # Succeeded (Ctrl-C)

Point the VM's data disk at the new volume, and tell it to migrate the data (not just swap the disk):

oc -n student-01-0024-012 get vm win-pg-s01 -o json | jq --arg old "$OLD" '
  .spec.updateVolumesStrategy = "Migration"
  | .spec.template.spec.volumes |= map(
      if ((.persistentVolumeClaim.claimName // .dataVolume.name) == $old)
      then {name: .name, dataVolume: {name: "win-pg-s01-data-nfs"}} else . end)
  | if .spec.dataVolumeTemplates then .spec.dataVolumeTemplates |= map(select(.metadata.name != $old)) else . end' \
  | oc replace -f -
oc -n student-01-0024-012 get vmim -w                                 # a migration runs and Succeeds (Ctrl-C)
oc -n student-01-0024-012 get vmi win-pg-s01 -o jsonpath='{range .status.volumeStatus[*]}{.name}  {.persistentVolumeClaimInfo.claimName}{"\n"}{end}'

✅ The data disk now reads win-pg-s01-data-nfs (nfs-csi), and the VM never restarted. In the console: VM → Configuration → Storage shows the data disk on nfs-csi.


1b.4 Inject, and read back

oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT * FROM ovedr_mark('student-01-0024-012', 'after-storage-migration', 'data disk on NFS');"
oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT id, ts, stage, payload FROM marks ORDER BY id;"
oc exec -n student-01-0024-012 deploy/sql-client -- sql -h 10.101.23.101 -c "SELECT * FROM ovedr_verify('student-01-0024-012');"

✅ After Demo 1's rows: before-live-migration, after-live-migration and after-storage-migration, in order, with chain_ok = t. PostgreSQL kept running through both moves, so nothing between them is missing.

Back to: Demo 1 · Next: Demo 2: warm migration with storage copy offload (🚧 coming soon: read only).

Thank you