vdi
corral-vdi is the Phase 1 implementation of
RFC-0001: static desktop pools, built by cloning
an already-built VM, with manual (CLI-driven) assignment. No broker, no
self-serve web page, no idle reclaim yet β those are later phases, tracked
in issue #69 (see also
VDI Epic Status). What's here is real and cluster-tested:
create a pool, hand a member to a user, connect, release it, delete the pool.
Installβ
corral plugin install vdi
Or build from source (same repo, no separate checkout):
go build -o ~/.local/share/corral/plugins/corral-vdi ./cmd/corral-vdi
Mental modelβ
A pool is not a new kind of object β it's just VMs with a
corral.dev/vdi-pool=<name> label. There's no pool CRD, no controller
watching anything, no reconciliation loop. corral vdi pool create clones
N VMs and labels them; corral vdi pool list reads the labels back;
corral vdi pool delete deletes the labeled VMs. If you're ever unsure
what a command actually did, kubectl get vm -n <ns> -l corral.dev/vdi-pool
shows you the truth directly β there's no other state to go stale.
Assignment ownership is held by a Kubernetes coordination.k8s.io/v1
Lease named corral-vdi-<member>. Lease creation is first-writer-wins; a
stale Lease is replaced with its resourceVersion, so concurrent claimants
cannot select the same member. The Lease records the pool/member in labels,
the identity, acquisition/renewal time, and a one-hour expiry. The
corral.dev/vdi-assigned-to=<user> label plus corral.dev/vdi-claimed-at
annotation remain presentation state on the VM. Existing Phase 1 pools have
no Lease and remain visible; their first atomic claim creates one. A legacy
unassign can clear label-only members, while an active Lease may only be
released by its owner.
Prerequisitesβ
- A golden VM β an already-built, already-working VM you want to make
copies of. Build it the normal way:
- Desktop Linux:
corral bootc create mydesktop --image ghcr.io/ublue-os/bluefin:latest - Windows:
corral windows create mydesktop --iso <url>(see the create dialog's ISO presets in the web UI, ordocs/api.md's note onPOST /api/vmswithwindows: true) - Anything else:
corral create mydesktop --kubevirt ...
- Desktop Linux:
- The golden VM's clone needs whatever the source needs β same
StorageClass availability, same node placement constraints.
corral doctorcatches most of this before you find out the hard way. - KubeVirt's clone feature needs a
VolumeSnapshotClassfor persistent-disk VMs (the same requirementcorral clone/snapshot already has βcorral doctorflags a missing one).
Walkthroughβ
# 1. Build (or reuse) a golden VM. This one's a normal corral bootc VM.
corral bootc create golden-desktop --image ghcr.io/ublue-os/bluefin:latest
corral start golden-desktop
# ...customize it however you like (install packages, configure things)...
corral stop golden-desktop # clone from a stopped VM for a clean disk state
# 2. Create a pool of 3 clones.
corral vdi pool create devpool --from golden-desktop --size 3
# pool "devpool" created: 3 members
# devpool-1
# devpool-2
# devpool-3
# 3. See what's in it.
corral vdi pool list
# devpool (ns/corral-vms, 3 members)
# devpool-1 free stopped
# devpool-2 free stopped
# devpool-3 free stopped
# 4. Hand one to a user. This also starts it if it was stopped.
corral vdi assign devpool alice
# assigned devpool-1 β alice
# connect: corral vdi connect devpool-1
# 5. Connect. Prints every reachable path β pick whichever fits the guest.
corral vdi connect devpool-1
# VNC (browser or client): corral web β open devpool-1 β Console
# RDP (if the guest answers on 3389): corral viewer devpool-1 (or a native RDP client via virtctl port-forward)
# SSH (Linux guests): corral ssh devpool-1
# 6. Native USB Redirection (smartcards, security keys, YubiKeys).
# List local USB devices available on the client:
corral vdi usb list
# Redirect a selected device to the running assigned desktop (requires virtctl):
corral vdi usb redir devpool-1 --device 1050:0407 --user alice
# 7. When alice is done, release it. This stops the VM too β pooled
# desktops don't stay running unclaimed.
corral vdi unassign devpool-1
# 8. Tear the whole pool down when you're finished with it.
corral vdi pool delete devpool
Native USB Redirectionβ
corral vdi usb redir <member> redirects a local USB device from the operator's machine straight to the guest via virtctl usbredir (wrapping KubeVirt's native USB redirection rather than legacy SPICE).
- Use cases: Hardware security keys (YubiKey / FIDO2), smartcards (CAC/PIV), USB tokens, and external peripheral passthrough.
- Prerequisites & constraints:
- Requires the
virtctlclient binary installed locally. - Desktop member must be actively assigned and running in KubeVirt.
- Ownership authorization check (
--user) ensures users cannot redirect local devices to another user's desktop. - KubeVirt VMs support USB redirection; CT and non-KubeVirt backends report actionable unsupported errors.
- Before attaching, security implications (host device exclusivity, migration blockage, guest trust boundaries) are displayed.
- Requires the
What "connect" actually does todayβ
corral vdi connect <member> prints instructions β it does not yet
pick a protocol and open a session for you. That one-click behavior is
Phase 2 territory (see the RFC), and specifically depends on
ADR-0002 phase 2 (in-browser RDP)
landing first, so Windows members get the same one-click experience VNC
already gives Linux members. Until then:
| Guest | How to actually connect |
|---|---|
| Any VM (VNC always works) | corral web β open the VM β Console tab (noVNC in the browser) |
| Linux VM with RDP configured | GET /api/vms/{ns}/{name}/rdp (or the Summary panel) tells you if 3389 answers; connect with a native RDP client via virtctl port-forward |
| Windows VM | Same RDP path as above β corral-windows-created VMs expose RDP through the proxy service if you passed --rdp at create time |
| Any VM with SSH | corral ssh <member> |
Troubleshootingβ
golden VM "X" not foundβ the--fromVM doesn't exist in the target namespace.corral vdi pool createdoesn't search other namespaces; pass-nto match wherever the golden VM actually lives.timed out ... waiting for the clone to produce VMβ KubeVirt's clone controller didn't produce the target VM within 2 minutes. Checkkubectl get virtualmachineclone -n <ns>for the clone object's phase; a stuck clone is almost always a StorageClass/VolumeSnapshotClass issue (see Prerequisites above) or the source VM's PVC not being snapshottable. This is a real, live-found failure mode β see the RFC's commit history for the bug that motivated the wait/timeout logic.pool has no free membersβ every member is currently assigned or another claimant won its Lease race. Eithercorral vdi unassignone, orcorral vdi pool createa bigger pool (there's no live resize yet β delete and recreate, or create a second pool).- A claim is stuck after a partial failure β inspect
kubectl get lease -n <ns> corral-vdi-<member> -o yaml. The Lease is intentionally retained if label update or VM startup fails, preventing a second user from receiving a half-configured desktop. The owner can retrycorral vdi assign; after the one-hour expiry an administrator can retry and recover the member. insufficient device capacity for "X"ordevice admission failedβ The golden VM requests host devices (PCI passthrough, mediated vGPU, SR-IOV), but cluster capacity cannot satisfysize * per_vm_devicesacross the nodes.corral vdi pool createvalidates allocatable devices minus active VMI allocations and refuses pool creation up front before partial clones can be created.- Assigned member won't start β
corral vdi assignsurfaces the underlyingvirtctl starterror directly (e.g. insufficient cluster capacity, a feature-gate the golden VM's spec needs). The assignment label is still set even if start failed βcorral vdi unassignto back out and retry once the underlying problem's fixed.
GPU & Host-Device Capacity Validationβ
When a golden VM requests host devices or GPUs (via gpus or hostDevices in its spec), corral vdi pool create performs pre-flight capacity admission:
- Device type classification: Inspects KubeVirt configuration to distinguish exclusive PCI passthrough (
pciHostDevices), mediated vGPU devices (mediatedDevices), and external device providers (vGPU/SR-IOV). - Allocatable vs Allocated tracking: Queries per-node allocatable resources and active running VMIs to compute actual remaining capacity.
- Concurrency & Topology constraints: Ensures not only that total devices are sufficient, but that node placement can satisfy multi-device requirements per VM.
- Actionable failure reporting: Refuses impossible pools before any mutation and reports resource shortages with per-node availability breakdowns.
Hardware Limitationsβ
- Single-VM Full Passthrough: Physical GPUs attached via full PCI passthrough (e.g., consumer GPUs without SR-IOV/vGPU support) are strictly 1:1 exclusive to a single running VM instance. A pool of size $N$ requires $N$ distinct physical GPUs allocatable across cluster nodes.
- Mediated / vGPU Sharing: Mediated devices (NVIDIA GRID/vGPU, Intel GVT-g) allow multiple pool members per physical card up to the configured profile limit.
Current limitations (by design, not bugs)β
- No self-serve. Assignment is a CLI/admin action β there's no end-user "get a desktop" page yet. The Lease primitive is ready for the later broker, but the broker is not part of this plugin slice.
- No idle/logout reclaim. Unassigned-but-still-running members don't happen (unassign always stops), but there's nothing that notices "alice hasn't touched devpool-1 in 3 hours" and reclaims it automatically.
- No live resize. Growing or shrinking a pool means deleting and recreating it, or manually cloning one more member and hand-labeling it.
- CT-backed pools aren't implemented. Only VM (KubeVirt) golden sources work today; Containers (CT) pooling is exploratory (RFC's Phase 4 territory).
None of this is hidden β see RFC-0001 for the full phased plan, docs/vdi-epic-status.md for the hardware gating and dependency chain, and issue #69 for what's tracked next.