Architecture¶
How CAPVCO is put together, for operators and contributors who want the model beneath the guides. If you just want to use a feature, the guides are the place; this page explains why things are shaped the way they are.
The big picture¶
flowchart LR
subgraph mgmt["Management cluster"]
capi["CAPI core + kubeadm providers"]
capvco["CAPVCO manager"]
creds[("per-cluster<br/>credential Secret")]
end
subgraph vco["whitesky.cloud"]
cs["Cloudspace (per cluster)"]
cp["Control-plane VMs"]
wk["Worker VMs"]
lb["Control-plane LB / endpoint"]
end
capi -->|Cluster / KCP / MachineDeployment| capvco
capvco -->|reads| creds
capvco -->|"REST /api/1 + /alpha"| vco
cs --- cp
cs --- wk
lb --> cp
The management cluster holds Cluster API + CAPVCO. CAPVCO watches the CAPI objects
(Cluster, KubeadmControlPlane, MachineDeployment and its own VCOCluster / VCOMachine) and
drives the whitesky.cloud REST API to realise them. It holds no standing credential — it resolves
a per-cluster credential from the VCOCluster's credentialSecretRef for every backend call.
Two objects, two controllers¶
| CRD | Controller ensures |
|---|---|
VCOCluster |
The cloudspace(s), the VPN mesh (geo), the registry (air-gapped), the control-plane endpoint/LB, and then injects add-on credentials + opt-in backup / image-publishing / drift detection. |
VCOMachine |
One VM: creates/deletes it, attaches disks (data + etcd), registers it in the control-plane LB pool (control-plane machines), grows disks, and reports addresses + conditions. |
VCOClusterTemplate and VCOMachineTemplate are the template shapes CAPI's Cluster /
KubeadmControlPlane / MachineDeployment stamp instances from.
Cloudspace per cluster¶
Each cluster gets its own whitesky.cloud cloudspace — network + tenancy isolation. The control-plane endpoint is published on it: by default an ingress L4 load balancer (HA-capable, ≥ 3 control-plane nodes), or a port-forward (single control plane). For geo clusters there is one cloudspace per location, meshed by site-to-site VPN, with a health-aware DNS control-plane endpoint instead of a single LB.
The endpoint (LB + public IP) is allocated before any node exists, so control-plane certificates
can be minted against a stable controlPlaneEndpoint from the very first boot.
Machines and provider-id¶
A VCOMachine maps 1:1 to a VM. The bootstrap userdata (from the kubeadm bootstrap provider) is
submitted as whitesky.cloud userdata with the CAPI provider-id
vco://<cloudspaceId>/<machineUID> merged in, so the node registers with exactly the provider-id the
controller expects.
Node ↔ VM identity for CSI is separate: the CSI driver resolves its VM from the SMBIOS
product_uuid, independent of the provider-id. So storage works regardless of the provider-id
scheme.
Credential model¶
CAPVCO follows the CAPI cluster-identity pattern: the operator supplies a distinct, narrowly-
scoped credential per cluster (VCOCluster.spec.credentialSecretRef), and the controller builds a
per-cluster API client from it (cached by the Secret's resourceVersion). The manager mints nothing
and holds no standing credential.
A few operations (VM-image publishing, the registry TLS cert) are operator-privileged and take a separate operator credential. The full model — keys, rotation, operator-privileged ops, and the credentials injected into workload clusters — is in Configuration.
Add-ons via ClusterResourceSet¶
Cluster-scoped features are delivered as static manifests through ClusterResourceSet (CRS),
selected by labels on the Cluster, with per-cluster Secrets injected directly by the controller
(a CRS can't template Secret data):
| Label | Add-on |
|---|---|
…/cni |
CNI (Calico; veth_mtu pinned to 1350 so any cluster is geo/VPN-safe) |
…/csi |
whitesky.cloud CSI driver + StorageClasses (+ injected csi-driver-secret) |
…/csi-encryption |
Key-backend config for encrypted volumes |
…/ccm |
Cloud-controller-manager (node init + Service-LB) |
…/service-lb |
Standalone Service-type=LoadBalancer controller |
…/external-dns |
ExternalDNS + the whitesky.cloud DNS webhook |
…/apiserver-proxy |
Internal-API-path proxy (geo) |
The full label→value→component mapping is in Configuration.
Reconciliation order¶
The VCOCluster controller ensures things in an order that respects dependencies:
flowchart LR
cs["cloudspace(s)"] --> mesh["mesh (geo)"] --> reg["registry (air-gapped)"] --> ep["control-plane endpoint"] --> add["add-on creds + backup / images / drift"]
VCOMachine reconciliation, in turn, creates the VM (stopping first if an etcd disk
must be attached pre-boot), registers control-plane machines in the LB pool by API-server health,
and evaluates disk growth. Everything is idempotent (adopt-by-name / adopt-by-address) and
tolerant of 404 on delete, so retries and partial failures converge rather than duplicate or wedge.
Geo provisioning is all-or-nothing — on a hard failure the controller tears down everything it created, so you never get a half-built geo cluster.
Status and conditions¶
Both CRDs report real backend state back as conditions, so clusterctl describe cluster tells the
truth. Notable VCOCluster conditions: CloudspaceReady, MeshReady (geo), EndpointReady,
BackupReady, KeyEscrowed (amber until you acknowledge a provider-generated backup key),
DriftDetected (informational). VCOMachine reports readiness, vmId, addresses, and disk state.
Where to go next¶
- The exact CRD fields: CRD API reference (generated from the Go types).
- The honest edges: Known limitations.
- Design rationale in depth:
cluster-api-plan.mdin the repository — a per-milestone design and validation narrative.