| Field | Value |
|---|---|
| Project | Project 1.5: VM-to-Kubernetes Migration Platform |
| Stage | 1H |
| Date | 2026-09-28 (all times UTC; H1 checks at 04:22) |
| Approval | Explicitly approved by the user as design, feasibility and architecture only: research current KubeVirt, CDI, Kubernetes and AWS requirements; compare EKS with self-managed Kubernetes; design the network, storage, guest-remediation and guest-agent model; write ADRs and diagrams; make one evidence-based target-platform decision. Not approved: any AWS resource, EC2 instance, EKS or kubeadm cluster, Kubernetes/KubeVirt/CDI installation, Multus, NetworkAttachmentDefinition, StorageClass, PVC, DataVolume or VM; any change to legacy-source-vm, the Stage 1E golden artifact or the Stage 1G images; Stage 1I. |
| Result | Success (H1 to H10 PASS). Chosen target for the first KubeVirt migration: a single-node, self-managed kubeadm cluster on one EC2 instance with nested virtualization (m8i.xlarge, ap-south-1), running CentOS Stream 9 + CRI-O 1.36 + Kubernetes 1.36 + KubeVirt v1.9.0 + CDI v1.66.1 on x86_64 with KVM. The VM uses the pod network with masquerade binding (no Multus) and gets its address by DHCP; 192.168.50.31 does not survive and is replaced by a Kubernetes Service. Storage is EBS gp3 through the EBS CSI driver, Block volume mode, ReadWriteOnce; the disk travels as qcow2 and CDI stores it as raw on the PVC. qemu-guest-agent is installed offline at conversion time, not at first boot. Nothing was created, installed or migrated. |
| Related | ADR 007 (platform), ADR 008 (network), ADR 009 (storage and disk format), ADR 010 (guest network remediation), Stage 1G record, Stage 0 feasibility, Stage 1 index |
| Post-review corrections | 2026-09-30. The Stage 1H architecture review (verdict: REQUIRES ARCHITECTURAL CORRECTIONS) required eight documentation corrections, review R1 to R8. With the user’s approval they are applied to this record and to ADRs 008, 009 and 010 as documentation only. The details are in section 29. No AWS action, cluster, conversion, boot or other runtime experiment was performed for them. The 2026-09-28 findings, gates H1 to H10 and sources E1 to E49 are kept as recorded. Statements changed by the review are marked “(corrected 2026-09-30, review Rn)” or “(added 2026-09-30, review Rn)”. Review R1 to R8 are the review’s correction numbers and are unrelated to the risk IDs R1 to R13 in section 27. |
| Later outcome | Note added 2026-10-15. This record is kept as written. Statements such as “nothing was created”, “not started” and “BLOCKED” describe 2026-09-28 to 2026-09-30. On 2026-10-01, Stage 1I built this design, migrated legacy-source-vm to KubeVirt, validated it and destroyed the target. AWS credentials (U15) were working by then. Design-versus-build differences are in the “Post-implementation validation” sections of ADRs 007 to 010. |
Labels: OBSERVED = seen in this lab by a command we ran. DOCUMENTED = stated by an official source listed in Sources (retrieved 2026-09-28, or 2026-09-30 where stated). INFERRED = reasoned from observations or documentation, not tested. NOT TESTED = deliberately not done. Decision states: DECIDED, DEFERRED, UNKNOWN, BLOCKED. Section 29 uses the review’s four labels:
Source references such as [E5] point to the numbered table in Sources, which records URL, title, retrieval date, the exact requirement and its implication.
Diagrams: stage-1h-target-platform.svg, stage-1h-migration-data-path.svg, stage-1h-network-model.svg.
Answer fifteen design questions with evidence, so that a later, separately approved stage can build the target without guessing:
| # | Question | Answer | State |
|---|---|---|---|
| 1 | Kubernetes distribution / environment | Upstream Kubernetes bootstrapped with kubeadm, one node (control plane and worker together), on one EC2 instance | DECIDED |
| 2 | Kubernetes version | 1.36 (latest patch at build time; v1.36.5 was current on 2026-09-28 [E25]) | DECIDED |
| 3 | KubeVirt version | v1.9.0 [E1, E2] | DECIDED |
| 4 | CDI version | v1.66.1 [E13] | DECIDED (no official KubeVirt-to-CDI matrix exists; section 8) |
| 5 | Worker node host OS | CentOS Stream 9 x86_64 (EL9 kernel, aligned with the virt-launcher userland) [E6, E7, E31] | DECIDED |
| 6 | Node virtualization capability | EC2 nested virtualization (Intel VT-x passed through by Nitro) on m8i.xlarge; KVM (kvm_intel) in the node kernel [E32] |
DECIDED (to be proven on the node) |
| 7 | EKS vs self-managed vs local vs other | Self-managed on EC2. EKS deferred, local VMware-hosted rejected as the primary target, bare metal only a documented fallback (sections 10 to 12) | DECIDED |
| 8 | Storage model for the first cold migration | EBS CSI driver, gp3, volumeMode: Block, ReadWriteOnce, 40 GiB, one AZ; CDI upload DataVolume [E14, E40] |
DECIDED |
| 9 | Network model | Pod network with masquerade binding; flannel CNI; no Multus [E20] |
DECIDED |
| 10 | Can the VM retain 192.168.50.31? | No (section 14) | DECIDED |
| 11 | What replaces IP continuity? | Service continuity: a Kubernetes Service in front of the VM, plus workload-identity checks (page hash) instead of address identity | DECIDED |
| 12 | nginx validation after migration | The same 12 checks as Stages 1C to 1G, run inside the guest and from outside the cluster through the Service (section 21) | DECIDED |
| 13 | Guest network remediation | netplan: match the NIC by driver virtio_net, dhcp4: true; applied offline to a disposable converted copy (section 15, ADR 010) |
DECIDED |
| 14 | Guest-agent strategy | Pre-install qemu-guest-agent offline during conversion; remove the virt-v2v first-boot install; agent is useful but not a pass/fail requirement (section 19) |
DECIDED |
| 15 | What stays deferred | Live migration, RWX storage, Multus, bridge and passt bindings, IP preservation, DNS names, HA, EKS comparison, Secure Boot, production design (section 25) | DEFERRED |
In scope, all done as desk research and design:
Not done, by instruction: creating any AWS resource (VPC, EC2, EBS, IAM, EKS); installing Kubernetes, KubeVirt, CDI, Multus, a CNI or a CSI driver; creating NetworkAttachmentDefinitions, StorageClasses, PVCs, DataVolumes or VMs; running virt-host-validate; changing, shutting down or snapshotting legacy-source-vm; touching the golden artifact or the Stage 1G images; installing qemu-guest-agent anywhere; creating credentials or SSH keys; starting Stage 1I.
Also not done: no AWS CLI call was made (the CLI is not installed, and the AWS credential volume was not touched), and no Kubernetes API was contacted. Two pre-existing kubeconfig contexts on the workstation (ckad-lab, docker-desktop) belong to unrelated work and were only listed by name.
OBSERVED at H1 (04:22), unchanged since Stage 1C:
| Item | Value |
|---|---|
| VM | legacy-source-vm, Vmid 2 on esxi-8-lab, powered on, 0 snapshots |
| Guest | Ubuntu 24.04.5 LTS, kernel 6.8.0-142-generic (Stage 1G), UEFI, 2 vCPU / 4096 MB, 40 GiB disk (PVSCSI), one VMXNET3 NIC |
| Network | Static 192.168.50.31/24 on VMnet8 (a VMware Workstation NAT network on the laptop), gateway and DNS 192.168.50.2, MAC 00:0c:29:0f:3d:15 |
| Application | nginx on :80; HTTP 200, 267 bytes, page sha256 b9826e18a06a354d6ba97e3419e266b1453cb2a3b0038d4b3dcd45c96c170046 from Windows and from ESXi |
| Other VMs | conversion-host-01 (Vmid 3) on at .32; kvm-learning-01 (Vmid 1) off |
From the Stage 1G record, which this stage relies on and does not repeat:
-i disk -o local -of qcow2) produced a qcow2 (40 GiB virtual, 2.73 GiB file) that boots under QEMU/KVM with OVMF, Secure Boot off, through the ESP fallback loader, on virtio-blk (vda) and virtio-net.ens192. The virtio NIC was enp0s3, so the guest came up with no network.guestfs-firstboot scripts that run apt-get update and install qemu-guest-agent. On an isolated network this stalled boot completion (starting for over 4 minutes).virtio_net, static test address) produced a routable guest serving the source page hash.Stage 1H consequences: the KubeVirt design must supply EFI (Secure Boot off), a virtio disk bus and a virtio NIC; the netplan remediation has to fit the chosen network binding; and the guest-agent install must not depend on first-boot internet access.
| Fact | Evidence | State |
|---|---|---|
| Newest stable release | v1.9.0, published 2026-07-30 (a promotion of v1.9.0-rc.2) [E1]; the project’s stable.txt returns v1.9.0 [E2] |
DOCUMENTED |
| Newer releases | Only v1.10.0-alpha.0, marked pre-release [E1]. Not used. |
DOCUMENTED |
| Kubernetes range | “KubeVirt v1.9 is built for Kubernetes v1.36 and additionally supported for the previous two versions” [E3]; the support matrix lists 1.36, 1.35, 1.34 for v1.9 and no 1.37 [E4]. The user-supplied statement is confirmed. | DOCUMENTED |
| Upstream CI on release-1.9 | Presubmit lanes run on k8s-1.33 to k8s-1.36, including sig-compute, sig-network and sig-storage on k8s-1.36 [E8] | DOCUMENTED |
| CI node platform for 1.36 | kubevirtci k8s/1.36: Kubernetes 1.36.5, base centos9, CRI-O 1.36, flannel, kubeadm, systemd cgroup driver, container-selinux [E9] |
DOCUMENTED |
| virt-launcher userland | Built on CentOS Stream 9 by default: libvirt 11.10.0-12.el9, QEMU 10.1.0-20.el9 [E6] |
DOCUMENTED |
| Beta features | From v1.9 all Beta feature gates are on by default [E11]; v1.9.0 Beta gates include PasstBinding and Snapshot [E10] |
DOCUMENTED |
| cgroup v1 | Deprecated in v1.9.0, removal planned for the next release [E12] | DOCUMENTED |
| Architectures | Operator install supports x86_64 and Arm64 [E5] | DOCUMENTED |
| Fact | Evidence | Implication |
|---|---|---|
| Maintained upstream minors | 1.37, 1.36, 1.35 [E25] | 1.37 is out (1.37.0 on 2026-08-26) but not supported by KubeVirt v1.9 [E4] |
| Current patches | stable-1.36.txt = v1.36.5, stable.txt = v1.37.1 [E25] |
Pin the 1.36 patch at build time |
| End of life | 1.36: 2027-06-28; 1.35: 2027-02-28; 1.34: 2026-10-27 [E25] | 1.34 expires in a month: rejected. 1.36 has the longest window and is the version KubeVirt v1.9 is built for |
| kubeadm packages | Per-minor repositories on pkgs.k8s.io; generic Red Hat instructions (dnf, exclude= pinning) [E26] |
CentOS Stream 9 is a supported install path |
| Privileged API server | kubeadm writes --allow-privileged=true into the kube-apiserver manifest by default [E48] |
KubeVirt’s requirement [E5] is met by default and is inspectable |
| Single node | Remove the control-plane taint for a single-machine cluster [E27] | VMs can schedule on the only node |
| cgroups | cgroup v1 deprecated since Kubernetes 1.35; kubelet does not start on cgroup v1 by default; RHEL 9-like distributions use v2 [E28] | CentOS Stream 9 (cgroup v2) is correct for both Kubernetes and KubeVirt |
The tuple was chosen as one unit, not component by component: it is the platform KubeVirt’s own CI uses for its Kubernetes 1.36 lanes [E8, E9], moved onto an EC2 instance.
| Field | Value | Evidence |
|---|---|---|
| Kubernetes | 1.36 (v1.36.5 observed; pin at build) | [E3, E4, E25] |
| KubeVirt | v1.9.0 | [E1, E2] |
| CDI | v1.66.1 (2026-09-06) | [E13] |
| Node OS | CentOS Stream 9, x86_64, EL9 kernel (5.14 line) | [E6, E7, E30, E31] |
| Container runtime | CRI-O 1.36.x (v1.36.6 on 2026-09-21), systemd cgroup driver, device_ownership_from_security_context = true |
[E5, E9, E18, E29] |
| Pod network (CNI) | flannel (v0.28.9 current) | [E9, E47] |
| CPU architecture | x86_64 (Intel; every nested-virtualization family is Intel) | [E32] |
| Virtualization | EC2 nested virtualization, KVM as the L1 hypervisor | [E32, E34] |
| CSI | AWS EBS CSI driver (v1.66.0 current), gp3 | [E40, E41] |
CDI pairing evidence. No official KubeVirt-to-CDI compatibility matrix exists (also recorded in ADR 004). What does exist: CDI v1.66.0 added Kubernetes 1.36 CI lanes [E13]; KubeVirt’s CI deploys the latest CDI release (kubevirtci fetch-latest-cdi.sh, KUBEVIRT_DEPLOY_CDI=true) [E9]; KubeVirt v1.9.0 compiles against CDI API types v1.64.0, and DataVolume stays at cdi.kubevirt.io/v1beta1 [E19]. So v1.66.1 is the best-evidenced choice, and it stays a candidate until the Stage 0 runtime validation gate (feasibility section 3) passes on the real node.
What a worker node must provide, from [E5] unless noted, and how the chosen node meets it. virt-host-validate qemu is the planned check. It was not run in this stage.
| Requirement | Why | How the chosen node meets it | Planned proof (future stage) |
|---|---|---|---|
| CPU virtualization (VT-x) | KVM needs it | EC2 nested virtualization enabled at launch (CpuOptions NestedVirtualization=enabled) on a listed family [E32] |
grep -c vmx /proc/cpuinfo > 0 |
/dev/kvm |
QEMU acceleration | Appears only on bare metal or with nested virtualization on; missing means it was not enabled [E34] | ls -l /dev/kvm; virt-host-validate qemu |
/dev/vhost-net |
virtio-net data path | vhost_net module of the CentOS Stream 9 kernel |
virt-host-validate qemu |
/dev/net/tun |
Tap devices for VM NICs | tun module |
virt-host-validate qemu |
| Kernel modules | kvm, kvm_intel, vhost_net, tun, plus overlay, br_netfilter for Kubernetes |
Standard in the EL9 kernel; loaded explicitly and persisted in /etc/modules-load.d |
lsmod |
| Privileged workloads | virt-handler is a privileged DaemonSet | kubeadm sets --allow-privileged=true [E48]; the kubevirt namespace gets no restrictive Pod Security label |
virt-handler Running |
| Container runtime | containerd or CRI-O [E5] | CRI-O 1.36 (the runtime KubeVirt CI uses [E9]) | kubectl get nodes -o wide |
| Block device ownership | CDI writes block PVCs as non-root | CRI-O device_ownership_from_security_context = true [E18] |
CDI upload to a block PVC succeeds |
| Kernel/userspace alignment | virt-launcher ships EL userland; divergent (especially newer non-EL) kernels are “not recommended” [E5]; upstream builds and tests only EL9 kernels [E7] | CentOS Stream 9: the same distribution base and kernel line as the userland | uname -r; boot a VM |
| AppArmor | Host libvirtd profiles can block virt-handler [E5] | Not applicable: CentOS Stream 9 uses SELinux, and no host libvirt is installed | - |
| SELinux | Needs container-selinux >= 2.170.0 [E5] | Enforcing, with container-selinux from the distribution (as kubevirtci installs it [E9]) | rpm -q container-selinux; no AVC denials |
| cgroups | cgroup v2 required in practice [E12, E28] | CentOS Stream 9 default is cgroup v2 | stat -fc %T /sys/fs/cgroup = cgroup2fs |
| CPU architecture | x86_64 or Arm64 [E5] | x86_64 | uname -m |
| Emulation fallback | useEmulation: true exists for hosts without KVM [E5] |
Not used. If /dev/kvm is missing, the build stops instead |
- |
The question was not “does EC2 support KVM” but whether an EKS worker architecture meets every row of section 9. Findings:
| Aspect | Finding | State |
|---|---|---|
| Kubernetes version | EKS standard support offers 1.36, 1.35, 1.34; 1.36 on EKS since 2026-06-02 [E35] | Met |
| Privileged API server | The EKS control plane is managed, so the --allow-privileged flag cannot be inspected. EKS’s default Pod Security Admission level is privileged for all namespaces [E38] |
INFERRED met |
/dev/kvm on workers |
Needs the node’s launch template to set CpuOptions NestedVirtualization=enabled. The EKS list of settings prohibited in managed-node-group launch templates (subnet, IAM instance profile, shutdown/hibernate behaviour) does not include CPU options [E36]. AWS does not document EKS + nested virtualization + KubeVirt as a path. |
UNKNOWN until tested |
| Nested virtualization | Same instance families as plain EC2 [E32] | Available |
| Privileged DaemonSets | Allowed by the default PSA level [E38] | INFERRED met |
| Node OS / kernel | EKS-optimized AMIs are Amazon Linux, Bottlerocket, Ubuntu and Windows [E37]; none is Enterprise Linux. KubeVirt upstream builds on CentOS Stream 9 and validates only EL9 host kernels; non-EL hosts are outside upstream CI [E7] | Not aligned without a custom AMI |
| Kernel / module control | Possible on AL2023 and Ubuntu, restricted on Bottlerocket; a custom EL9 node AMI would be self-built and self-bootstrapped | Possible, at high effort |
| Networking | Amazon VPC CNI gives each pod a VPC address from an ENI [E39]. Masquerade works with any CNI (INFERRED), but VPC CNI is not a KubeVirt CI platform | Workable, untested upstream |
| Storage | EBS CSI driver available (as an add-on or self-installed) [E40] | Met |
| Multus | Not needed by the chosen network model (section 13) | N/A |
| KubeVirt / CDI install | Operator manifests; nothing EKS-specific documented | INFERRED workable |
| Cost | Adds USD 0.10 per cluster-hour in ap-south-1 [E44], about 42% on top of the single node (section 22) | Higher |
Conclusion. EKS is not rejected: it may well run KubeVirt on nested-virtualization nodes. But it meets the kernel-alignment requirement only through a custom EL9 node image, which removes most of the benefit of a managed node group. Its /dev/kvm path is undocumented, its CNI is outside KubeVirt’s CI, and it costs more. EKS is therefore DEFERRED to a later comparison stage.
| Aspect | Finding | State |
|---|---|---|
| Bootstrap | kubeadm 1.36 from pkgs.k8s.io, Red Hat instructions [E26]; single-node taint removal [E27] |
Met |
| Node OS | CentOS Stream 9 official AMI, x86_64, ap-south-1 ami-0e7930d02f47291cb on 2026-09-28, login user ec2-user [E31]; EOL 2027-05-31 [E30] |
Met (re-resolve the AMI at build) |
| Kernel control | Full: the kernel is EL9, matching virt-launcher [E6, E7]; kernel updates are controlled by us | Met |
| KVM availability | Nested virtualization at launch on m8i.xlarge [E32] |
Met on paper; prove at build |
| Privileged workloads | kubeadm default --allow-privileged=true [E48] |
Met |
| Runtime | CRI-O 1.36 from the isv:cri-o stable v1.36 repository [E29] |
Met |
| Networking | flannel, as in KubeVirt CI [E9]; pod network plus masquerade needs nothing else | Met |
| Storage | EBS CSI driver self-installed, IAM through an instance profile (no static keys) [E40] | Met |
| Observability | kubectl, virtctl, node journal, VMI status; no monitoring stack needed for the first migration | Sufficient |
| Teardown | Two ownership domains (corrected 2026-09-30, review R1). Terraform-managed AWS infrastructure (VPC, subnet, internet gateway, route table, security group, instance profile, the instance and its root volume) is destroyed through the Terraform state. EBS volumes created by the EBS CSI driver (the VM disk, the CDI scratch volume) are not in the Terraform state; they are deleted through Kubernetes before the destroy and checked for afterwards. Ordered sequence: section 29.1 | Simple, but ordered |
| Terraform suitability | All resources are plain EC2/VPC/IAM resources; nested virtualization is a launch CPU option | Suitable (ADR 002 requires Terraform) |
Conclusion. Self-managed kubeadm on EC2 meets every requirement in section 9 that can be settled on paper. It reproduces the exact platform KubeVirt tests on, and its only unproven element, /dev/kvm through nested virtualization, is shared with EKS.
Recorded in ADR 007.
Chosen target for the first KubeVirt migration: one EC2 instance, m8i.xlarge (4 vCPU, 16 GiB, Intel, nested virtualization enabled), in ap-south-1, single AZ, running CentOS Stream 9 with a single-node kubeadm Kubernetes 1.36 cluster, CRI-O 1.36, flannel, the EBS CSI driver, KubeVirt v1.9.0 and CDI v1.66.1. Built and destroyed with Terraform (ADR 002). Not a claim about production suitability.
| Alternative | Outcome | Reason |
|---|---|---|
| EKS with nested-virtualization nodes | DEFERRED | Section 10: kernel alignment needs a custom AMI; /dev/kvm path undocumented; +USD 0.10/h |
| Local VMware-hosted Kubernetes | Rejected as the primary target | See below |
Bare-metal EC2 (m7i.metal-24xl, USD 5.0904/h in ap-south-1 [E42]) |
Fallback only | About 23 times the chosen instance’s cost. Used only if nested virtualization fails, and only after a new decision |
m7i.xlarge (USD 0.2121/h) |
Alternate instance | Same shape and also listed [E32]; used if m8i is unavailable. 8th-generation Intel has had nested virtualization since the February 2026 launch; 7th generation since June [E33] |
| Multi-node cluster | DEFERRED | Nothing in a single cold migration needs a second node; live migration is deferred |
Why local VMware-hosted Kubernetes is not the primary target:
Analysis of every current binding [E20, E21, E22, E10]. “Static guest IP” means the guest could keep a self-configured address; “reachable” is a separate property.
| Property | Pod network + masquerade | Pod network + bridge | Multus secondary + bridge | Pod network + passt (core passtBinding) |
|---|---|---|---|---|
| Guest IP allocation | DHCP from virt-launcher: 10.0.2.2/24 by default, gateway 10.0.2.1 (from vmNetworkCIDR default 10.0.2.0/24) |
The pod IP is delegated to the guest by DHCP; the pod keeps no IP | From the NAD’s IPAM, or static in the guest if the NAD has no IPAM | DHCP from passt; the guest gets the pod’s address (INFERRED from the documented IP-sync behaviour) |
| NAT | Yes, nftables SNAT/DNAT in the pod, to and from the pod IP | No | No | Yes, userspace L2-to-L4 translation |
| External reachability | Through a Service to the pod IP, then DNAT to the guest | Pod IP (CNI-dependent) | Whatever the L2 network reaches | Through a Service |
| Static guest IP | No (DHCP expected) | No (must match the pod IP) | Possible | No |
| MAC control | macAddress settable (guest-side only) |
Settable, but some CNIs reject custom MACs | Passed to the CNI; bridge and OVS support it natively | - |
| Live migration | Allowed; reach the VM through a Service because the pod IP changes | Not allowed on the pod network | Depends on the network; not assumed | Supported; guest keeps its old IP until it renews |
| CNI dependency | Any CNI | CNI must tolerate the MAC move; Istio-type tools break | Needs a CNI plugin for the secondary L2 (bridge, OVS; not macvlan/ipvlan) | Any CNI |
| Multus dependency | None | None | Required, plus NetworkAttachmentDefinitions | None |
| AWS suitability | Good: VM traffic leaves as the pod/node address, which the VPC accepts | Workable, but the VM gets a pod IP anyway | Poor: a VPC only accepts addresses assigned to an ENI from the subnet range, and source/destination checks drop others [E46]. A guest-chosen address such as .31 is not deliverable | Good |
| Suits first cold migration | Yes | Weak (no benefit over masquerade, loses live migration) | No (needs Multus, NADs, an L2 design and ENI IP management) | Possible, but a Beta feature with 250 Mi extra memory per VM |
| Complexity | Low | Low to medium | High | Medium |
Decision (ADR 008): pod network + masquerade, one interface, model: virtio, with an explicit port list (80, 22). No Multus is installed because this model does not need it.
The two ports serve different purposes (clarified 2026-09-30, review R4):
virtctl ssh or virtctl port-forward) can reach the guest’s sshd inside the pod. No Service exposes it, and the AWS security group does not open it.See section 29.4.
What 10.0.2.0/24 is, and what it is not (clarified 2026-09-30):
Traffic paths (INFERRED from [E20, E21]; to be observed on the node):
virtctl console, virtctl vnc, and virtctl ssh or virtctl port-forward, all over the API. Guest management never uses the NodePort (section 29.4).192.168.50.31 cannot survive the migration, and the design does not try to keep it. Four independent reasons:
What replaces IP continuity:
| Continuity type | Mechanism |
|---|---|
| Service address | A Kubernetes Service (ClusterIP inside the cluster, NodePort 30080 for the validation client) survives VM restarts and pod IP changes [E20] |
| Client-facing name | DEFERRED: no DNS name in the first migration. The validation client uses node-public-IP:30080 |
| Workload identity | Proven by content, not by address: filesystem UUIDs, nginx package/config hashes, HTTP 200, page sha256 b9826e18...0046. The guest is unchanged except for the enumerated migration and preparation changes (corrected 2026-09-30, review R2; section 29.2) |
| Guest address | Changes from 192.168.50.31 (static) to 10.0.2.2 (DHCP, inside the pod). Recorded as an infrastructure identity change |
Recorded in ADR 010. Requirements: no dependency on VMXNET3 or the VMware interface name, suitable for VirtIO, persistent, and consistent with masquerade (DHCP).
DESIGN ONLY. Not applied to any disk in Stage 1H. Target content of /etc/netplan/50-cloud-init.yaml (owner root:root, mode 0600, the file the guest already uses):
# DESIGN (Stage 1H) - KubeVirt target network config for legacy-source-vm.
# Match by driver so the config does not depend on the interface name or MAC.
# Address, route and DNS come from the KubeVirt masquerade DHCP server.
network:
version: 2
ethernets:
primary:
match:
driver: virtio_net
dhcp4: true
dhcp6: false
| Property | How the design meets it |
|---|---|
| VMXNET3 independence | No vmxnet3 or ens192 reference; the match is the driver the target provides |
| VirtIO suitability | virtio_net is the driver of KubeVirt’s model: virtio NIC; Stage 1G proved driver matching works ([Match] Driver=virtio_net) |
| Persistence | A normal netplan file in the guest image, not a runtime tweak |
| No VMware naming | No name match, no set-name, no MAC match (the MAC changes unless set) |
| Chosen network model | DHCP, as masquerade expects [E20] |
How and where it is applied (a future, separately approved stage): offline with guestfish or virt-customize, on a new disposable copy of the virt-v2v output, on conversion-host-01. Never on the golden artifact, the working copy or the kept Stage 1G images. The prepared image must pass the pre-transfer boot test on conversion-host-01 before it leaves the lab (added 2026-09-30, review R5; section 29.5). Known limits: with more than one virtio NIC the match would configure all of them (fine for the single-NIC design). The inert ens192 in /etc/cloud/cloud.cfg.d/90-installer-network.cfg is left alone because cloud-init is disabled.
Recorded in ADR 009.
| Item | Decision | Evidence |
|---|---|---|
| Provisioner | AWS EBS CSI driver (ebs.csi.aws.com), installed by us on the self-managed cluster; IAM through the instance profile with AmazonEBSCSIDriverPolicyV2; IMDSv2 with hop limit 2 so the driver pods can reach instance metadata |
[E40] |
| Volume type | gp3: 3,000 IOPS and 125 MiB/s baseline included, 1 GiB to 64 TiB | [E41] |
| StorageClass (design) | type: gp3, encrypted: "true" (added 2026-09-30, review R7), volumeBindingMode: WaitForFirstConsumer, reclaimPolicy: Delete, allowVolumeExpansion: true |
INFERRED design; encrypted defaults to false in the driver [E50] |
| Encryption at rest | Every CSI-created volume (VM disk and CDI scratch) is encrypted through the StorageClass parameter, not through an assumed default (added 2026-09-30, review R7; section 29.7) | [E50] |
| Volume mode | Block for the VM disk: KubeVirt consumes the raw device directly, with no disk.img file or filesystem overhead [E23] |
[E23, E40] |
| Scratch space | CDI always requests scratch as Filesystem, ReadWriteOnce, and upload always needs scratch; the same gp3 class serves both modes | [E17] |
| Access mode | ReadWriteOnce. EBS is RWO and AZ-scoped; the VM is therefore not live-migratable, which is accepted for a cold migration | [E22], ADR 002 |
| Disk bus | virtio (proven in Stage 1G; KubeVirt’s standard bus) |
[E23] |
| Size | 40 GiB, exactly the source’s virtual size (42,949,672,960 bytes). Never smaller. Keeping it equal avoids CDI growing the virtual disk on import [E15] | Stage 1G |
| Import mechanism | CDI upload DataVolume (source: upload), filled by virtctl image-upload |
[E14, E16] |
| Lifecycle | A standalone DataVolume, not a dataVolumeTemplate. CDI creates the PVC for it and manages it through the DataVolume [E55]; the VM only references that PVC, so deleting the VM does not delete the migrated disk. Deleting the disk is therefore a deliberate teardown step: delete the DataVolume, then verify that the PVC, the PV and the EBS volume are gone. The EBS volume is outside the Terraform state (added 2026-09-30, review R1; clarified 2026-09-30; section 29.1) |
[E23, E55] |
| CRI setting | device_ownership_from_security_context = true in CRI-O, required for CDI on block PVCs |
[E18] |
Cold versus live migration. Live migration needs a shared ReadWriteMany volume and rules out pod-network bridge binding [E22]. The first migration is a cold copy of a powered-off disk into a new VM, so it needs neither shared storage nor live migration. The design is deliberately not shaped around live migration. RWX storage (EFS, or a clustered block solution) is DEFERRED.
| Stage of the path | Format | Why |
|---|---|---|
| Input (golden, read only) | VMDK: vmfs descriptor + 40 GiB flat extent | What cold acquisition produced (Stage 1E). Never converted in place |
| Conversion output / transfer format | qcow2 (virt-v2v -of qcow2, then remediation and agent install on a copy) |
Guest-aware conversion is required (netplan, VMware Tools, first-boot jobs). qcow2 carries only allocated data: about 2.7 GiB to move instead of 40 GiB. CDI accepts qcow2 for upload and import [E14] |
| Target representation | raw on a Block-mode PVC | CDI converts every supported format to raw [E14, E15]; KubeVirt reads raw block devices directly [E23] |
What “raw at rest” means here (clarified 2026-09-30):
The uploaded qcow2 must be standalone (added 2026-09-30):
qemu-img info --backing-chain must show exactly one image.Not chosen:
disk.img [E23].Design, in order. Nothing was run.
conversion-host-01):
qemu-img check, sha256 the result and make it read-only.conversion-host-01 as defined in section 29.5, and transfer nothing until it passes.kubectl port-forward -n cdi service/cdi-uploadproxy 8443:443 [E16], then virtctl image-upload dv legacy-source-vm-disk --size=40Gi --volume-mode=block --access-mode=ReadWriteOnce --storage-class=<gp3 class> --image-path=<qcow2> --uploadproxy-url=https://127.0.0.1:8443 --insecure --force-bind (flags from the v1.9.0 source [E49]; --force-bind avoids waiting for a consumer under WaitForFirstConsumer).qemu-img converts it to raw onto the Block PVC, and the scratch PVC is removed [E17]. Confirm that the scratch PVC is actually gone (added 2026-09-30, review R1). If a scratch volume is created, record its EBS volume ID from its PV while it exists (clarified 2026-09-30; section 29.1).Succeeded, PVC Bound, size 40Gi, volumeMode: Block. Record the EBS volume ID of the migrated disk from its PV (clarified 2026-09-30; section 29.1).
Succeeded proves that CDI finished writing. It does not prove that the right image was imported, or that the guest workload is correct (clarified 2026-09-30).VirtualMachine referencing the PVC with bus: virtio, EFI with secureBoot: false, 2 vCPU / 4096 Mi (from the .vmx, not the virt-v2v defaults), one masquerade interface, runStrategy: Manual so start and stop are explicit.
VirtualMachine -> references the standalone PVC -> the PVC is managed by the standalone DataVolume from step 4 -> the EBS volume behind its PV. The VM does not own the migrated disk’s lifecycle; the standalone DataVolume is the lifecycle object for the migration disk. CDI’s completed-DataVolume garbage-collection setting was removed in v1.62 [E56], so the DataVolume is expected to remain after Succeeded (ENGINEERING INFERENCE; to be observed). The disk is removed only by deliberately deleting the DataVolume in teardown, followed by verification that the PVC, the PV and the EBS volume are gone (section 29.1)./dev/kvm -> Ubuntu -> nginx.| Question | Answer |
|---|---|
| Required for boot? | No. KubeVirt calls the guest agent “an optional component” [E24]; Stage 1G booted without it |
| What it adds | The VMI condition AgentConnected, guest OS info and guest-reported interfaces in status, and the guestosinfo, userlist and filesystemlist subresources [E24] |
| Needed for the first migration? | No. It is an extra, non-blocking observation. The pass/fail checks (section 21) do not depend on it |
| Offline install without internet on first boot? | Yes, by design: install it before import |
| Install during conversion? | Yes: on a disposable copy of the virt-v2v output. Fetch qemu-guest-agent and any missing dependencies as .deb files on conversion-host-01 (the same Ubuntu 24.04 release) with apt-get download, upload them into the image, and run dpkg -i offline with virt-customize (guestfs-tools 1.52.0 is installed, ADR 006). Then delete the virt-v2v guestfs-firstboot scripts that run apt-get update/install and start the agent, so first boot never touches the network for packages. Refined 2026-09-30 (review R2): see the notes below this table |
| Install offline before import? | Yes, that is the same step: the image leaves the conversion host with the agent installed and no pending first-boot package work |
| How KubeVirt detects and uses it | Through the QEMU guest-agent virtio-serial channel (virt-v2v’s own XML also declares org.qemu.guest_agent.0); presence is reported as AgentConnected [E24]. That the channel is added automatically is INFERRED |
Rule kept: no agent installation depends on uncontrolled internet access at first boot, and nothing is installed into the source VM or the kept Stage 1G images.
Refinements to the install step (added 2026-09-30, review R2):
5000-0001-wait-online, 5000-0002-setenforce-0, 5000-0003-install-qga, 5000-0004-setenforce-restore, 5000-0005-start-qga. Removing only the apt scripts would leave the 30-second wait-online script.guestfs-firstboot service with its enablement links, and firstboot.sh. All of these were added by virt-v2v, and none exists in the source.conversion-host-01 does need access to the Ubuntu archive for apt-get download.End to end (see stage-1h-migration-data-path.svg and stage-1h-target-platform.svg):
ESXi legacy-source-vm -> cold acquisition (Stage 1E, done) -> golden VMDK on Windows (read only) -> working copy on conversion-host-01 (immutable) -> virt-v2v guest-aware conversion to qcow2 (Stage 1G, done) -> disposable copy with netplan remediation and offline guest agent (future) -> pre-transfer boot test on conversion-host-01 (future gate, section 29.5) -> transfer to the EC2 node -> CDI upload DataVolume -> raw Block PVC on EBS gp3 -> VirtualMachine -> virt-launcher pod -> QEMU/KVM on /dev/kvm (nested virtualization) -> Ubuntu 24.04 -> nginx.
Network path (see stage-1h-network-model.svg): guest virtio NIC (DHCP 10.0.2.2) -> masquerade binding in the virt-launcher pod (NAT here) -> pod network (flannel) -> node ENI (VPC private IP) -> public IPv4. The original VMware IP 192.168.50.31 stays on VMnet8 with the running source. IP continuity: none, by design. DNS: cluster DNS for the guest (INFERRED), no public name (DEFERRED). External HTTP validation point: node public IPv4, NodePort 30080, from the operator /32.
Migration semantics (added 2026-09-30, review R8):
The same 12 checks used since Stage 1C, adapted to KubeVirt. Access (corrected 2026-09-30, review R4):
virtctl console (serial), virtctl vnc, or virtctl ssh / virtctl port-forward to the guest’s port 22.curl from outside to the NodePort.There is no guest SSH Service: NodePort 30080 is HTTP validation only (section 29.4).
| # | Check | How on KubeVirt | Pass condition |
|---|---|---|---|
| 1 | Boot | VMI phase Running; console shows a login prompt |
Reaches multi-user target |
| 2 | Mounts | findmnt /, findmnt /boot/efi |
Mounted by UUID, as the baseline |
| 3 | OS identity | /etc/os-release, uname -r |
Ubuntu 24.04.5, kernel 6.8.0-142-generic |
| 4 | Filesystem UUIDs | blkid |
Equal to the Stage 1C baseline |
| 5 | Application files | sha256 of the page and nginx config | Equal to the baseline |
| 6 | nginx package | dpkg -s nginx |
Same version |
| 7 | nginx config | nginx -t, config hash |
Valid, unchanged |
| 8 | nginx service | systemctl is-active nginx |
active |
| 9 | HTTP 200 | curl guest-local, then through the Service |
200 |
| 10 | Page hash | sha256 of the response body | b9826e18...0046 |
| 11 | Application content | Page marker p15-stage1c-source-v1 |
Present |
| 12 | Network reachability | From outside: operator -> node-public-IP:30080; from the guest: gateway 10.0.2.1, DNS lookup | 200 from outside; gateway reachable |
Extra, non-blocking observations: AgentConnected, guestosinfo, systemctl is-system-running = running (no pending first boot).
Infrastructure identity (expected to change, recorded not failed): hypervisor and Vmid (ESXi Vmid 2 -> KubeVirt VMI UID), MAC (00:0c:29:0f:3d:15 -> KubeVirt-generated), disk (PVSCSI sda -> virtio vda, VMDK -> raw PVC), interface name (ens192 -> a virtio name), SMBIOS/firmware IDs and NVRAM, IP (192.168.50.31 -> 10.0.2.2 behind the pod IP).
Workload identity: unchanged except for the explicitly enumerated migration and preparation changes (corrected 2026-09-30, review R2).
The original 2026-09-28 wording, which listed the Ubuntu root filesystem content among the things that stay the same, contradicted the design’s own guest changes and is superseded.
Designed, not provisioned. Region ap-south-1 (Asia Pacific, Mumbai), on-demand Linux, public price files retrieved 2026-09-28 ([E42] published 2026-09-25, [E43] 2026-09-25, [E44] 2026-09-18, [E45] 2026-09-17).
| Resource | Design |
|---|---|
| Network | One VPC, one public subnet in one AZ, internet gateway, route table. No NAT gateway. Proposed ranges: VPC 10.40.0.0/16, subnet 10.40.1.0/24 (added 2026-09-30, review R3; address plan in section 29.3) |
| Security group | Inbound only from the operator’s /32: TCP 22 (SSH to the node’s own sshd, not to the guest) and TCP 30080 (HTTP validation NodePort). The API (6443) is reached through the SSH tunnel. Outbound: all. Guest port 22 is never opened here (clarified 2026-09-30, review R4) |
| Instance | 1 x m8i.xlarge (4 vCPU, 16 GiB), CpuOptions NestedVirtualization=enabled, CentOS Stream 9 AMI, IMDSv2 required with hop limit 2 |
| Root volume | 50 GiB gp3, encrypted (added 2026-09-30, review R7) (OS, container images, the uploaded qcow2). Terraform-managed |
| VM disk | 40 GiB gp3 (dynamic, from the EBS CSI driver), plus a temporary scratch volume of about 40 GiB during import. Both encrypted through the StorageClass (review R7). Both outside the Terraform state (review R1) |
| IAM | Instance profile with AmazonEBSCSIDriverPolicyV2 only [E40] |
| Public IPv4 | One (auto-assigned) |
| Lifecycle | Terraform apply for a session, destroy afterwards (ADR 002), in the teardown order of section 29.1, which deletes the CSI-created volumes first and checks for leftovers after the destroy (corrected 2026-09-30, review R1) |
Minimum footprint: 1 node, 4 vCPU, 16 GiB RAM, 90 GiB of gp3 steady state (plus about 40 GiB for the length of the import), nested virtualization (no extra charge [E32]), 1 public IPv4.
Cost, calculated only from the stated assumptions (730 hours per month; gp3 at list price with baseline performance; data transfer not priced, since it is expected to be negligible: inbound image and package traffic plus KB-sized HTTP responses):
| Item | Unit price | Per hour | 730 h |
|---|---|---|---|
m8i.xlarge |
USD 0.2227/h | 0.2227 | 162.57 |
| gp3 root 50 GiB | USD 0.0912/GB-month | 0.0062 | 4.56 |
| gp3 VM disk 40 GiB | USD 0.0912/GB-month | 0.0050 | 3.65 |
| Public IPv4 | USD 0.005/h | 0.0050 | 3.65 |
| Total, self-managed | about 0.239 | about 174.43 | |
| EKS control plane (if EKS were used) | USD 0.10/h | +0.1000 | +73.00 |
Bare-metal fallback m7i.metal-24xl instead of m8i.xlarge |
USD 5.0904/h | +4.8677 | - |
A 4-hour working session costs about USD 0.96, plus the scratch volume for under an hour (about USD 0.005). Left running for a month, it would cost about USD 174, which is why teardown is part of the design.
| Area | Design |
|---|---|
| Privileged KubeVirt workloads | virt-handler and parts of CDI run privileged; the whole node is therefore trusted infrastructure. No other workloads, no multi-tenancy |
| Node isolation | One disposable node in its own VPC; nothing else in the account depends on it |
| SSH | A new, dedicated key pair created at build time on the workstation, never committed. Security group limited to the operator /32. Password login off (AMI default) |
| AWS credentials | None in the repository, manifests or the node. Terraform runs with the operator’s own credentials (still BLOCKED, see section 26). The node uses its instance profile only. The Docker volume platform-aws-tools-aws is untouched |
| Instance metadata | IMDSv2 only; hop limit 2 is needed by the EBS CSI driver [E40], which also lets any pod read the instance role. Mitigation: the role holds only the EBS CSI policy, and the node runs only our workloads |
| RBAC | kubeadm admin kubeconfig kept on the workstation outside Git; no extra users. KubeVirt’s default roles unchanged |
| Network exposure | Only 22 (node sshd) and 30080 (HTTP validation) from one /32. The upload proxy is used through port-forward on the node and never exposed. Guest management goes through the Kubernetes API, never through a Service (clarified 2026-09-30, review R4) |
| Storage permissions | The EBS CSI policy is scoped to volumes tagged ebs.csi.aws.com/cluster: true [E40]. Corrected 2026-09-30 (review R1): the original text said that reclaimPolicy: Delete removes the volumes together with the cluster, which is inaccurate. reclaimPolicy: Delete acts only when a PVC is deleted while the CSI driver still runs. Terminating the instance leaves CSI-created volumes behind as billed, available volumes, outside the Terraform state. They are deleted through their DataVolume before the destroy and verified gone by their recorded volume IDs. After the destroy, that tag is used for detection only: an unrecognized volume is never deleted automatically, because the tag is not unique to this project (clarified 2026-09-30; section 29.1) |
| Encryption at rest | Root volume and all CSI-created volumes encrypted (added 2026-09-30, review R7; section 29.7) |
| Disk artifacts | Every copy of the guest disk (VMDK, qcow2, EBS volume) is sensitive data: it contains SSH host private keys, password hashes, machine identity, logs and application data. Handling rules in section 29.7 (added 2026-09-30, review R7) |
| Secrets | No Kubernetes Secrets with cloud credentials. Kubeconfig, SSH private key and Terraform state stay outside Git (.gitignore) |
| SELinux | Enforcing (as in KubeVirt CI [E9]). Any denial is recorded, and the stage stops rather than silently switching to permissive |
| Feature gates | v1.9 enables all Beta gates by default [E11]. Accepted for a disposable lab; disabling unused ones is DEFERRED hardening |
| Guest identity | The migrated guest keeps the source’s SSH host keys and machine-id. It never shares a network with the source |
| # | Decision | Value | State |
|---|---|---|---|
| 1 | Kubernetes environment | Self-managed, kubeadm, single node on EC2 | DECIDED (ADR 007) |
| 2 | Kubernetes version | 1.36 (patch pinned at build) | DECIDED |
| 3 | KubeVirt version | v1.9.0 | DECIDED |
| 4 | CDI version | v1.66.1 | DECIDED (candidate until the runtime gate passes) |
| 5 | Node OS | CentOS Stream 9 x86_64 | DECIDED |
| 6 | Container runtime | CRI-O 1.36 | DECIDED |
| 7 | EC2 instance strategy | m8i.xlarge, nested virtualization, on demand, single AZ; m7i.xlarge alternate; metal only after a new decision |
DECIDED |
| 8 | Storage model | EBS CSI, gp3, Block, RWO, 40 GiB, standalone upload DataVolume | DECIDED (ADR 009) |
| 9 | Target disk format | qcow2 in transit, raw on a Block PVC | DECIDED (ADR 009) |
| 10 | Network model | Pod network, masquerade, flannel, no Multus | DECIDED (ADR 008) |
| 11 | Guest network remediation | netplan driver match virtio_net, DHCP, offline on a disposable copy |
DECIDED (ADR 010) |
| 12 | Guest-agent approach | Offline pre-install at conversion, remove first-boot install, non-blocking | DECIDED |
| - | Pod network CNI | flannel | DECIDED |
| - | Region | ap-south-1 | DECIDED |
| - | SELinux mode | Enforcing | DECIDED |
| - | IP continuity | Not preserved; Service continuity instead | DECIDED |
| Item | Why deferred |
|---|---|
| Live migration and RWX storage | Not needed for a cold migration; EBS is RWO |
| Multus, secondary networks, bridge binding | Not needed by masquerade; poor fit for a VPC |
| passt binding | Beta; no benefit for the first migration |
| Any form of IP preservation | Rejected by design (section 14) |
| DNS name, Route 53, LoadBalancer, Ingress | NodePort from one /32 is enough for validation |
| EKS comparison | Section 10; a later comparison stage |
| Multi-node or HA cluster, multiple AZs | Nothing in the first migration needs them |
| Bare-metal instances | Only if nested virtualization fails |
| Secure Boot on | Not proven in Stage 1G |
| CentOS Stream 10 nodes | When KubeVirt’s default userland moves to CS10 |
| MAC preservation, cloud-init enablement, instancetypes/preferences | Not needed |
| Snapshots, backup, Beta-gate hardening, monitoring stack | After the first migration works |
| The route for moving the qcow2 to AWS | Stage 1I detail (section 18). Any route must meet the mandatory constraints of section 29.6 (added 2026-09-30, review R6). S3 staging is not part of this design |
| Terraform code, GitOps, the custom controller (ADR 005), warm migration (ADR 003) | Later stages |
| Production design | Out of scope. Not a claim about production suitability |
| # | Unknown | How it will be resolved |
|---|---|---|
| U1 | Whether m8i.xlarge reports nested-virtualization in ap-south-1. AWS says nested virtualization is in all commercial Regions [E33], and the family is sold there [E42], but no describe-instance-types call was made |
Pre-flight aws ec2 describe-instance-types before any launch |
| U2 | KubeVirt stability and performance on EC2 nested virtualization (L2 guests on Nitro) | First boot; AWS itself recommends bare metal for performance-sensitive work [E32] |
| U3 | The EKS nested-virtualization path | Only if EKS is revisited |
| U4 | container-selinux version on current CentOS Stream 9 (needs >= 2.170.0) | rpm -q on the node |
| U5 | Guest DNS through masquerade DHCP (resolver handed to the guest) | Observe in the guest |
| U6 | This guest’s EFI boot with KubeVirt’s OVMF build and fallback loader (proven only with Ubuntu’s OVMF) | First boot |
| U7 | The virtio NIC name inside the KubeVirt guest | Irrelevant with the driver match; observe |
| U8 | The offline dependency set of qemu-guest-agent for the guest’s package state |
apt-get download plus a dry run on the conversion host |
| U9 | Whether virt-v2v’s first-boot scripts skip the agent install when it is already present | Inspect the scripts on the copy; remove them regardless. 2026-09-30: moot for the design. The Stage 1G record shows all five scripts serve the agent install, and the design now removes all five plus the service (section 29.2) |
| U10 | CDI upload under WaitForFirstConsumer on a single node with --force-bind |
First upload |
| U11 | EBS CSI metadata access through flannel with hop limit 2 | Driver logs |
| U12 | Formal KubeVirt-to-CDI compatibility | Not published; the runtime gate decides |
| U13 | Data-transfer charges | Not priced; expected to be negligible |
| U14 | The CentOS Stream 9 AMI ID at build time | Re-resolve from centos.org |
| U15 | AWS credentials (ADR 002 blocker) | BLOCKED for any build; not needed for this design stage |
| # | Risk | Mitigation |
|---|---|---|
| R1 | EC2 nested virtualization is new (February 2026) and slower than metal | Functional goals only; metal fallback costed; stop if /dev/kvm is missing (no emulation) |
| R2 | CentOS Stream 9 reaches EOL on 2027-05-31 | Disposable lab; plan the CS10 move with KubeVirt |
| R3 | A kernel update on the node could diverge from the virt-launcher userland | Record uname -r; do not update the kernel mid-session |
| R4 | Single node is a single point of failure | Disposable; the source of truth is the conversion artifact plus Git |
| R5 | Pods can read the instance role through IMDS (hop limit 2) | Minimal IAM policy; no other workloads |
| R6 | Public exposure of SSH and the NodePort | Operator /32 only; teardown after the session |
| R7 | The guest boots unreachable if DHCP or the netplan design fails | virtctl console/vnc for diagnosis; the design was proven in principle in Stage 1G |
| R8 | CDI scratch space temporarily doubles storage | Budgeted (section 22) |
| R9 | Forgotten teardown leaks about USD 174 per month | Session checklist; Terraform destroy. Corrected 2026-09-30 (review R1): also the post-destroy check for CSI-created volumes (section 29.1), because the destroy alone does not remove them |
| R10 | SELinux enforcing blocks a component | Record AVC, stop, decide explicitly |
| R11 | All Beta feature gates on by default | Accepted for a lab; hardening deferred |
| R12 | Duplicate guest identity (machine-id, host keys, hostname) | The source and target never share a network |
| R13 | EBS is AZ-scoped; no live migration | Accepted: cold migration only |
Stage 1I is not defined or started here. Status at the end of Stage 1H:
| Criterion | Status |
|---|---|
| Target environment chosen | Met: section 12, ADR 007 |
| Compatible tuple | Met on paper: section 8 (candidate until the runtime gate) |
| Worker requirements defined | Met: section 9 |
| Node OS chosen | Met: CentOS Stream 9 |
| Hardware virtualization path defined | Met: EC2 nested virtualization, m8i.xlarge (U1 open) |
| Network model chosen | Met: masquerade, ADR 008 |
| Guest remediation defined | Met: section 15, ADR 010 |
| IP continuity decision | Met: not preserved; Service continuity (section 14) |
| Storage class/model defined | Met: EBS CSI gp3 Block RWO, ADR 009 |
| Target format | Met: qcow2 transfer, raw on PVC |
| CDI workflow | Met: section 18 |
| Guest-agent strategy | Met: section 19 |
| AWS footprint defined without provisioning | Met: section 22 |
| Source healthy | Met: H1 and final check |
| Golden protected | Met: hashes, size, timestamps and ReadOnly attribute unchanged |
| No Kubernetes/KubeVirt/CDI resources exist | Met: nothing created |
| No migration occurred | Met |
Stage 1I also requires the user’s explicit approval, with objective, scope and change boundary written down, and working AWS credentials (U15).
Added 2026-09-30. The Stage 1H architecture review found that the “Met” statuses above were not sufficient on their own, and required corrections review R1 to R8. They are now documented in section 29:
| Criterion (review) | Status |
|---|---|
| Teardown and EBS lifecycle defined (R1) | Defined: section 29.1 (not executed) |
| Guest identity and intended changes defined (R2) | Defined: section 29.2; preparation evidence pending future image preparation |
| Address plan defined (R3) | Defined as proposed design: section 29.3; nothing runtime-confirmed |
| Guest management path defined (R4) | Defined: section 29.4 |
| Pre-transfer boot test gate defined (R5) | Defined: section 29.5 (not executed) |
| Transfer constraints defined (R6) | Defined: section 29.6; route still deferred |
| Encryption and artifact handling defined (R7) | Defined: section 29.7 |
| Point-in-time, no-cutover semantics stated (R8) | Stated: section 29.8 |
Stage 1I remains not started. Whether the corrected design is ready for approval is a human decision. Documenting these corrections does not approve Stage 1I.
Note added 2026-10-15: Stage 1I was later approved gate by gate and completed on 2026-10-01 (I1 to I5 PASS). Section 29.1 (teardown) and section 29.5 (pre-transfer boot test) were executed there. See the Stage 1I record.
Added 2026-09-30 from the Stage 1H architecture review, section 15, with the user’s approval.
The original record said that Terraform destroys the instance “and its volumes”, and that reclaimPolicy: Delete removes the volumes with the cluster. That conflated two different ownership domains.
| Domain | Resources | Created by | Removed by | Basis |
|---|---|---|---|---|
| Terraform-managed AWS infrastructure | VPC, subnet, internet gateway, route table, security group, IAM role and instance profile, the EC2 instance and its root volume | terraform apply (ADR 002) |
terraform destroy, which only knows what is in its state |
PROPOSED DESIGN (ADR 002) |
| Kubernetes / EBS CSI-provisioned storage | The 40 GiB VM disk volume; the roughly 40 GiB CDI scratch volume (normally removed by CDI after the import) | The EBS CSI driver, when a PVC is bound. It tags the volume ebs.csi.aws.com/cluster = true; the IAM policy only lets it manage volumes with that tag (or with kubernetes.io/created-for/pvc/name) [E40, E51] |
Deleting the standalone DataVolume, which removes its PVC, while the driver still runs (reclaimPolicy: Delete); otherwise only an explicit deletion of a volume positively identified as this session’s |
CURRENT UPSTREAM DOCUMENTATION [E51, E55]; ENGINEERING INFERENCE |
Why this matters (ENGINEERING INFERENCE):
available.Session volume record (PROPOSED DESIGN; clarified 2026-09-30). During the migration session, record the exact EBS volume IDs created for:
No volume ID exists yet, and none is invented here.
Disk lifecycle (clarified 2026-09-30). The VirtualMachine references the standalone PVC, and the PVC is managed by the standalone DataVolume. The VM does not own the migrated disk’s lifecycle: the DataVolume is the lifecycle object. The normal workflow is to delete the DataVolume deliberately, then verify that the PVC and PV are gone, then verify that the underlying EBS volume is deleted. Deleting the PVC directly is not part of the normal workflow (step 3).
Teardown sequence (PROPOSED DESIGN; not executed; performed by the operator, and needs the AWS credentials that are still BLOCKED, U15):
Never capture disk content.
VirtualMachine and the validation Service. This does not delete the migrated disk, because the VM does not own it.reclaimPolicy: Delete the EBS CSI driver then deletes the PV and the EBS volume (ENGINEERING INFERENCE from [E55] and the StorageClass design; verified in step 4).Do not run the Terraform destroy until all of these hold.
ebs.csi.aws.com/cluster = true or kubernetes.io/created-for/pvc/name.This step deletes nothing. Expected result: no volume belonging to this session.
conversion-host-01).The goal is unchanged: no orphaned storage and no storage deliberately left billed. The post-destroy verification remains mandatory; it detects, and deletion is limited to volumes positively identified as this session’s.
Definition: the migrated guest is unchanged except for the explicitly enumerated migration and preparation changes below. Anything else that differs is a finding.
| # | Intended change | Introduced by | Evidence status |
|---|---|---|---|
| C1 | open-vm-tools purged: binaries, libraries, plugins, /etc/vmware-tools/, /etc/pam.d/vmtoolsd, units, rc links, udev rules 60-open-vm-tools.rules and 99-vmware-scsi-udev.rules |
virt-v2v | PROJECT-DERIVED FACT (Stage 1G section 14, virt-diff) |
| C2 | /etc/initramfs-tools/modules gains the virt-v2v comment and bochs; the initramfs for 6.8.0-142-generic is rebuilt; the old one is kept as /boot/initrd.img-6.8.0-142-generic.pre-v2v |
virt-v2v | PROJECT-DERIVED FACT (Stage 1G section 14) |
| C3 | New /etc/modprobe.d/virt-v2v-added.conf: alias scsi_hostadapter virtio_blk |
virt-v2v | PROJECT-DERIVED FACT (Stage 1G section 14) |
| C4 | /var/lib/dpkg/status rewritten by the purge. Incidental: /etc/ld.so.cache, /run/blkid, /run/needrestart |
virt-v2v | PROJECT-DERIVED FACT (Stage 1G section 14) |
| C5 | The first-boot mechanism that virt-v2v added is removed again: /usr/lib/systemd/system/guestfs-firstboot.service with its SysV links, /usr/lib/virt-sysprep/firstboot.sh, and its five scripts 5000-0001-wait-online, 5000-0002-setenforce-0, 5000-0003-install-qga, 5000-0004-setenforce-restore, 5000-0005-start-qga. Net effect after preparation: none of these present |
virt-v2v adds; preparation removes | Addition: PROJECT-DERIVED FACT (Stage 1G section 14). Removal: PROPOSED DESIGN; the exact removed-file list is pending future image preparation |
| C6 | /etc/netplan/50-cloud-init.yaml replaced with the section 15 content, owner root:root, mode 0600 |
Preparation (ADR 010) | Content: PROPOSED DESIGN (section 15). File hash: pending future image preparation |
| C7 | qemu-guest-agent and its exact dependency set installed: the files owned by those packages (including their systemd and udev units), plus the dpkg database and dpkg log entries the install causes |
Preparation (section 19) | Package names, versions and hashes: pending future image preparation (U8). None are invented here |
Explicitly not intended to change (PROJECT-DERIVED FACT: checksum-identical after virt-v2v in Stage 1G section 14):
/etc/fstab;/etc/machine-id;/var/www/html/index.html;Runtime state written after the guest boots (logs, journal, timestamps) is not an identity change (ENGINEERING INFERENCE).
Preparation record (PROPOSED DESIGN; written during future image preparation; no hash below exists yet, and none is invented):
94bc1cd6...91c6 (PROJECT-DERIVED FACT, Stage 1G) before the copy.qemu-img check result;qemu-img info --backing-chain showing exactly one image (standalone, section 17)..deb;virt-diff, as in Stage 1G) of the virt-v2v output against the prepared image, showing only C5 to C7.The AWS subnet is intentionally contained within the VPC: 10.40.1.0/24 lies inside 10.40.0.0/16. That containment is required, not an overlap to avoid (clarified 2026-09-30).
The independent address domains must not overlap:
The masquerade CIDR is a per-VM, guest-side network inside each virt-launcher pod, not a shared subnet (section 13).
Status meanings:
| Network | Value | Status | Basis |
|---|---|---|---|
| AWS VPC CIDR | 10.40.0.0/16 | Proposed; selected for the lab | PROPOSED DESIGN, chosen to stay clear of every independent domain below (the subnet is carved from it) |
| AWS subnet CIDR (one public subnet, one AZ) | 10.40.1.0/24 | Proposed; selected for the lab | PROPOSED DESIGN |
| Kubernetes Pod CIDR | 10.244.0.0/16 | Default (flannel v0.28.9 net-conf.json, VXLAN backend); selected for the lab. kubeadm must be given the same value |
CURRENT UPSTREAM DOCUMENTATION [E52] |
| Kubernetes Service CIDR | 10.96.0.0/12 (10.96.0.0 to 10.111.255.255); cluster DNS 10.96.0.10 | Default (kubeadm); selected for the lab | CURRENT UPSTREAM DOCUMENTATION [E53] |
KubeVirt masquerade vmNetworkCIDR |
10.0.2.0/24: gateway 10.0.2.1, guest 10.0.2.2, inside each VM’s virt-launcher pod (section 13) | Default; selected for the lab; unchanged from the 2026-09-28 design | CURRENT UPSTREAM DOCUMENTATION [E21] |
| Kept clear of | CRI-O’s default bridge network 10.85.0.0/16 [E54]; the lab network 192.168.50.0/24, which is not routed to AWS and is avoided so records stay unambiguous | Constraint | CURRENT UPSTREAM DOCUMENTATION [E54]; PROJECT-DERIVED FACT |
Overlap check (ENGINEERING INFERENCE, by arithmetic):
Why it matters (ENGINEERING INFERENCE):
Future pre-flight checklist. CNI prerequisites and the pod and Service CIDR selections belong in the future implementation pre-flight checklist, which is not written in this task. That checklist includes:
br_netfilter loaded and IPv4 forwarding enabled;The original access line in section 21 offered guest SSH via the Service as an alternative to virtctl ssh. That was inaccurate: the design has one Service, and it exposes HTTP on NodePort 30080 only.
| Path | Purpose | Transport | Opened in the AWS security group? |
|---|---|---|---|
| Node SSH, TCP 22 on the node’s public IPv4 | Node administration, the API tunnel, the transfer target (section 29.6) | SSH to the node’s own sshd | Yes, from the operator /32 only |
| Kubernetes API, TCP 6443 | kubectl, virtctl |
Through the node SSH tunnel | No |
virtctl console |
Serial console of the guest | Kubernetes API, then KubeVirt to the virt-launcher pod | No |
virtctl vnc |
Graphical console, where applicable | Kubernetes API | No |
virtctl ssh or virtctl port-forward to guest port 22 |
Guest shell over SSH | Kubernetes API forwarding into the virt-launcher pod, then to the guest’s port 22 | No |
| NodePort 30080 | External HTTP validation only | Node public IPv4, then Service, then pod port 80, then masquerade DNAT to guest port 80 | Yes, from the operator /32 only |
ports list so that the API-path forwarding can reach the guest’s sshd. That port is inside KubeVirt’s guest networking model: it is not exposed by the AWS security group and has no Service. That the forwarding needs the port to be declared is ENGINEERING INFERENCE from the documented masquerade behaviour [E20], to be observed at runtime.This test proves the exact prepared artifact before paid-cloud transfer. It is a mandatory gate between image preparation and transfer (section 18, step 2). It is documented only and was not performed.
| Element | Requirement |
|---|---|
| Where | conversion-host-01 (ADR 006) |
| What | The exact prepared qcow2 whose sha256 is in the preparation record (section 29.2). It is opened read-only as the backing file of a disposable overlay, as in Stage 1G section 9, so its bytes cannot change. Its sha256 is re-verified after the test and must still equal the record. Any test-harness instrumentation (such as Stage 1G’s serial autologin) lives only in the overlay, never in the prepared image |
| Hypervisor | QEMU/KVM, as in Stage 1G |
| Firmware | UEFI with OVMF, Secure Boot off, a fresh copy of the variable-store template for this boot, as in Stage 1G section 7 |
| Disk | virtio-blk |
| Network | virtio-net on QEMU user-mode networking, isolated (restrict=on, as in Stage 1G), using its built-in DHCP server; host port forwards for HTTP (and SSH, if used) |
| Guest agent | A virtio-serial port named org.qemu.guest_agent.0, the channel name KubeVirt uses |
Pass conditions (all required):
virtio_net obtains a DHCP lease from the user-mode network and has a default route. The address itself is QEMU’s, not the KubeVirt 10.0.2.2, and is not a pass condition.systemctl is-system-running reports running: no pending first-boot job and no failed unit.b9826e18...0046.Not proven by this test:
On failure:
Cleanup:
The route remains DEFERRED to Stage 1I: directly from conversion-host-01, or through Windows. S3 is not part of this design. Whatever the route, the transfer must satisfy all of the following (PROPOSED DESIGN):
scp or rsync over SSH). There is no public upload endpoint and no exposed CDI upload proxy.conversion-host-01), where any copy of the private key lives is recorded, and that copy is removed at the end of the session.conversion-host-01). The transfer never moves or deletes it.Succeeded), the qcow2 is deleted from the node, unless it is explicitly retained as evidence (for example, until the guest checks pass). A retained copy is deleted in teardown step 8 (section 29.1) and never outlives the session.Encryption at rest (PROPOSED DESIGN):
encrypted: "true".
false [E50], so the design never relies on the default.kmsKeyId unset, AWS uses the Region’s default EBS KMS key [E50].Disk artifacts are sensitive data (ENGINEERING INFERENCE from the guest’s contents; PROJECT-DERIVED FACT that host keys and machine-id are preserved, Stage 1G). Every VMDK, qcow2 and EBS copy of this guest may contain:
/etc/shadow password hashes;/etc/machine-id, hostname);Handling requirements:
PROJECT-DERIVED FACT (Stage 1E, Stage 1H H1):
Consequences (ENGINEERING INFERENCE):
Succeeded is not proof of a correct guest. It is not by itself proof that the guest workload image is correct; artifact-level and guest-level validation are still required (section 18, step 6).| Gate | Evidence | Result |
|---|---|---|
| H1 Repository and lab state | main = origin/main = a7aa1f54e7b033f9619a46057063c8a3121796c8 (Stage 1G), clean tree; source HTTP 200, 267 bytes, page sha256 b9826e18...0046 from Windows and ESXi; Vmid 1 off, Vmid 2 and 3 on, 0 snapshots; golden descriptor and flat hashes, sizes, timestamps and ReadOnly attribute unchanged |
PASS |
| H2 Official compatibility | Sections 6 to 8: KubeVirt v1.9.0, Kubernetes 1.34 to 1.36, CDI v1.66.1, runtimes, architecture; one tuple from KubeVirt CI | PASS |
| H3 Host requirements | Section 9, with virt-host-validate qemu as the planned (not executed) check |
PASS |
| H4 EKS vs self-managed | Sections 10 to 12, with the AWS nested-virtualization list read from AWS docs [E32] and EKS evaluated on its own evidence | PASS |
| H5 Platform decision | ADR 007 | PASS |
| H6 Network analysis | Section 13 (four bindings), section 14 (.31 does not survive), ADR 008 | PASS |
| H7 Guest netplan design | Section 15 (DESIGN only), ADR 010 | PASS |
| H8 Storage and format | Sections 16 and 17, ADR 009 | PASS |
| H9 Guest agent | Section 19 | PASS |
| H10 End-to-end design and documentation | Section 20, three diagrams, this record, index updates, validation, commit | PASS |
E1 to E49 retrieved on 2026-09-28. E50 to E56 retrieved on 2026-09-30 for the post-review corrections (section 29). Local evidence (outside Git): C:\VMs\conversion-host-01\stage-1h\H1.txt and h1.ps1.
| # | Source (URL) | Title | Exact requirement or fact | Implication |
|---|---|---|---|---|
| E1 | https://github.com/kubevirt/kubevirt/releases | Releases - kubevirt/kubevirt | v1.9.0 published 2026-07-30, promotion of v1.9.0-rc.2; newer only v1.10.0-alpha.0 (pre-release) | Target v1.9.0 |
| E2 | https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt | KubeVirt stable release pointer | Returns v1.9.0 |
Confirms the stable release |
| E3 | https://kubevirt.io/user-guide/release_notes/ | Release notes - KubeVirt user guide | “KubeVirt v1.9 is built for Kubernetes v1.36 and additionally supported for the previous two versions.” | Kubernetes 1.34 to 1.36 |
| E4 | https://github.com/kubevirt/sig-release/blob/main/releases/k8s-support-matrix.md | KubeVirt to Kubernetes version support matrix | 1.9: 1.36, 1.35, 1.34; not 1.37 | 1.37 excluded |
| E5 | https://kubevirt.io/user-guide/cluster_admin/installation/ | Installation - KubeVirt user guide | Latest three Kubernetes releases at KubeVirt release time; apiserver --allow-privileged=true; runtimes containerd, crio; AppArmor caveats; virt-host-validate qemu checks /dev/kvm, /dev/vhost-net, /dev/net/tun; EL-based virt-launcher userland, divergent non-EL kernels not recommended; SELinux needs container-selinux; useEmulation fallback |
Host checklist (section 9) |
| E6 | https://github.com/kubevirt/kubevirt/blob/v1.9.0/hack/rpm-deps.sh | kubevirt v1.9.0 hack/rpm-deps.sh |
Default CentOS Stream 9; libvirt 0:11.10.0-12.el9, QEMU 17:10.1.0-20.el9 |
EL9 host kernel is the aligned choice |
| E7 | https://github.com/kubevirt/kubevirt.github.io/blob/main/_posts/2026-09-03-understanding-kubevirt-host-kernel-userspace-dependencies.md | Understanding KubeVirt’s Host Kernel and Userspace Dependencies (kubevirt.io blog) | Release images built on CentOS Stream 9; upstream CI runs on EL9 host kernels (5.14); non-EL hosts are not covered by upstream CI (issue #16386) | CentOS Stream 9 nodes; EKS AMIs misaligned |
| E8 | https://github.com/kubevirt/project-infra/blob/main/github/ci/prow-deploy/files/jobs/kubevirt/kubevirt/kubevirt-presubmits-1.9.yaml | KubeVirt release-1.9 presubmit jobs | Lanes on k8s-1.33 to 1.36, incl. sig-compute, sig-network, sig-storage on 1.36 | Kubernetes 1.36 is CI-proven for v1.9 |
| E9 | https://github.com/kubevirt/kubevirtci/tree/main/cluster-provision/k8s/1.36 | kubevirtci cluster-provision/k8s/1.36 |
version 1.36.5; base centos9; CRI-O 1.36; flannel; kubeadm; cgroupDriver: systemd; container-selinux; fetch-latest-cdi.sh |
The tuple mirrors KubeVirt CI |
| E10 | https://github.com/kubevirt/kubevirt/releases/download/v1.9.0/feature-gates.json | v1.9.0 feature-gates.json | PasstBinding Beta, Snapshot Beta; no BlockVolume gate listed |
passt is Beta; block volumes not gated |
| E11 | https://github.com/kubevirt/kubevirt.github.io/blob/main/_posts/2026-07-10-Beta-Features-On-By-Default-In-v1-9.md | Beta Features Enabled by Default in KubeVirt v1.9 | All Beta feature gates enabled by default from v1.9 | Security note (section 23) |
| E12 | https://github.com/kubevirt/kubevirt/releases/tag/v1.9.0 | KubeVirt v1.9.0 release notes | cgroup v1 deprecated, removal planned next release | cgroup v2 node |
| E13 | https://github.com/kubevirt/containerized-data-importer/releases | Releases - containerized-data-importer | v1.66.1 2026-09-06 (latest); v1.66.0 2026-08-05 “Update kubevirtci, 1.36 lanes” | CDI v1.66.1 |
| E14 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/doc/supported_operations.md | CDI supported operations | Formats qcow2, VMDK, VDI, VHD, VHDX, raw (xz/gz) all converted to raw; sources incl. upload | qcow2 upload, raw target |
| E15 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/README.md | CDI README | kubevirt content type: convert qcow2 to raw, resize to all available space; NFSv3 not supported |
Size PVC = virtual size |
| E16 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/doc/upload.md | CDI upload | cdi-uploadproxy must be reachable; kubectl port-forward -n cdi service/cdi-uploadproxy 8443:443; virtctl image-upload |
Upload on the node via port-forward |
| E17 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/doc/scratch-space.md | CDI scratch space | Upload needs scratch; scratch always Filesystem, RWO | Class must serve Filesystem too |
| E18 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/doc/block_cri_ownership_config.md | device_ownership_from_security_context CRI configurable | Required for block PVCs; CRI-O [crio.runtime] device_ownership_from_security_context = true |
CRI-O setting |
| E19 | https://github.com/kubevirt/kubevirt/blob/v1.9.0/go.mod | kubevirt v1.9.0 go.mod | kubevirt.io/containerized-data-importer-api v1.64.0 |
DataVolume API compatibility evidence |
| E20 | https://kubevirt.io/user-guide/network/interfaces_and_networks/ | Interfaces and Networks - KubeVirt user guide | Bindings bridge, sriov, masquerade (nftables NAT), passtBinding; masquerade only on the pod network; guest should use DHCP; expose via a Service to survive restarts; bridge on pod network: no live migration; Multus needed for secondary networks; macvlan/ipvlan invalid for bridge; passt +250Mi, feature gate | Section 13 |
| E21 | https://github.com/kubevirt/kubevirt/blob/v1.9.0/staging/src/kubevirt.io/api/core/v1/schema.go and https://github.com/kubevirt/kubevirt/blob/v1.9.0/pkg/network/link/address.go | KubeVirt v1.9.0 API schema and masquerade address code | vmNetworkCIDR default 10.0.2.0/24; gateway = 2nd address, guest = 3rd (10.0.2.1, 10.0.2.2) |
Guest address 10.0.2.2 |
| E22 | https://kubevirt.io/user-guide/compute/live_migration/ | Live Migration - KubeVirt user guide | PVCs must be RWX to live migrate; bridge on pod network not allowed | Cold migration needs neither |
| E23 | https://kubevirt.io/user-guide/storage/disks_and_volumes/ | Disks and Volumes - KubeVirt user guide | bus: virtio; filesystem PVC needs raw disk.img; block PVC consumed as raw device; DataVolume behaviour; containerDisk |
Block PVC, virtio |
| E24 | https://kubevirt.io/user-guide/user_workloads/guest_agent_information/ | Guest Agent information - KubeVirt user guide | GA is optional; AgentConnected condition; guestosinfo, userlist, filesystemlist |
Agent non-blocking |
| E25 | https://kubernetes.io/releases/ and https://dl.k8s.io/release/stable-1.36.txt | Releases - Kubernetes; stable-1.36 pointer | Maintained 1.37, 1.36, 1.35; EOL 1.36 2027-06-28, 1.34 2026-10-27; stable-1.36 = v1.36.5; stable = v1.37.1 | Kubernetes 1.36 |
| E26 | https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ | Installing kubeadm | Red Hat instructions via pkgs.k8s.io per minor; runtime sockets (CRI-O /var/run/crio/crio.sock); swap handling; SELinux note |
kubeadm on CentOS Stream 9 |
| E27 | https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ | Creating a cluster with kubeadm | Single machine: kubectl taint nodes --all node-role.kubernetes.io/control-plane- |
Single-node scheduling |
| E28 | https://kubernetes.io/docs/concepts/architecture/cgroups/ | About cgroup v2 | cgroup v1 deprecated since 1.35; kubelet fails on v1 by default; RHEL 9-like distros use v2 | cgroup v2 |
| E29 | https://github.com/cri-o/packaging and https://github.com/cri-o/cri-o/releases | cri-o/packaging README; CRI-O releases | Packages from download.opensuse.org/repositories/isv:/cri-o per minor; v1.36.6 on 2026-09-21 |
CRI-O 1.36 |
| E30 | https://www.centos.org/download/ | Download - The CentOS Project | CentOS Stream 9 EOL 2027-05-31; Stream 10 EOL 2030-05-31 | Lab lifetime fits |
| E31 | https://www.centos.org/download/aws-images/ | CentOS AWS AMI Cloud Images | CentOS Stream 9 x86_64 ap-south-1 ami-0e7930d02f47291cb; cloud user ec2-user |
Node image exists in the Region |
| E32 | https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/amazon-ec2-nested-virtualization.html | Use nested virtualization to run hypervisors in Amazon EC2 instances | Families M7i, M7i-flex, M8i, M8id, M8i-flex, C7i, C7i-flex, C8i, C8id, C8i-flex, R7i, R7iz, R8i, R8id, R8i-flex, X8i, I7i, I7ie; KVM and Hyper-V as L1; no additional cost; enable at launch or on a stopped instance; check with describe-instance-types; bare metal recommended for performance-sensitive work |
m8i.xlarge with nested virtualization |
| E33 | https://aws.amazon.com/about-aws/whats-new/2026/06/nested-virtualization-intel-us-gov-cloud/ | Nested virtualization is now available on additional Intel platforms and AWS GovCloud (US) regions | 2026-06-18: 7th-gen families added; support in all commercial Regions | ap-south-1 in scope (U1) |
| E34 | https://docs.aws.amazon.com/linux/al2023/ug/virtualization-getting-started.html | Get started with virtualization on Amazon Linux 2023 | Processor extensions only on bare metal or with nested virtualization on; missing /dev/kvm almost always means it was not enabled |
/dev/kvm check |
| E35 | https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html | Understand the Kubernetes version lifecycle on EKS | Standard support 1.36, 1.35, 1.34; 1.36 on EKS 2026-06-02 | EKS version fine |
| E36 | https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html | Customize managed nodes with launch templates | Prohibited: subnet, IAM instance profile, shutdown/hibernate behaviour (CPU options not listed) | EKS /dev/kvm path plausible, undocumented |
| E37 | https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-amis.html | Create nodes with pre-built optimized images | Amazon Linux, Bottlerocket, Ubuntu, Windows AMIs; custom AMIs possible | No EL-aligned EKS AMI |
| E38 | https://docs.aws.amazon.com/eks/latest/best-practices/pod-security.html and https://aws.amazon.com/blogs/containers/implementing-pod-security-standards-in-amazon-eks/ | Pod Security (EKS best practices); Implementing Pod Security Standards in Amazon EKS (AWS Containers blog) | Privileged PSS level; EKS default PSA configuration is privileged for all modes |
EKS admits privileged DaemonSets |
| E39 | https://docs.aws.amazon.com/eks/latest/userguide/pod-multiple-network-interfaces.html | Attach multiple network interfaces to Pods | By default the VPC CNI assigns one IP per pod from an ENI | EKS pod networking model |
| E40 | https://github.com/kubernetes-sigs/aws-ebs-csi-driver (README, docs/install.md, releases) | Amazon Elastic Block Store (EBS) CSI driver | Raw block volumes; compatible with all upstream-supported Kubernetes versions; IAM AmazonEBSCSIDriverPolicyV2 scoped to tag ebs.csi.aws.com/cluster: true; IMDSv2 needs hop limit >= 2 unless host network; v1.66.0 on 2026-09-10 |
Storage design |
| E41 | https://docs.aws.amazon.com/ebs/latest/userguide/general-purpose.html | General Purpose SSD volumes | gp3 baseline 3,000 IOPS and 125 MiB/s; 1 GiB to 64 TiB | gp3 sizing |
| E42 | https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/ec2/USD/current/ec2-ondemand-without-sec-sel/Asia%20Pacific%20(Mumbai)/Linux/index.json | AWS public price file, EC2 on-demand Linux, Mumbai (published 2026-09-25) | m8i.xlarge 0.2227, m7i.xlarge 0.2121, m7i.metal-24xl 5.0904 USD/h; all listed nested families sold in the Region | Cost model |
| E43 | https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/ec2/USD/current/ebs.json | AWS public price file, EBS (published 2026-09-25) | gp3 storage in Mumbai 0.0912 USD/GB-month | Cost model |
| E44 | https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEKS/current/ap-south-1/index.json | AWS Price List, AmazonEKS ap-south-1 (published 2026-09-18) | 0.10 USD per cluster-hour; extended support 0.50 USD/h | EKS cost |
| E45 | https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonVPC/current/ap-south-1/index.json | AWS Price List, AmazonVPC ap-south-1 (published 2026-09-17) | Public IPv4 0.005 USD/h (in use or idle) | Cost model |
| E46 | https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html | Elastic network interfaces | ENI IPv4 addresses come from the subnet range; source/destination checks (on by default) ensure the instance is the source or destination of its traffic | .31 cannot appear in a VPC |
| E47 | https://github.com/flannel-io/flannel/releases | Releases - flannel-io/flannel | v0.28.9 on 2026-08-07 | CNI version |
| E48 | https://github.com/kubernetes/kubernetes/blob/release-1.36/cmd/kubeadm/app/phases/controlplane/manifests.go | kubeadm control-plane manifests (release-1.36) | {Name: "allow-privileged", Value: "true"} |
Privileged API server by default |
| E49 | https://github.com/kubevirt/kubevirt/blob/v1.9.0/pkg/virtctl/imageupload/imageupload.go | virtctl image-upload (v1.9.0) | Flags --volume-mode, --access-mode, --storage-class, --uploadproxy-url, --insecure, --force-bind, --size |
Upload command design |
| E50 | https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/v1.66.0/docs/parameters.md | AWS EBS CSI driver v1.66.0 StorageClass parameters | encrypted: true/false, default false; kmsKeyId: if not specified, AWS uses the default KMS key for the Region |
Explicit encrypted: "true" (section 29.7) |
| E51 | https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonEBSCSIDriverPolicyV2.html | AmazonEBSCSIDriverPolicyV2 (AWS managed policy) | Create, modify and delete limited to volumes tagged ebs.csi.aws.com/cluster = true (or kubernetes.io/created-for/pvc/name for migrated volumes) |
Post-destroy orphan check by tag (section 29.1) |
| E52 | https://github.com/flannel-io/flannel/releases/download/v0.28.9/kube-flannel.yml | flannel v0.28.9 deployment manifest | net-conf.json: "Network": "10.244.0.0/16", backend vxlan |
Pod CIDR (section 29.3) |
| E53 | https://github.com/kubernetes/kubernetes/blob/release-1.36/cmd/kubeadm/app/apis/kubeadm/v1beta4/defaults.go | kubeadm v1beta4 defaults (release-1.36) | DefaultServicesSubnet = "10.96.0.0/12", DefaultClusterDNSIP = "10.96.0.10" |
Service CIDR (section 29.3) |
| E54 | https://github.com/cri-o/cri-o/blob/release-1.36/contrib/cni/11-crio-ipv4-bridge.conflist | CRI-O release-1.36 default bridge CNI configuration | Bridge network crio with subnet 10.85.0.0/16 |
Range kept clear (section 29.3) |
| E55 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/doc/datavolumes.md | Data Volumes - CDI v1.66.1 documentation | “Data Volumes(DV) are an abstraction on top of Persistent Volume Claims(PVC)”; the DV “will monitor and orchestrate the import/upload/clone of the data into the PVC”; the pvc and storage sections both “result in CDI creating a PVC resource” |
The standalone DataVolume is the migration disk’s lifecycle object (sections 16, 18, 29.1) |
| E56 | https://github.com/kubevirt/containerized-data-importer/blob/v1.66.1/staging/src/kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1/types.go | CDI v1.66.1 API types | DataVolumeTTLSeconds: “the time in seconds after DataVolume completion it can be garbage collected”; “Deprecated: Removed in v1.62” |
The DataVolume is expected to remain after Succeeded (INFERRED; section 18) |