A CT-backed pool is a set of ephemeral Linux desktops that Corral builds from one container image. Each member is a normal Corral Container (CT). When a user releases a member, Corral destroys it and makes a new one from the image. The next user always gets a clean desktop. Issue #230 tracks this slice of RFC-0001. For VM-backed pools, see docs/vdi.md.
Kubernetes has no VirtualMachinePool equivalent for pods that keep a stable
name and volume. Corral does not act as if one exists. A small reconciler in
pkg/vdi keeps the members in line with a declared pool.
| Part | Where it lives | What it holds |
|---|---|---|
| Pool definition | ConfigMap corral-vdi-ctpool-<pool>, key spec |
Source image, CPU, memory, disk, StorageClass, privileged and init flags, mode, and size |
| Pool status | Same ConfigMap, key status |
Desired, observed, ready, claimed, free, and unschedulable counts, one row for each member, and the errors of the last reconcile |
| Member | CT <pool>-<n>, n from 1 to the size |
A pod and a data PVC with the labels corral.dev/vdi-ct-pool, corral.dev/vdi-ct-member, and corral.dev/vdi-ct-source |
| Claim | Lease corral-vdi-ct-<member> |
The holder identity, the acquire time, and the expiry, as in the VM claim contract of #226 |
| Assignment display | Annotation corral.dev/vdi-assigned-to on the member PVC |
A copy of the claim holder for display. The Lease is the authority. |
The corral.dev/vdi-ct-source label is a hash of the source fields. A member
with a different hash has drifted from the pool definition.
A member is in one of these states: Missing, Stopped, Starting, Ready,
Unschedulable, Failed, or Terminating. The status row also shows the
claim holder, an expired claim, maintenance, drift, and a member above the
pool size.
# Define a pool of 3 desktops and reconcile it once.
corral vdi ct-pool apply desk --image ghcr.io/example/desktop:1 --size 3 --mem 2Gi
# Keep the pool in line with its definition. Failed steps are retried.
corral vdi ct-pool reconcile desk --watch --interval 30s
# Show the live state of each member. This command changes nothing.
corral vdi ct-pool status desk
# Give a free desktop to a user. A second claim by the same user gets the same desktop.
corral vdi ct-pool claim desk alice@example.com
# The user is done: destroy the desktop and make a new one from the image.
corral vdi ct-pool release desk desk-1 alice@example.com
# Change the image or the size, then reconcile again.
corral vdi ct-pool apply desk --image ghcr.io/example/desktop:2 --size 5
# Remove the pool and all of its members.
corral vdi ct-pool delete deskAll commands use the -n namespace flag. The default is the Corral namespace.
The --context flag selects a Corral context of the kubevirt backend. Without
it, kubectl uses its current context.
Each reconcile reads the definition, the members, and the claims. Then it does these steps:
- It creates each missing member from 1 to the pool size.
- It starts each stopped member.
- It replaces each unclaimed member that has drifted or failed. It deletes the member and then creates it again from the current source.
- It deletes each unclaimed member above the pool size.
- It records the new status in the pool ConfigMap.
A reconcile never destroys a member that a user has claimed. This rule applies to drift, failure, scale-down, and an expired claim. A claimed member stays until the user or an administrator releases it.
Before a create, a replace, or a delete, the reconciler takes the member Lease
as the holder corral-vdi/reconciler. A user cannot claim a member while the
reconciler holds its Lease. If a user claims the member first, the reconciler
does not get the Lease and leaves the member alone. The reconciler removes its
Lease when the step ends, also when the step fails. A Lease from a reconciler
that stopped mid-step expires after 10 minutes. Then the next reconcile takes
it over.
Because of these rules, you can run reconcile again at any time. Two reconcilers can also run at the same time. A second run with no change in the cluster does nothing.
A claim follows the Lease contract of #226. The first writer to create the Lease wins. Corral gives a free member to the identity. If the member has no pod, Corral starts it.
A free member has no Lease, an index in the pool size, and a current source.
Its state is Ready, Starting, or Stopped. If no member is free, the
claim fails with "pool has no free members". The error shows the desired,
claimed, and unschedulable counts.
A release by the claim holder first changes the Lease holder to the
reconciler. This change is a compare-and-swap on the Lease
resourceVersion. Then Corral deletes the pod, the console Service, and the
data PVC, and waits until they are gone. Then it creates the member again from
the current source, but only if the member index is in the pool size. At the
end, Corral deletes the Lease.
If Corral cannot create the new member, the release reports the error. The next reconcile then creates the member.
A release of a member with no claim does nothing. An administrator can use
--force to release a claim that another identity holds.
A claim expires after one hour. The session work of
#228 will renew claims. Until
then, a claim by the same identity renews an expired claim. An expired claim
still protects the desktop. Corral does not give the member to a second user.
Use release --force to recover it.
Only the ephemeral mode is supported. A release destroys the member data
PVC. A replacement after drift or failure also destroys the data PVC of an
unclaimed member. A pool definition with mode: persistent fails with an
explicit error at apply, reconcile, claim, and release. Use a VM pool
for persistent desktops.
The data PVC keeps data while a member stops and starts. An unprivileged member
keeps /data only. A privileged member keeps its root file system on the PVC
(see the CT model in pkg/ct).
- A member pod that the scheduler cannot place is
Unschedulable. Its status row shows the scheduler message. It does not count as free. When the cluster gets capacity again, the scheduler starts the pod and the member becomes free without help. exhausted: truein the status shows that the pool has members but none is free.- A failed create, start, or delete adds a line to
errorsin the status and makesreconcileexit with an error. The step does not leave a Lease, so the next reconcile tries again.reconcile --watchretries on each interval. - A deleted pool is first set to size 0. If a member delete fails, the
definition stays, and you can run
deleteagain.
The identity that runs corral vdi ct-pool needs these permissions in the
pool namespace:
- ConfigMaps: get, list, create, patch, delete.
- Pods and PersistentVolumeClaims: get, list, create, patch, delete.
- Services: delete (a release removes a console Service if one exists).
- Pods
exec, forcorral ct console. coordination.k8s.ioLeases: get, list, create, update, delete.
- The image must be pullable from each node that can run a member.
- Members run
sleep infinityunless you set--init. Use--initfor an image with its own entrypoint that does not stop. A desktop session or an SSH server is an example. - The image is the only source of a member. Put the desktop software in the image, not in a member. Corral destroys all changes at release.
- Each member gets one
ReadWriteOncePVC of--disksize (default5Gi). - The StorageClass must make volumes on request (a dynamic provisioner). A release deletes and creates a PVC each time, so a slow provisioner makes a release slow.
- A PVC that stays in the
Terminatingstate blocks the new member. Corral waits up to 2 minutes for each delete.
- A member has the pod network of the cluster. Corral does not add a NetworkPolicy. Add one if members must not reach each other.
corral ct console <member>opens a terminal throughkubectl exec. A CT member has no VNC framebuffer. Browser desktop streams (WebRTC/Selkies) are a separate RFC-0001 phase.
- By default, Corral makes unprivileged members.
--privilegedgives the member containerprivileged: true. Use it only for trusted users. - Do not put tenant secrets, cloud credentials, or tokens in the image or in the pool ConfigMap. The ConfigMap is readable by all users with ConfigMap read access in the namespace. Use Kubernetes Secrets for secrets.
- The claim identity is the string that the caller gives. The CLI does not authenticate it. A broker in front of the pool (#227) must give a trusted identity. Tailscale or the OIDC gateway can supply this identity.
- Corral keeps the identity
corral-vdi/reconcilerfor the reconciler. A claim with this identity fails.