Lessons
03 — Cluster provisioning

Lesson 3 — Cluster provisioning & lifecycle systems

2026-09-11

What interviewers are really probing

They are not asking whether you can click “Create cluster” in a cloud console. They want to know if you have owned the machine that creates machines: desired-state inventory → infra (VPC/subnets/IAM) → control plane bootstrap → node join → CNI/CSI/addons → ready-for-workload, with canary cells and health-gated waves so a bad MachineImage does not brick 100 clusters overnight.

I have watched a “simple AMI bump” strand half a region because node bootstrap raced ahead of the CNI DaemonSet and kubelets marked NotReady in a thundering herd. The fix was not more kubectl — it was a lifecycle controller with gates, pinned images, and a kill switch on the wave.

LayerOwnsTypical tools
Desired stateWhat a cell should be (SKU, CIDR, k8s version, addons)CMDB / ClusterClaim CR / Terraform modules
Infra planeVPC, subnets, IAM, LBs, KMS, disk encryptionTerraform + Atlantis / Crossplane
Cluster planeControl plane + MachinesCluster API (CAPI), managed (EKS/GKE/AKS), or kubeadm
Bootstrapcloud-init, kubelet join, runtimeMachineImage + bootstrap provider
Addons / day-2CNI, CSI, metrics, policy, ingressHelm/Argo CD / addon controllers
Fleet wavesOrder, health gates, rollbackTemporal / Argo Workflows / custom Lifecycle controller

Core diagram: provisioning pipeline

Cluster provisioning and lifecycle

Read left → right: you never “SSH and kubeadm init” at fleet scale. You declare a cell, merge a PR, let Terraform shape the cloud plumbing, let CAPI (or the managed control-plane API) reconcile Machines, then promote through waves only when health gates pass.

Inside one cell: bootstrap sequence

Interview trap: “cluster Ready” ≠ “safe for prod traffic.” CAPI Cluster.Status.ControlPlaneReady can be true while Cilium is still CrashLooping. Gate on your condition: CNI healthy, CSI provisioner up, NodeProblemDetector quiet, smoke probes green.

Cluster API shape (know the objects)

At hyperscale, even if you use EKS/GKE, the mental model is CAPI: a management cluster reconciles many workload clusters.

# Sketch — names vary by provider (capa/capg/capz); pattern is the point
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: use1-prod-cell-07
  labels:
    fleet.cell: "07"
    fleet.region: us-east-1
    fleet.env: prod
spec:
  clusterNetwork:
    pods:
      cidrBlocks: ["10.7.0.0/16"]   # from IPAM — never hand-pick in prod
    services:
      cidrBlocks: ["10.107.0.0/16"]
  topology:                         # ClusterClass path (preferred at scale)
    class: general-v3
    version: v1.29.4
    controlPlane:
      replicas: 3
    workers:
      machineDeployments:
        - name: general
          class: general-on-demand
          replicas: 120
        - name: spot-batch
          class: spot-batch
          replicas: 40
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AWSMachineTemplate   # or GCP/Azure equivalent
metadata:
  name: general-v3-2026-09-11
spec:
  template:
    spec:
      instanceType: m6i.2xlarge
      ami:
        id: ami-0abc…               # pinned — never :latest
      iamInstanceProfile: nodes-general
      subnet:
        filters: [{name: "tag:Tier", values: ["private"]}]
# Operator muscle memory — CAPI / lifecycle
kubectl --context mgmt get clusters.cluster.x-k8s.io -A
kubectl --context mgmt get machinedeployments,machines,machinesets -n fleet-use1
kubectl --context mgmt describe cluster use1-prod-cell-07 -n fleet-use1
 
# Conditions that matter
kubectl --context mgmt get cluster use1-prod-cell-07 -n fleet-use1 \
  -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.reason}{"\n"}{end}'
 
# Workload side — is it actually usable?
kubectl --context use1-prod-cell-07 get nodes -o wide
kubectl --context use1-prod-cell-07 get ds -n kube-system   # CNI/CSI must be Ready
kubectl --context use1-prod-cell-07 get --raw='/readyz?verbose' | head
 
# Managed control plane equivalents (same questions, vendor CLIs)
# aws eks describe-cluster --name use1-prod-cell-07
# gcloud container clusters describe use1-prod-cell-07 --region us-east1

Machine images & bootstrap: where outages hide

ConcernOperator rule
Pin imagesAMI/image family + digest/ID in Git. :latest is an incident generator.
Immutable bakePacker/image builder: kubelet, containerd, CNI bits, CIS baseline, cloud-init join template.
Join secretsShort-lived bootstrap tokens / TPM / cloud identity — never long-lived kubeconfigs on disk.
cloud-init racesDisk grow, sysctl, containerd start, kubelet join — order them; fail loud; journald to central logs.
Node registration--node-labels + taints from bootstrap (SKU, lifecycle=spot). Wrong labels = wrong scheduling forever.
Drain / replacePrefer replace Machine over mutate-in-place; CAPI MachineDeployment rollingUpdate with surge.
# Spot-check bootstrap on a bad node (break-glass)
sudo journalctl -u cloud-final -u kubelet -u containerd --since "30 min ago"
sudo cat /var/lib/cloud/instance/user-data.txt | head -80   # confirm join URL / labels
# kubelet NotReady? classic: CNI not installed, disk full, clock skew, API unreachable
kubectl describe node $NODE | sed -n '/Conditions:/,/Addresses:/p'

Lifecycle at fleet scale: waves, not big-bang

Product decisions you must own:

  1. What blocks a wave? Define SLOs in code (Temporal activity / Argo step), not Slack folklore.
  2. Rollback is a pin revert, not “fix the nodes.” Revert MachineImage / ClusterClass version; let controllers converge.
  3. Control-plane upgrades ≠ worker upgrades. Sequence them; never dual-jump skew outside supported skew policy.
  4. CIDR + IAM are slower than kube. Pre-provision network/IAM in a prior change window; keep cluster waves focused on CP/workers/addons.

Idiosyncrasies you only learn in production

  • Management cluster blast radius. One mgmt cluster owning 100 workload clusters is fine until its apiserver dies. HA the mgmt plane; consider regional mgmt shards; never put prod apps on the mgmt cluster.
  • ClusterClass beats copy-paste YAML. One class general-v3 with versioned templates; cells are topology instances. Drift = incident.
  • IPAM is a service. Overlapping pod CIDRs across cells kill multi-cluster east-west later. Allocate from a registry before Terraform runs.
  • Addon skew. Bumping k8s while Cilium/CSI charts lag is a classic canary-killer. Pin addon matrix per k8s minor.
  • Partial create is the common failure mode. VPC exists, CP up, workers fail join → orphan cost + confused humans. Controllers must be idempotent and delete-safe (finalizers that actually clean cloud resources).
  • “Terraform apply then kubectl apply” without a workflow engine does not scale. Temporal/Argo own retries, compensation, and human-in-the-loop freezes.
  • Managed CP still needs a lifecycle system. EKS/GKE hide etcd, not node AMI races, addon order, or wave gates.

Failures & fixes

SymptomLikely root causeFix / mitigation
New Machines loop Provisioning → Failed; no Node objectBad AMI / cloud-init; IAM/instance profile; SG blocks apiserver; subnet/IP exhaustionCAPI Machine events; cloud console instance console output; verify join endpoint + SG; check IPAM free IPs; pin last-known-good AMI and halt wave
Nodes Ready but pods NetworkPluginNotReadyCNI DaemonSet not rolled / wrong arch / not scheduled (taints)Gate on CNI Ready before declaring cell usable; tolerate bootstrap taints on CNI DS; fix chart vs k8s skew
Wave auto-promoted; 20% fleet NotReady after AMI bumpHealth gate too shallow (only ControlPlaneReady)Add CNI/CSI/NPD/probe gates; require soak time on canary; automatic pin revert
Cluster delete leaves ENIs/LBs/disksFinalizers / CCM not cleaning; Terraform state driftDeletion ordering: drain → delete Machines → delete Cluster → TF destroy; orphan sweeper job; alert on cost anomalies
Mgmt cluster API latency → all provisioning stuckShared etcd pressure from too many Machine objects / logsShard mgmt by region; APF on mgmt; prune failed Machines; don’t run apps on mgmt
EKS/UpdateClusterVersion stuck; nodegroups oldWorker AMI / launch template not updated in lockstepSeparate CP and worker waves; managed nodegroup release version pin; maxUnavailable budget
Spot pool never reaches desiredAZ capacity + interruption; oversubscribed ASGOn-demand floor; multi-AZ; diversify instance families; don’t gate prod readiness on spot alone
Join works in canary, fails in Wave 2Regional AMI replication lag / different subnet tags / IAM path typo in classPreflight: image available in all AZs/regions; validate ClusterClass render per region before wave

Security & hardening

Provisioning is when you bake immutable, least-privilege foundations. A bad AMI, long-lived join cred, or over-powered instance profile becomes every node’s attack surface for the life of the wave.

ControlOperator practice
CIS-baked MachineImagesGolden AMI/OS image with CIS baselines; pin digests; canary before fleet wave
Short-lived join credsBootstrap tokens / TPM / cloud identity — never permanent join secrets
IAM instance profilesNode role: pull images, describe instances, write logs — not * on S3 or IAM
No long-lived kubeconfigs on diskPrefer cloud identity / OIDC; break-glass kubeconfigs offline + rotated
Immutable infraNodes replaced, not SSH-patched; userdata is declarative and reviewed
# Spot-check: node instance profile should not be a god role
aws iam get-instance-profile --instance-profile-name nodes-general
aws iam list-attached-role-policies --role-name nodes-general
# Join tokens should expire (kubeadm-style); alert if bootstrap tokens linger
kubectl -n kube-system get secrets -l kubernetes.io/legacy-token-user=...

AI/ML fleets: GPU AMIs and device-plugin DaemonSets are part of the supply chain — sign/pin images, private registries only, and give node profiles least privilege to pull model/base images (no world-readable weight buckets). Checkpoint and artifact buckets get KMS + bucket IAM separate from general node roles.

Interview soundbites (steal these)

  1. “Provisioning is a control loop, not a script.” Desired state in Git; CAPI/managed APIs reconcile; humans merge PRs and watch gates.
  2. “ReadyForWorkload is my condition, not the vendor’s.” Control plane up is necessary, not sufficient — CNI/CSI/probes gate traffic.
  3. “Pin the MachineImage; promote with waves.” Canary → health gate → percentage waves → automatic halt/rollback.
  4. “Mgmt cluster is production.” HA it, shard it, keep it empty of app workloads — it is the plane that births the fleet.
  5. “Terraform shapes the plumbing; CAPI shapes the cell; Temporal/Argo shapes the rollout.” Mixing those layers in one shell script is how fleets freeze.

How this maps to the JD

JD themeProvisioning angle
100+ clusters / 10K+ nodesCells as cattle; wave lifecycle; inventory-driven desired state
K8s internalsBootstrap produces a healthy control plane (Lesson 2) per cell
Borg/Mesos-likeCell allocation + machine lifecycle ≈ Borg cell / Autopilot
Cloud networkingVPC/subnet/SG/CIDR allocated before join (next lessons deepen this)
Cilium / eBPFAddon order after kubelet Ready — part of the gate
SecurityIAM instance profiles, short-lived join creds, CIS-baked images
Terraform / AtlantisInfra PRs; no console clicks
Temporal / ArgoWave orchestration, retries, compensation

Watch (talks & demos)

Real-world talks and demos that map to this lesson — watch after the Failures & fixes table.

Don't Start With 500 Clusters: Patterns in Scaling Multi-Cluster Kubernetes — KubeCon

CAPI fleets for 100+ clusters: when multi-cluster is justified, GitOps lifecycle, and cutting “workload-ready” from weeks to a day.

Open on YouTube (opens in a new tab)

Migrate 700 Kubernetes Clusters to Cluster API with Zero Downtime — Mercedes-Benz (KubeCon)

Real migration of 700 on-prem clusters onto CAPI — waves, worker then control-plane, zero customer impact.

Open on YouTube (opens in a new tab)

References

Quick self-check

If someone asks: “How do you roll a new node AMI across 100 clusters without a fleet outage?” — answer pin the image in ClusterClass/MachineTemplate, canary 1–2 cells, health-gate on ReadyForWorkload (CNI/CSI/NPD/probes), promote in percentage waves with automatic halt + pin revert, keep CP and worker waves sequenced, and preflight image availability + IPAM + IAM per region. Tie it to Lesson 1 (cells as blast radius) and Lesson 2 (each cell’s control plane must stay healthy through the roll).


Previous: Lesson 2 — K8s control plane

Next up (Lesson 4): Borg/Mesos-like orchestration patterns.