kubevirt-vm-migration-platform

ADR 010: Guest network remediation by netplan driver match and DHCP, applied offline at conversion time

Context

The source guest’s netplan matches the VMware interface name ens192 and configures a static 192.168.50.31. On a virtio NIC the interface gets a different name, so the guest boots healthy but unreachable (Stage 1C risk, confirmed in Stage 1G for both conversion paths). Stage 1G proved on a disposable copy that matching by driver fixes the interface problem. ADR 008 now fixes the addressing: masquerade hands out 10.0.2.2 by DHCP, and .31 is not kept.

This deserves its own record because it is a policy for changing the guest during migration, with real alternatives, and not just a consequence of the binding.

Decision

  1. Replace the content of the guest’s only netplan file, /etc/netplan/50-cloud-init.yaml (root:root, 0600), with one ethernet primary that uses match: driver: virtio_net, dhcp4: true and dhcp6: false. There is no name match, MAC match, static address, route or nameserver. (The exact design text is in section 15 of the Stage 1H record.)
  2. Apply it offline (guestfish or virt-customize) on a new disposable copy of the virt-v2v output on conversion-host-01, together with the offline guest-agent install. Hash the result and make it read-only.
  3. Never apply it to the source VM, the golden artifact, the working copy or the kept Stage 1G images.
  4. “Offline” refers to the guest (clarified 2026-09-30). The guest never needs network access for this change or for the agent install. conversion-host-01 does need Ubuntu archive access to download the agent packages.
  5. The netplan replacement is an enumerated, intended guest change (added 2026-09-30, review R2). The others are:
    • the virt-v2v changes recorded in Stage 1G;
    • the qemu-guest-agent install with its exact dependency set;
    • removal of all five virt-v2v first-boot scripts and their service.

    They are recorded, with hashes, in a preparation record. The guest is otherwise unchanged. See the Stage 1H record, section 29.2. The file hash and package evidence are pending future image preparation.

  6. The prepared image must pass the pre-transfer boot test on conversion-host-01 before it is transferred (added 2026-09-30, review R5). The test boots it under QEMU/KVM with OVMF (Secure Boot off), virtio disk and NIC, user-mode DHCP and the guest-agent channel, through a disposable overlay. It checks that DHCP works, systemctl is-system-running, the agent, and the nginx page hash. See the Stage 1H record, section 29.5. Stage 1G proved only a static test address, so this gate is the first test of the DHCP design.

Alternatives

Alternative Why not
Keep static 192.168.50.31 Not deliverable on masquerade or in a VPC (ADR 008)
Match by the MAC and set macAddress: 00:0c:29:0f:3d:15 on the KubeVirt interface Works, but couples the guest to a VMware-OUI MAC for no benefit, and still needs DHCP
KubeVirt cloudInitNoCloud network data at first boot cloud-init is disabled in this guest (Stage 1F); enabling it is a bigger, riskier change than one netplan file
Fix it after boot through the console Manual, not reproducible, and leaves the first boot unreachable
Rely on virt-v2v virt-v2v 2.4.0 left netplan untouched (Stage 1G)

Consequences

Evidence

Stage 1G record, sections 13, 14, 17 and 20 (driver match proven; virt-v2v changes and first-boot scripts recorded); Stage 1H record, sections 14, 15 and 29; Stage 1H architecture review, section 9.

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 5 to 11 (Gate I1), 15 (Gate I2) and 18.5 (Gate I4).

Aspect Outcome
Design decision Replace netplan with a virtio_net driver match and DHCP, install qemu-guest-agent offline, and remove the virt-v2v first-boot mechanism. Apply all of this offline to a new disposable copy, then hash and protect it
Applied (Gate I1) A new standalone qcow2 was made from the kept virt-v2v output on conversion-host-01. The changes were netplan (new file hash 89ea1ea3...6a3b), qemu-guest-agent 1:8.2.2+ds-0ubuntu1.18 plus its one missing dependency liburing2, and removal of all 14 first-boot entries. An offline virt-diff showed nothing else changed; 41 of 41 checks passed. The image was set to mode 0444 and chattr +i, with sha256 2b30e195...93b2. The source, golden artifact and kept Stage 1G images were unchanged
Differences from the design guestfish was used instead of virt-customize, so that every guest write was explicit (record D2). The pre-transfer boot test’s first run on QEMU user-mode networking with restrict=on was blocked, because that network offers no DHCP router. The passing run used an isolated network namespace with a DHCP server that offers a router, approved by the user. The image itself was never modified
Validated Gate I2: 54 of 54 checks, including DHCP lease, default route, guest agent and the nginx page hash. Gate I4 on KubeVirt: the NIC came up as enp1s0 (driver virtio_net), was configured by the driver match, and received 10.0.2.2 by DHCP. 36 of 36 guest boot and network checks passed, AgentConnected=True, and machine-id, filesystem UUIDs, SSH host keys and nginx configuration were unchanged
Limitations Validated with a single virtio NIC only. The guest’s stock apt timers came with the image. In Gate I4 one catch-up run installed nothing, and Gate I5-A found 0 package changes before teardown
Destroyed / retained The migrated VM and its EBS disk were removed in Gate I5. The prepared image remains on conversion-host-01, outside Git, read-only and immutable