Skip to content

Your first cluster

With the provider installed, you’ll now create a small but real cluster: one control-plane node and one worker, in a single whitesky.cloud location. We’ll go slowly and show the output to expect at each step.

What you’ll do

  1. Create the credential Secret · 2. Render a manifest · 3. Apply & watch · 4. (next page) get the kubeconfig and deploy an app.

1. Create the credential Secret

Each VCOCluster points at a Kubernetes Secret holding the whitesky.cloud credential used to provision and run the cluster. Create it in the management cluster, in the namespace your cluster will live in (default here):

kubectl create secret generic my-cluster-vco-credential \
  --from-literal=jwt="<your self-refreshing whitesky.cloud JWT>" \
  --from-literal=customer_id="<your customer id>"

Keys:

  • jwt, customer_id — required.
  • portal_url, iam_refresh_url — optional per-cluster overrides of the manager’s instance defaults (only needed if this cluster targets a different whitesky.cloud portal than the manager’s default).

Treat the JWT as a live secret

It provisions and controls real infrastructure. Don’t commit it; rotate it when no longer needed. CAPVCO reads it from this Secret — it never logs or copies it elsewhere except the controlled injection into the workload cluster for the CSI driver.

The Secret name here (my-cluster-vco-credential) follows the template default ${CLUSTER_NAME}-vco-credential. Keep that convention and the template finds it automatically.

2. Render a cluster manifest

You don’t hand-write the objects — you render a template with clusterctl generate cluster. Start with the base template:

VCO_LOCATION=<your-location> \
VCO_IMAGE_ID=<stock-ubuntu-image-id-in-that-location> \
VCO_EXTNET_ID=<external-network-id-in-that-location> \
clusterctl generate cluster my-cluster \
  --kubernetes-version v1.35.8 \
  --infrastructure vco \
  --worker-machine-count 1 > my-cluster.yaml

Three things that silently bite if you skip them

  • VCO_IMAGE_ID is required — there is no default. vm-image ids are location-specific (an id from one location doesn't exist in another), so no portable default is possible. Find the stock-Ubuntu image id for your location in the whitesky.cloud portal (or the VCO API); see pre-baked images for the same lookup. Omit it and clusterctl errors with value for variables [VCO_IMAGE_ID] is not set — which is the point: it fails loudly instead of booting the wrong image.
  • VCO_EXTNET_ID is required — there is no default. It selects the external network your cluster's cloudspace attaches to (the source of its public IP / egress). If your customer has more than one external network, letting the backend pick its default silently attaches the cluster to the wrong one — so you must choose. List your external networks in the portal (or the VCO API) and pass the id for the network this cluster should use. Omit it and clusterctl errors with value for variables [VCO_EXTNET_ID] is not set. (Geo clusters set this per-location via spec.geo.locationExternalNetworks instead — see geo-redundancy.)
  • --worker-machine-count defaults to 0. Leave it off and you get a control plane with zero workers (nothing fails — you just silently get no worker node). Always pass --worker-machine-count 1 (or more). This is a clusterctl flag; it overrides the template, so setting WORKER_MACHINE_COUNT as an env var has no effect.

Open my-cluster.yaml and skim it — you’ll recognise the objects from Core concepts: a Cluster, a VCOCluster, a KubeadmControlPlane + its VCOMachineTemplate, and a MachineDeployment + its template and a MachineHealthCheck.

Common knobs

Override any of these on the command line:

Variable Default Meaning
CONTROL_PLANE_MACHINE_COUNT 1 Control-plane replicas (use 3 for HA)
VCO_CP_VCPUS / VCO_CP_MEMORY_MIB 2 / 4096 Control-plane VM size
VCO_MD_VCPUS / VCO_MD_MEMORY_MIB 2 / 4096 Worker VM size
POD_CIDR 10.244.0.0/16 Pod network — must not overlap the cloudspace subnet

(VCO_IMAGE_ID and the worker count aren't in this table on purpose — see the warning above: the image id is required, and the worker count is the --worker-machine-count flag.) The full list and every other template is in the configuration reference.

Base vs pre-baked image

The base template uses a stock Ubuntu image and installs containerd + Kubernetes on first boot (simple, but slower and needs egress). For fast, deterministic, offline-friendly boots, use a pre-baked image and the cluster-template-prebaked.yaml template. Start with the base template here; switch later.

3. Apply and watch it build

kubectl apply -f my-cluster.yaml
cluster.cluster.x-k8s.io/my-cluster created
vcocluster.infrastructure.cluster.x-k8s.io/my-cluster created
kubeadmcontrolplane.controlplane.cluster.x-k8s.io/my-cluster-control-plane created
vcomachinetemplate.infrastructure.cluster.x-k8s.io/my-cluster-control-plane created
machinedeployment.cluster.x-k8s.io/my-cluster-md-0 created
...

Watch it converge — CAPVCO reports real whitesky.cloud state as conditions:

clusterctl describe cluster my-cluster

You’ll see the VCOCluster move through CloudspaceReady → EndpointReady → Ready as it creates the cloudspace, publishes the control-plane load balancer, and the first control-plane node boots and initialises.

Track the machines directly:

kubectl get machines -l cluster.x-k8s.io/cluster-name=my-cluster -w
NAME                             CLUSTER      PHASE          AGE   VERSION
my-cluster-control-plane-abcde   my-cluster   Provisioning   30s   v1.35.8
my-cluster-md-0-xxxxx-yyyyy      my-cluster   Pending        30s   v1.35.8

Machines go Pending → Provisioning → Provisioned → Running. A first cluster on a stock image typically reaches a running control plane in a few minutes; the worker joins shortly after.

Stuck in Provisioning for a while?

On the stock image the node installs containerd + Kubernetes on first boot, which takes a few minutes and needs egress to pkgs.k8s.io. If it never progresses, check the troubleshooting guide — the usual causes are a bad credential, an unreachable location, or a POD_CIDR that overlaps the cloudspace network.

Nodes are NotReady until the CNI lands — and that's automatic

A brand-new node has no pod network, so for a short while it registers NotReady with container runtime network not ready: cni plugin not initialized. This is expected — not a failed deploy. You don't fix it by hand: because you applied the CNI ClusterResourceSet when you installed the provider, the management cluster pushes Calico into the workload cluster automatically as soon as its API server is reachable, and the node flips to Ready within a minute or so.

If nodes are still NotReady after several minutes, the CNI add-on almost certainly wasn't applied to the management cluster — go back to that step. (You'll confirm Ready nodes on the next page.)

When the control-plane machine is Running with a NODE NAME, the API server is up. Time to access it and deploy something.


Next → Access it & deploy an app.