Skip to content

Latest commit

Β 

History

History
305 lines (268 loc) Β· 17.9 KB

0001-vdi-plugin.md

File metadata and controls

305 lines (268 loc) Β· 17.9 KB

RFC-0001: VDI plugin β€” Windows/Linux desktop pools on Corral

Status: Phases 0 and 1 implemented. Browser RDP via IronRDP/RDCleanPath and the corral-vdi static-pool CLI have shipped. From Phase 2, atomic Lease claims (#226), CT-backed ephemeral pools (#230), the self-service broker API (#227, ADR-0013), reclaim (#228), and one-click connect (#229) have shipped. The rest of Phases 2–4 remain a design proposal; this RFC defines their corrected scope but does not commit their implementation. Setup guides: docs/vdi.md and docs/vdi-ct-pools.md, epic status: docs/vdi-epic-status.md. Date: 2026-07-02 Updated: 2026-10-15 Author: grilled out of a live session with James Reilly + Claude

Summary

Add a corral-vdi plugin that turns Corral's existing VM/Container machinery into a small, self-hosted Virtual Desktop Infrastructure. It provides pools of Windows or Linux desktops that Corral assigns to users on request. Users reach them through the browser, and Corral reclaims them after an explicit release or under a conservative session policy. It is not a Citrix/Horizon replacement. It is a homelab-to-small-team-scale VDI that keeps optional broker/auth concerns in plugins and works through Tailscale or another private ingress. This follows the scope discipline that keeps Corral's core lean.

Why now

Corral already has almost every component a VDI stack needs, built for other reasons:

VDI need Already exists in Corral as
Remote display in the browser noVNC bridge (/api/vnc/{ns}/{name}), xterm.js serial bridge
Browser RDP IronRDP over the RDCleanPath bridge, plus RDP detection and raw websocket transport (ADR-0002 phases 1 and 2)
Windows guest provisioning corral-windows plugin β€” UEFI/TPM/virtio, driver ISO
Full desktop Linux images corral bootc β€” builds Universal Blue/Bluefin/TunaOS desktop images on-cluster from a container image
Lightweight ephemeral Linux sessions Containers (CT) β€” pkg/ct, distrobox-style persistent rootfs
Reachability direct-first console routing, Tailscale exposure, ingress-agnostic KubeVirt Services, and Corral-peer relay fallback
Identity trusted Tailscale identity or the optional OIDC auth-gateway plugin (ADR-0003/ADR-0006)
Cluster capability gating corral doctor β€” GPU/PCI passthrough check, StorageClass checks
Replica pools of identical VMs KubeVirt's own VirtualMachinePool CRD

What's missing is the thin layer that turns "a pool of VMs" into "VDI". That layer is assignment (which user gets which desktop) and lifecycle policy (power on demand, reclaim on idle/logout). It also includes one connect button that picks the right protocol per desktop. That layer, not a new console layer and not a new hypervisor integration, is the actual scope of this RFC.

What we're explicitly not building

These exclusions come from the research (full brief: link in the issue that tracks this RFC). They are deliberate, not oversights:

  • Not a new remote-display protocol. noVNC/RDP-over-websocket stay. SPICE is dead upstream. Per a 2025 kubevirt-dev thread, QEMU is on track to drop SPICE server support entirely. Do not build on it.
  • Not a general-purpose connection broker as a product. oVirt's engine+portal architecture and Leostream (commercial, KubeVirt-aware since 2025) are the reference points for the pattern (pool + ticket + assignment). They are not something to run alongside Corral as a second control plane.
  • Not a fix for Windows license rules. Pooled/multi-session Windows needs Enterprise E3/E5 or RDS CALs. This is a customer-facing constraint that this RFC flags and then leaves. It is not a technical problem that Corral can solve.
  • Not a GPU virtualization layer. KubeVirt and the GPU Operator from NVIDIA already handle vGPU/mediated-device passthrough. corral doctor already checks for it (this session's GPU/PCI passthrough check). Reuse, don't rebuild.

Prior art, and how this differs

  • oVirt (community-maintained, with releases still in 2026): proves the pool+ticket broker pattern. It is not Kubernetes-native, so it is not directly reusable; only the pattern is.
  • Leostream: commercial, added OpenShift Virtualization/KubeVirt support ~end of 2025. It validates "an external broker drives the KubeVirt API" as a real architecture. Someone who solves the same problem commercially chose it. Closed-source: differentiate from it, don't depend on it.
  • Kasm Workspaces: as of v1.16, it provisions real VMs via Harvester/KubeVirt in addition to its container-native workspaces. It independently came to the same "containers for ephemeral, VMs for persistent" split. This RFC proposes that split for Corral's CT + bootc combination. It is the strongest external validation we found that the dual-workload approach isn't a hack.
  • Selkies (Google-originated, actively developed): WebRTC desktop streams with GPU acceleration, container-first. It is not a drop-in noVNC replacement for VM consoles. It streams from inside a pod, not from a VM framebuffer. Its prebuilt container images for desktops are strong candidates for what runs inside an ephemeral desktop pool on Corral CTs. These images include KDE Plasma, Wine/Proton, and GPU passthrough. Reuse the images, not the whole project.
  • FOSDEM 2026 "VDI and KubeVirt" talk (KubeVirt maintainer, USB redirection/console work). It proposes the D-Bus display interface of QEMU as a pluggable remote-display seam. External bridges (Guacamole, Selkies, custom) can then attach, and they do not have to own the whole console pipeline. It has no shipped code yet. Track it: if it lands upstream, it may be a better long-term seam than virtctl vnc --proxy-only for phase 3+ below.

Design

Concepts (candidates for CONTEXT.md once this settles)

  • Desktop Pool β€” a named group of identical desktops (VM- or CT-backed). A pool has a template (bootc image / Windows ISO+answer-file / CT image), a target size, and a reclaim policy. For the VM-backed case, it builds on KubeVirt's VirtualMachinePool. CT-backed pools use a thin equivalent. No upstream CRD exists for pooled pods, so this is new code, but small.
  • Assignment β€” an exclusive claim from an authenticated identity to one pool member. Identity can come from trusted Tailscale ingress or the OIDC auth gateway. VDI consumes the identity contract that results and does not depend on either provider directly. Phase 1's labels are presentation state, not a concurrency primitive. Self-service assignment must use an atomic Kubernetes operation (a per-member Lease or resource-versioned compare-and-swap). This way, no two simultaneous claims can receive the same desktop.
  • Session β€” the active connection associated with an Assignment. An open websocket to the console proves only that a connection exists. It does not prove that the user actively types or moves the mouse. Reclaim policy has distinct inputs: disconnect time, explicit release, maximum session duration, and an optional activity signal from the guest.
  • Connect β€” one button/command that finds the actual reachable protocol of a desktop and opens the right client path. Corral already has an RDP probe. Extend the same idea to answer "is this a VNC-only guest, RDP-capable, or a CT with only a terminal". Today the client paths are the existing noVNC/xterm.js bridges and in-browser IronRDP. Corral tries the direct path first: it uses an advertised guest or ingress endpoint when one is reachable. For complicated network topologies, it then falls back to a Corral-peer console relay.

Phased plan

Phase 0 β€” in-browser RDP prerequisite. Implemented. ADR-0002 phase 2 shipped: the web UI now embeds IronRDP and Corral provides the RDCleanPath transport that IronRDP needs. Windows and RDP-enabled Linux desktops can therefore use the same one-click experience in the browser as VNC desktops.

Phase 1 β€” static pools, manual assignment (CLI, no broker yet). Implemented. corral vdi pool create <name> --from <golden-vm> --size N clones an already-built VM N times via kubevirt.Client.Clone and labels the clones as pool members. You build the golden VM the normal way (corral bootc/ corral-windows/corral create), then customize and stop it.

corral vdi assign <pool> <user> hand-wires a claim through a per-member Kubernetes Lease. The existing K8s label/annotation mirror the Lease as presentation state. Lease creation is first-writer-wins, and stale recovery uses a resource-versioned replacement. Existing label-only Phase 1 pools migrate lazily when their first atomic claim creates a Lease. corral vdi connect <member> prints the existing VNC/RDP/SSH paths for that member. Full setup guide: docs/vdi.md.

The result differs slightly from the first draft above. It uses --from <existing-vm> (clone a golden VM), not --template <image> (build N from scratch). A clone directly reuses the already-tested primitive of corral clone. It also matches how real VDI systems build pools: golden image once, clone many. This avoids N repeated runs of a full bootc build or Windows ISO install.

Live verification found a real bug, and we fixed it. Clone() returns as soon as it applies the VirtualMachineClone CRD, not once the target VM exists. On a real cluster, CreatePool originally raced ahead and tried to label a VM that did not exist yet. The fix is a poll-wait (waitForVM, 2min timeout) between clone and label.

Phase 2 β€” atomic self-service claims, sessions, and reclaim. Proposed. An authenticated user hits "Get a desktop," atomically claims an available member, and powers it on if necessary. Corral then sends the user to the best reachable console. Claim creation must be compare-and-swap safe. The existing list-then-label implementation of Phase 1 is not enough under concurrent requests.

The first reclaim policy is deliberately conservative:

  1. explicit "Release desktop" is authoritative;
  2. console disconnect starts a configurable grace period;
  3. a reconnect during the grace period cancels the reclaim;
  4. a maximum session duration is an optional administrative ceiling; and
  5. true input-idle reclaim needs an optional guest/protocol activity signal; Corral does not infer it merely from websocket age.

After reclaim, persistent pools stop the VM and retain its disks. Ephemeral pools destroy and recreate the member from the golden source. CT-backed pools have their own reconciliation primitive, now implemented (#230, docs/vdi-ct-pools.md). A ConfigMap holds the pool definition, and members are CTs. Claims use the same per-member Lease, and a release recreates the member from its image. This phase is the actual VDI broker, not another VM-management command.

The broker API has shipped (#227): corral web serves claim, start, inspect, and release under /api/vdi/ when CORRAL_VDI_BROKER=1 is set. It takes the caller from the trusted identity headers and authorizes each desktop operation against the holder of the member's Lease. See docs/vdi.md.

Session records and reclaim have shipped (#228) with rules 1 to 4 above. The broker counts the open consoles of each desktop and records them on the Lease, so a restart keeps the disconnect times. The defaults are a 15-minute disconnect grace and no maximum duration. corral vdi pool create --ephemeral makes VM pools whose members Corral clones again from the golden VM after each claim. Rule 5 stays unimplemented. See docs/vdi.md.

One-click connect has shipped (#229). The broker chooses RDP in the browser when TCP 3389 answers, then VNC, then the serial console; a CT gets its terminal. It gives the console route and the fallbacks, and the web Desktops screen and corral vdi connect use the same answer. The console websockets of a claimed desktop accept only its holder and admins. See docs/vdi.md.

Phase 3 β€” capacity-aware GPU pools and native USB redirection. Proposed. Pool creation inspects the golden VM's host-device requests and compares the requested pool size with allocatable matching devices. It must distinguish exclusive passthrough from mediated/vGPU capacity. A generic "GPU present" doctor result is not enough to promise concurrent desktops.

In this phase, the client type decides how USB redirection works. A native CLI command can wrap virtctl usbredir for smartcards and security keys. Browser USB redirection is a separate experimental feature. It needs WebUSB permission UX and a purpose-built bridge. The CLI transport does not imply it, and it is not part of the initial Phase 3 commitment.

Phase 4 (exploratory, not committed) β€” WebRTC streams for ephemeral Linux pools. Selkies-style container images inside Corral CTs for GPU-accelerated, low-latency ephemeral desktops, as an alternative to noVNC for that specific pool type. The time-boxed decision record for the WebRTC spike now defers implementation. It needs a representative cluster comparison against VNC before work goes ahead. Track the FOSDEM D-Bus-display proposal before you commit engineering time here, because it may change the right integration seam.

Feasibility, honestly

  • Phases 0 and 1 have shipped. Phase 1 was low-risk assembly of existing Corral machinery (bootc, corral-windows, corral ct, KubeVirt's own VirtualMachinePool), not new hard problems.
  • Phase 2 claim selection is straightforward, but concurrency is not optional. A Lease or compare-and-swap claim must precede VM startup.
  • Phase 2's genuinely hard part is input-idle detection. Websocket open/close is useful session-presence data, but it is not user activity. Start with explicit release and disconnect grace. Add guest cooperation only when the operational value is worth the per-OS integration.
  • AMD's current driver/firmware support, not KubeVirt, limits Phase 3's GPU story. We verified this directly against AMD's GIM/SR-IOV driver release notes (2026-07). Officially supported hardware is only the MI-series Instinct accelerators for datacenters. The one exception is a Radeon PRO card for workstations. There are no APUs and no consumer/integrated GPUs at all. The Strix Halo APU from AMD in karnataka is a full-GPU-passthrough-to-one-VM device today, not a multi-tenant vGPU one. An AMD engineer has said that client-GPU SR-IOV is "in the roadmap," with no committed timeline. Check this again before you commit to Phase 3, and do not assume that it is permanently impossible. Either way, this hardware is fine today for "one nice, accelerated desktop", but not for a GPU-accelerated multi-user pool.
  • Overall scope: this is realistically a personal/small-team VDI, not an oVirt/Kasm/Leostream competitor. The phased plan fits what one person and Corral's existing components can ship in practice. It does not aim for feature parity with enterprise VDI.

Open questions (for the grilling session)

  1. Claim primitive: use a standard Kubernetes Lease per member, or a purpose-built Assignment CRD? A Lease gives atomic acquisition and expiry and adds no new API. A CRD gives clearer domain state and validation. This RFC rules out a local registry or an unguarded ConfigMap, because claims must be cluster-visible and concurrency-safe.
  2. CT-backed pool primitive β€” resolved by #230: a small reconciler of our own. A ConfigMap holds the declared pool, and labels mark the member CTs. The reconciler takes the member Lease before each destructive step. Only ephemeral CT pools are supported.
  3. Reclaim defaults β€” resolved by #228: a 15-minute disconnect grace (CORRAL_VDI_DISCONNECT_GRACE) and no maximum session duration (CORRAL_VDI_MAX_SESSION) by default. An administrator may force-release a connected session, and the task log records it. Input idle remains unavailable without guest/protocol cooperation.
  4. License UX: does Corral only document the Windows license constraint (the current lean)? Or does it actively refuse to create pools above some size without an explicit --i-have-licenses flag?
  5. Scope of "plugin" β€” resolved for Phase 1: single corral-vdi binary (pool/assign/unassign/connect subcommands), as leaned toward above. Revisit if/when Phase 2's broker becomes a genuinely separate, long-lived process, unlike Phase 1's one-shot CLI commands. That is a real reason to split, and not mere default caution.
  6. Broker placement β€” resolved by #227 (ADR-0013): opt-in /api/vdi/ routes in corral web, behind the existing request pipeline. Only that process holds both the trusted caller identity and the console sessions that reclaim (#228) needs. The policy lives in pkg/vdi, and the admin workflow stays in the corral-vdi plugin.

Sources

The research brief covers the KubeVirt VDI ecosystem, remote-display protocols, and prior art for session brokers. It also covers the GPU/USB state of Windows-on-KubeVirt and patterns for Linux desktop pools. See the issue that tracks this RFC for the complete, sourced version. Key links: FOSDEM 2026 VDI+KubeVirt talk, KubeVirt VirtualMachinePool docs, KubeVirt USB redirection. More links: NVIDIA GPU Operator + KubeVirt, Selkies, Kasm VDI on Kubernetes, oVirt project update, Sept 2025.