Files
home-network-k3s/docs/06-vm-workloads-kubevirt.md
jakman85andClaude Opus 5.5 250fb54154 docs: bring the specification in line with the accepted implementation
The operator ruled the implementation repo's main (50c8719) the latest
and accepted state. Sync the spec to it.

Accepted changes are reflected: one bare-metal environment, TrueNAS bulk
storage, R2 off-cluster backup, CloudNativePG, single-node VPN gateway
with Cilium fail-closed, inbound WireGuard, per-app auth sidecars,
Renovate, the CI gate as approver, and the pm-agent. New ADR-0017..0025;
ADR-0016 corrected and accepted; R-* IDs unchanged, eight amended.

Where the implementation has simply not met a requirement, the
requirement stands and the shortfall is recorded in docs/16-known-gaps.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 22:46:00 -06:00

94 lines
5.4 KiB
Markdown

# 06 — VM Workloads (IaaS via KubeVirt)
Serves: **R-PLAT-03**, **R-PLAT-04**, **R-STATE-01**. Related: [ADR-0005 (KubeVirt)](../decisions/adr-0005-vm-workloads-kubevirt.md).
## Why this exists
"Not everything fits in a container." Some workloads need a full OS, a specific kernel, non-container
software, or a lift-and-shift of an existing VM. The requirement (R-PLAT-03) is that these VMs are
managed through the **same Kubernetes API, CLI, and YAML** as containers — one control plane, one
GitOps flow, one network/storage/observability model. **KubeVirt** delivers exactly this: VMs as
Kubernetes custom resources.
## Do not confuse the two VM layers
- **Layer 1 — cluster nodes** are bare-metal mini-PCs, baselined and managed as code
([02](02-environments-and-provisioning.md)). **Not** KubeVirt.
- **Layer 2 — application VMs** run *inside* the cluster via KubeVirt and are the subject of this
spec. Managed by Kubernetes/GitOps like any other workload.
## What KubeVirt provides
- `VirtualMachine` / `VirtualMachineInstance` custom resources — declare a VM in YAML, reconcile via
Flux, manage via `kubectl` (and `virtctl` for console/start/stop).
- **CDI (Containerized Data Importer)** to import/clone disk images into **Longhorn** PVCs — VM disks
get the same replication as container volumes and are backed up by default
([04](04-storage-and-state.md)).
- VMs sit on the **same Cilium network** — so **default-deny isolation and egress classes apply to
VMs too** (R-NET-*). A KubeVirt VM can be `egress: vpn` just like a container.
- VMs are exposed through the same **Gateway API / Service** model and monitored through the same
stack.
## The unified contract
A VM onboarded via a ServiceDefinition (`kind: vm`) gets, from the *same* declarative surface:
- Compute (vCPU/RAM), an OS image (via CDI), and Longhorn-backed disks.
- Default-deny networking + explicit allow rules + egress class (R-NET-*).
- TLS + DNS + ingress if it exposes an HTTP(S) service (R-TLS/R-DNS).
- Metrics/logs and alerting (R-OBS-*) — node-exporter-class metrics from inside the guest plus
KubeVirt/virt metrics from the platform.
- Backup/DR of its disks (R-STATE-03) and rebuildability (R-STATE-01): destroy the VM, keep the
disk PVC, recreate from the declaration.
`kind: vm` is the catalog path. A VM with a custom install flow MAY instead be declared directly in
git under the same controls: Flux reconciliation, default-deny networking, Longhorn disks and
platform monitoring, in its own VM tier that never gates the application waves.
**Recorded per-VM exception:** UniFi's disk is excluded from volume backup; its Kubernetes resources
are captured, and the design relies on UniFi's own configuration export for application state
([04](04-storage-and-state.md)).
## Update policy for VMs (R-UPD-*)
VM "updates" differ from container image tag bumps:
- **Guest OS package updates** happen *inside* the guest on a schedule (unattended-upgrades / dnf-
automatic style), configured via the VM's cloud-init/declaration — no human action.
- **VM image/template updates** (new base image) are handled as declarative changes to the VM
definition; where a golden-image pipeline exists, new images can be promoted automatically per
policy.
- The requirement "latest stable without human/AI/GitOps change" is met for guest packages via in-
guest automation; base-image rebuilds follow the same automated-promotion philosophy as containers
where feasible. Record any per-VM exceptions explicitly.
## When to choose VM vs container
Prefer containers (PaaS) by default. Choose a KubeVirt VM (IaaS) when:
- The software isn't reasonably containerizable (kernel modules, full desktop, licensing tied to a
VM, appliance images).
- Lifting an existing VM is faster/safer than re-platforming.
- Strong OS-level isolation is required beyond namespace/network policy.
The catalog is container-first and KubeVirt is the escape hatch. **UniFi is the production VM
workload**: UniFi OS Server is an installer that expects a full Ubuntu host and runs its own container
stack and embedded datastore, so it is an appliance image in all but name. It is hand-authored in git
under the rule above. The catalog's `vm-demo` entry is the `kind: vm` reference and conformance
fixture and is not deployed. The IaaS path must be **proven** with at least one representative VM
workload so it is real, not theoretical.
## Prerequisites & constraints
- Nodes must expose virtualization extensions (native on the mini-PCs) — captured in the node baseline
([02](02-environments-and-provisioning.md)). VMs never fall back silently to software emulation: a
node without KVM fails loudly.
- Live migration is **enabled and opt-in per VM**: the VM sets a live-migrate eviction strategy and
keeps its disks on the migratable Longhorn class. There is no cluster-wide policy, because forcing
it would wedge node drains for VMs on non-migratable disks. Other VMs restart on drain.
- Resource pressure: VMs are heavier than pods — capacity planning must account for them,
particularly on the 3-node cluster.
## Acceptance gates
- Create, start, stop, and destroy a VM entirely through Kubernetes YAML + `kubectl`/`virtctl`.
- The VM's disk is Longhorn-backed; destroy/rebuild preserves data (R-STATE-01).
- Default-deny applies: the VM can reach only what its allow rules permit.
- An `egress: vpn` VM egresses via the VPN with a working kill-switch (R-NET-06).
- Guest OS auto-updates on schedule with no manual step (R-UPD-*).