kubevirt-vm-migration-platform

ADR 009: EBS gp3 Block PVC through CDI upload; qcow2 in transit, raw at rest

Context

ADR 006 left the KubeVirt target disk format and the CDI import path open. Stage 1G produced a guest-adapted qcow2 (virt-v2v, 40 GiB virtual, 2.73 GiB file) from the golden VMDK. CDI v1.66.1 imports qcow2, VMDK, VDI, VHD, VHDX and raw, and converts all of them to raw [E14]. KubeVirt consumes a PVC either as a raw block device or as a raw disk.img file on a filesystem [E23]. The target is one EC2 node with EBS (ADR 007).

Decision

Item Decision
Storage backend AWS EBS CSI driver, gp3 (3,000 IOPS / 125 MiB/s baseline) [E40, E41]
StorageClass type: gp3, encrypted: "true" (added 2026-09-30, review R7; the driver default is false), volumeBindingMode: WaitForFirstConsumer, reclaimPolicy: Delete, allowVolumeExpansion: true
Encryption at rest VM disk and CDI scratch encrypted through the StorageClass parameter above; the EC2 root volume is encrypted at launch. The design does not depend on an account-level default (added 2026-09-30, review R7)
VM disk PVC volumeMode: Block, ReadWriteOnce, 40Gi (equal to the source’s virtual size, never smaller)
Scratch Same class, Filesystem, RWO (CDI always uses Filesystem RWO scratch, and upload always needs it) [E17]
Input format VMDK, read only, never converted in place
Transfer format qcow2: the guest-adapted virt-v2v output, remediated on a disposable copy (about 2.7 GiB to move instead of 40 GiB). It must be standalone, with no backing file; that evidence is pending future image preparation (clarified 2026-09-30)
Target representation raw on the Block PVC, written by CDI. “Raw” means the guest disk bytes sit directly on the EBS block device, with no image container and no file. Only the container changes; the guest’s partition table and filesystems are carried unchanged, and no guest filesystem is converted (clarified 2026-09-30)
Import CDI upload DataVolume via virtctl image-upload run on the node through kubectl port-forward, so the upload proxy is never exposed [E16, E49]
Lifecycle A standalone DataVolume (not a dataVolumeTemplate) that manages the PVC; the VM only references the PVC, so deleting the VM keeps the disk. The DataVolume is the migration disk’s lifecycle object: the disk is removed by deliberately deleting the DataVolume, then verifying that the PVC, the PV and the EBS volume are gone (clarified 2026-09-30)
Disk bus virtio
Runtime setting CRI-O device_ownership_from_security_context = true (required for CDI on block PVCs) [E18]

Why

Cold versus live migration

Live migration requires shared ReadWriteMany volumes [E22]. EBS gp3 is RWO and AZ-scoped, so this VM is not live-migratable. That is accepted: the first migration is a cold copy. RWX storage is deferred and the design is not shaped around live migration.

Consequences

Evidence

Stage 1H record, sections 16 to 18 and 29, sources E14 to E18, E22, E23, E40, E41, E49 to E51, E55, E56; Stage 1H architecture review, section 7.

Post-implementation validation

Added 2026-10-15. The decision and its rationale above are unchanged. All values are OBSERVED in the Stage 1I record, sections 16.9, 18.1, 18.2 and 19.

Aspect Outcome
Design decision qcow2 in transit; CDI upload into a standalone DataVolume; 40Gi Block RWO PVC on an encrypted gp3 StorageClass; raw at rest; teardown through the DataVolume, followed by detection-only orphan checks
Deployed and validated StorageClass ebs-gp3-encrypted as specified. CDI’s StorageProfile reported Block and ReadWriteOnce, and the CRI-O device_ownership_from_security_context setting was applied (drop-in). The DataVolume was filled by virtctl image-upload on the node through kubectl port-forward, with cdi-uploadproxy kept ClusterIP. CDI v1.66.1 used its volume-populator flow: a 40Gi Block prime PVC and a 43Gi Filesystem scratch PVC, then qemu-img convert -O raw onto the block device. The result was EBS vol-0d94fba4ddd3d84d2, 40 GiB gp3, encrypted. The scratch volume was deleted automatically. The VM disk was attached as a raw virtio block device (cache=none, io=native)
Decision deferred in the design, made at runtime Transfer route: SFTP straight from conversion-host-01 to the node, using a temporary key restricted to internal-sftp and removed afterwards. The SHA-256 was checked before and after on the source side and recorded twice on the node; the original post-transfer log was lost and is disclosed in section 18.1
Limitations DataVolume Succeeded proved only that CDI finished writing. The imported content was proven by guest-level identity and page-hash checks, not by a hash of the raw PVC. RWO and AZ-scoped storage meant KubeVirt reported the VM as not live-migratable (DisksNotLiveMigratable), as designed. No snapshots or backup
Destroyed Deleting the VM left the disk intact (I5-C). Deleting the DataVolume removed the PVC by owner reference, the PV by reclaimPolicy: Delete and the EBS volume through the CSI driver, and AWS returned InvalidVolume.NotFound (I5-D, I5-E). No volume was deleted by hand. After terraform destroy, the detection-only scan found no volume belonging to the session (I5-I)