Cluster API
Cluster API (CAPI) turns entire Kubernetes clusters into ordinary declarative API objects: you write a Cluster and a MachineDeployment the way you write a Deployment, and controllers on one “management” cluster create, scale, upgrade, repair and eventually delete the real thing. It solves the problem that ruins every growing organisation — clusters built by hand or clicked into a console, each subtly different, each terrifying to upgrade, none reproducible — by turning clusters from pets into cattle, and “build me a cluster in Frankfurt” into a reviewable pull request.
Your school needs lots of identical treehouses. Right now every one is built by a different kid from memory, so no two have the same ladder and nobody dares fix the roof. Cluster API is a workshop with a blueprint drawer and a team of robots. Drop in a card saying “treehouse, 3 rooms, version 31” — the robots build it, check on it daily, and swap in a new plank when one rots. Want version 32? Cross out 31, write 32, and the robots quietly build new rooms and take the old ones away, one at a time, while everyone keeps playing. You never climb a ladder with a hammer again — you edit a card.
What Cluster API is and the problem it solves
☺ Like you’re 10: It’s Kubernetes used to build more Kubernetes — one cluster whose job is making and looking after all the other clusters.
Cluster API is a Kubernetes SIG Cluster Lifecycle sub-project — not a CLI that builds a cluster and walks away, but a set of controllers plus a family of custom resources. Install those controllers into a cluster (the management cluster) and from then on “create a cluster” means kubectl apply, because a cluster’s desired state is now YAML a controller reconciles forever. That single move drags provisioning into the same world as everything else here: GitOps can apply it, admission policy can vet it, RBAC can scope it, kubectl get can report on it.
The problem before CAPI
Before CAPI, cluster creation was solved twice and badly. The console path produced snowflakes: someone clicked through EKS or AKS, picked slightly different node sizes each time, and the knowledge lived in a wiki page that went stale in a fortnight. The scripted path — Terraform, Ansible, home-grown bash — was better but still run-to-completion: it made the world correct at 14:02 on Tuesday and then stopped caring. Nothing noticed a drifted node pool; nothing rebuilt a dead machine; upgrades were sweaty bespoke events repeated once per cluster. At three clusters that is annoying. At forty it is the entire job.
Clusters as cattle
CAPI applies the oldest lesson in cloud native — cattle, not pets — one level up the stack. A node is never patched in place; it is replaced. A cluster is never hand-tuned; it is re-derived from a template. Because the controllers reconcile continuously, drift is corrected without a human, a dead machine is rebuilt automatically, and an upgrade is a number you change in Git. The abstraction you trust for pods — Deployment owning ReplicaSets owning Pods — reappears as MachineDeployment → MachineSet → Machine. That symmetry is the design thesis.
CAPI does not invent a new provisioning language. It reuses the Kubernetes API machinery — CRDs, controllers, owner references, conditions, level-triggered reconciliation — and points it at machines and clusters. Everything you learned on Platform APIs & Operators applies unchanged. A cluster is just another custom resource with a controller behind it.
What Cluster API is deliberately not
Half of all CAPI confusion comes from expecting too much. CAPI creates a cluster that is empty and correct: a working control plane, joined nodes, a kubeconfig. It does not install a CNI, so a new cluster’s nodes sit NotReady until you give it one; nor ingress, cert-manager, the mesh or monitoring agents; nor does it back up etcd, which is Velero’s job. Filling a fresh cluster belongs to GitOps — knowing where that line sits saves hours of “why is my cluster broken?”
Where it fits in a platform
☺ Like you’re 10: CAPI sits at the very bottom — it makes the ground that everything else stands on.
In the layered platform model, Cluster API sits underneath everything: it produces the Kubernetes substrate itself. Every other tool on this site assumes a cluster exists; CAPI is how the cluster came to exist, and how it will be upgraded and eventually retired. In the reference architecture it lives on the hub of a hub-and-spoke fleet, alongside the fleet GitOps controllers.
Its neighbours
Beside CAPI sits Crossplane: Crossplane makes cloud resources into Kubernetes objects, CAPI makes clusters into Kubernetes objects, and mature platforms run both. Above it, Argo CD or Flux applies the CAPI manifests from Git so “add a region” is a merge, then installs add-ons once the new kubeconfig appears. Underneath, CLI IaC — Terraform or OpenTofu builds the accounts, the network backbone and the very first management cluster, since you cannot run CAPI before you have a cluster. Inside each workload cluster, Karpenter or Cluster Autoscaler handles minute-to-minute node scaling while CAPI owns the pool’s shape and version.
Why platform teams reach for it
Uniformity: one template stamps every cluster, so there are no snowflakes and any cluster can be rebuilt from Git. Upgrades: changing a version field is dramatically cheaper than forty artisanal maintenance windows, and it is rollback-able. Portability: the same object shape works on AWS, Azure, GCP, vSphere, OpenStack and bare metal with only the provider templates swapped — which is what makes a hybrid or on-prem platform tractable. The self-service payoff arrives when a team opens a PR adding a directory and receives a whole environment.
CNPE domain relevance
Be clear-eyed: Cluster API is not on the official CNPE tool list, so no task will ask you to type clusterctl. It is here because it is the industry standard for declarative cluster lifecycle, and because the concepts are squarely examinable — infrastructure as code, control-plane reconciliation, immutable infrastructure, fleet management, the management-versus-workload split. Expect those under Platform Architecture & Infrastructure (15% of the exam). The primary lesson here is Multi-Cluster & Fleet; the philosophy is on IaC & Control Planes; the blueprint is on the exam guide.
How it works — architecture, components and CRDs
☺ Like you’re 10: One boss cluster holds the blueprints and the robots. Each robot knows one job: make VMs, turn a VM into a node, or look after the control plane.
CAPI splits into a cloud-agnostic core and pluggable providers. Core understands Cluster, Machine, MachineSet, MachineDeployment, MachineHealthCheck and MachinePool, but knows nothing about EC2 or cloud-init; anything cloud-specific is delegated across a versioned contract to a provider that owns its own CRDs.
Management cluster vs workload clusters
The management cluster runs the CAPI controllers and stores the objects describing your fleet. Keep it small, hardened, HA, and free of business workloads — it can create and destroy every cluster you own and holds credentials to all of them, making it the highest-value target on the platform. A workload cluster is one it provisioned; it runs your applications and ideally knows nothing about CAPI. Crucially the hub must not be a runtime dependency of the spokes: if it is down, every workload cluster keeps serving traffic — you just cannot make fleet-level changes until it returns.
The management cluster usually starts as a temporary local kind cluster: install CAPI there, build the real management cluster with it, then pivot — move the CAPI objects onto the new cluster so it manages itself. That is what clusterctl move is for.
The three provider roles
Three provider types compose to make a node. Keeping them straight is the single most useful piece of CAPI knowledge.
| Provider role | Responsibility | Examples | CRDs it owns |
|---|---|---|---|
| Infrastructure | Creates the real substrate — VMs, networks, security groups, the control-plane load balancer | CAPA (AWS), CAPZ (Azure), CAPG (GCP), CAPV (vSphere), CAPO (OpenStack), Metal3 (bare metal), CAPD (Docker — for local testing) | AWSCluster, AWSMachineTemplate, DockerCluster, DockerMachineTemplate… |
| Bootstrap | Turns a blank VM into a Kubernetes node — generates the cloud-init / ignition that runs kubeadm init or kubeadm join | Kubeadm (CABPK, the default), Talos, RKE2, MicroK8s | KubeadmConfig, KubeadmConfigTemplate |
| Control plane | Owns the API-server/etcd tier as a unit: HA member management, certificate handling, rolling upgrades | KubeadmControlPlane (KCP, the default), plus managed variants like AWSManagedControlPlane (EKS) or AzureManagedControlPlane (AKS) | KubeadmControlPlane, KubeadmControlPlaneTemplate |
Two more categories exist: IPAM providers (addresses on on-prem networks) and add-on providers such as CAAPH, the Cluster API Add-on Provider for Helm. Core CAPI also ships ClusterResourceSet — a label-selected bundle of ConfigMaps/Secrets applied to matching new clusters, the classic way to get a CNI onto a cluster the moment it is born.
The object family, and why it looks familiar
The worker-node hierarchy deliberately mirrors the workload hierarchy you know.
| Workloads (pods) | CAPI (machines) | What it does |
|---|---|---|
Deployment | MachineDeployment | Declares a worker pool; owns MachineSets; a change to version or the template triggers a rolling replacement |
ReplicaSet | MachineSet | Keeps N Machines of one exact revision alive; you rarely touch it directly |
Pod | Machine | One node. Immutable: to change it you delete and recreate it |
| — | Cluster | The top-level object; points at an infrastructure object and a control-plane object via infrastructureRef and controlPlaneRef |
| liveness probe + restart | MachineHealthCheck | Watches Node conditions; deletes (and therefore replaces) Machines that stay unhealthy too long |
| node group / ASG | MachinePool | Delegates the pool to a cloud-native group (ASG, VMSS, MIG) instead of managing individual Machines |
| a Helm chart of the app | ClusterClass | A reusable cluster template; Clusters written with spec.topology are stamped from it |
ClusterClass and managed topologies
Writing six coupled objects per cluster does not scale. ClusterClass (a managed topology) is the answer: the platform team authors one ClusterClass holding the control-plane template, the infrastructure template and named worker classes, optionally with typed variables and patches. A consumer writes a tiny Cluster with a spec.topology block — class, version, replica counts — and the topology controller expands it into the full object graph and keeps it reconciled. Changing the golden image fleet-wide becomes one edit. It is the difference between handing developers a form and handing them a blank YAML file, and it needs the CLUSTER_TOPOLOGY feature enabled at clusterctl init time.
The resources you will actually write
☺ Like you’re 10: Here are the real cards you drop in the blueprint drawer.
Four manifests carry almost all of CAPI: the cluster, the control plane, a worker pool and a health check — then, once the fleet grows, the ClusterClass that replaces the first three.
A cluster and its control plane
The Cluster is deliberately thin — a join point naming a pod/service CIDR and pointing at two provider objects. The KubeadmControlPlane holds the interesting detail: how many members (always odd, because etcd needs a quorum), which version, and the kubeadm configuration stamped into each. The examples below use the long-established v1beta1 version of the CAPI API groups; newer Cluster API releases also serve a v1beta2 version of the same kinds with some fields reshaped, and infrastructure providers version their own group independently — so check kubectl api-resources and kubectl explain on your management cluster before copying any of this verbatim.
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: prod-eu-west
namespace: fleet
labels:
env: prod # used later by ClusterResourceSet / ApplicationSet
spec:
clusterNetwork:
pods: { cidrBlocks: ["192.168.0.0/16"] }
services: { cidrBlocks: ["10.128.0.0/12"] }
controlPlaneRef: # who owns api-server + etcd
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
name: prod-eu-west-cp
infrastructureRef: # who owns VPC, subnets, the CP load balancer
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSCluster
name: prod-eu-west
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
name: prod-eu-west-cp
namespace: fleet
spec:
replicas: 3 # ODD — stacked etcd quorum. 1 for dev, 3 or 5 for prod
version: v1.30.2 # bump this → rolling control-plane replacement
machineTemplate:
infrastructureRef:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSMachineTemplate
name: prod-eu-west-cp # templates are IMMUTABLE — create a new one to change
rolloutStrategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # add one new member before removing an old one
kubeadmConfigSpec:
clusterConfiguration:
apiServer:
extraArgs: # api-server flags, minus the leading "--"
audit-log-path: /var/log/audit.log
# NOTE: audit logging also needs a policy file on the node — ship it with
# kubeadmConfigSpec.files and mount it via apiServer.extraVolumes.
initConfiguration:
nodeRegistration:
kubeletExtraArgs: { cloud-provider: external }
joinConfiguration:
nodeRegistration:
kubeletExtraArgs: { cloud-provider: external }A worker pool, and a health check that repairs it
The MachineDeployment should feel like a Deployment; note that version lives on the machine template spec, not the top level. Beside it, a MachineHealthCheck turns “that node has been NotReady for ten minutes” into an automatic replacement — what makes a fleet self-repairing rather than merely self-describing.
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: prod-eu-west-md-0
namespace: fleet
spec:
clusterName: prod-eu-west
replicas: 6 # scale the pool like any Deployment
selector:
matchLabels: {} # left empty on purpose: the webhook defaults both the
# selector and the template labels to
# cluster.x-k8s.io/deployment-name: prod-eu-west-md-0
strategy:
type: RollingUpdate
rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
template:
spec:
clusterName: prod-eu-west
version: v1.30.2 # NOTE: on template.spec, not on spec
bootstrap:
configRef: # bootstrap provider: VM → Kubernetes node
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: prod-eu-west-md-0
infrastructureRef: # infrastructure provider: the actual VM
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSMachineTemplate
name: prod-eu-west-md-0
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineHealthCheck
metadata:
name: prod-eu-west-workers-unhealthy
namespace: fleet
spec:
clusterName: prod-eu-west
nodeStartupTimeout: 15m # grace period before a new node is judged
maxUnhealthy: 40% # CIRCUIT BREAKER: above this, stop remediating
selector:
matchLabels:
cluster.x-k8s.io/deployment-name: prod-eu-west-md-0
unhealthyConditions:
- type: Ready
status: "False"
timeout: 10m # NotReady for 10m → delete & replace the Machine
- type: Ready
status: Unknown
timeout: 10mA ClusterClass, and the tiny Cluster that uses it
Past two or three clusters, stop writing the objects above per cluster. Author one ClusterClass and let each cluster be a short topology — the moment CAPI becomes a genuine self-service API rather than a provisioning tool.
apiVersion: cluster.x-k8s.io/v1beta1
kind: ClusterClass
metadata:
name: standard-aws
namespace: fleet
spec:
controlPlane:
ref:
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlaneTemplate
name: standard-aws-cp
machineInfrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSMachineTemplate
name: standard-aws-cp
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSClusterTemplate
name: standard-aws
workers:
machineDeployments:
- class: general-purpose # a named worker CLASS consumers pick by name
template:
bootstrap:
ref:
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
name: standard-aws-worker
infrastructure:
ref:
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: AWSMachineTemplate
name: standard-aws-worker
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: team-blue
namespace: fleet
spec:
topology: # the whole cluster, in a dozen lines
class: standard-aws
version: v1.30.2 # ONE field upgrades control plane AND workers
controlPlane:
replicas: 3
workers:
machineDeployments:
- class: general-purpose
name: md-0
replicas: 4“I needed an isolated cluster for a load test. No ticket, and I never learned what a MachineSet is — I copied twelve lines into the fleet repo, changed the name and the replica count, and opened a PR. Twenty minutes after it merged a kubeconfig appeared in the vault path my team already uses. When the test finished I deleted the folder and the cluster went away. First time infrastructure ever felt like code to me.”
Day-to-day commands
☺ Like you’re 10: Most days you use plain kubectl. clusterctl is for setting up the workshop and moving it.
The mental model: clusterctl manages the management cluster (installing and upgrading providers, generating and moving manifests), while kubectl manages the fleet (creating, scaling and upgrading clusters, because they are just objects). Reaching for clusterctl to change a cluster usually means you are holding it wrong.
Bootstrapping a management cluster
# 0. Somewhere to run the controllers — a throwaway kind cluster is the classic bootstrap. kind create cluster --name capi-bootstrap # For the DOCKER provider only, that kind cluster must mount the host Docker socket, so create # it from a kind config with an extraMounts entry for /var/run/docker.sock instead of the line above. # 1. Enable the ClusterClass feature and initialise core + provider controllers. export CLUSTER_TOPOLOGY=true clusterctl init --infrastructure aws # core + kubeadm bootstrap + KCP + CAPA clusterctl init --infrastructure docker # CAPD: local clusters-in-containers, great for learning clusterctl init --infrastructure azure,vsphere # several infra providers on one management cluster # 2. What did that install, and is it healthy? kubectl get providers -A # core / bootstrap / control-plane / infrastructure + versions kubectl get pods -n capi-system # the core controller kubectl get pods -n capa-system # the AWS provider controller (one namespace per provider)
Creating, inspecting and reaching a workload cluster
# Generate manifests from a provider's template — then READ them before applying. clusterctl generate cluster prod-eu-west \ --kubernetes-version v1.30.2 \ --control-plane-machine-count=3 \ --worker-machine-count=6 \ --target-namespace fleet > clusters/prod-eu-west.yaml # In a GitOps platform you commit that file; Argo CD or Flux does the apply. kubectl apply -f clusters/prod-eu-west.yaml # Watch it come up. `describe` renders the whole object tree with conditions — the best CAPI command. clusterctl describe cluster prod-eu-west -n fleet kubectl get cluster,kubeadmcontrolplane,machinedeployment,machine -n fleet kubectl get machines -n fleet -o wide # phase: Provisioning → Provisioned → Running # The kubeconfig is a Secret named <cluster>-kubeconfig; clusterctl extracts it for you. clusterctl get kubeconfig prod-eu-west -n fleet > /tmp/prod-eu-west.kubeconfig kubectl --kubeconfig /tmp/prod-eu-west.kubeconfig get nodes # Nodes NotReady? Expected — CAPI installs no CNI. Install one (e.g. Cilium/Calico) or use a ClusterResourceSet.
Scaling, upgrading, pivoting and deleting
# Scale a worker pool — ordinary Kubernetes, no special tooling.
kubectl scale machinedeployment prod-eu-west-md-0 -n fleet --replicas=10
# Upgrade: change the version. Control plane FIRST, then workers, never more than one minor at a time.
kubectl patch kubeadmcontrolplane prod-eu-west-cp -n fleet --type=merge \
-p '{"spec":{"version":"v1.31.0"}}'
kubectl patch machinedeployment prod-eu-west-md-0 -n fleet --type=merge \
-p '{"spec":{"template":{"spec":{"version":"v1.31.0"}}}}'
# (With a ClusterClass topology, both of the above collapse to one edit of spec.topology.version.)
# Pause / resume a rollout, or roll a pool for a new image without a version change.
clusterctl alpha rollout pause machinedeployment/prod-eu-west-md-0 -n fleet
clusterctl alpha rollout resume machinedeployment/prod-eu-west-md-0 -n fleet
clusterctl alpha rollout restart machinedeployment/prod-eu-west-md-0 -n fleet
clusterctl alpha rollout undo machinedeployment/prod-eu-west-md-0 -n fleet
# Upgrade the CAPI providers themselves (NOT the clusters). Always plan first.
clusterctl upgrade plan
# Use the contract version that `upgrade plan` reports for the target release.
clusterctl upgrade apply --contract v1beta1
# PIVOT: move CAPI objects from the bootstrap cluster onto the real management cluster.
clusterctl move --to-kubeconfig=/tmp/mgmt.kubeconfig --namespace fleet
# Delete a cluster — ALWAYS via the Cluster object, so finalizers unwind the cloud resources in order.
kubectl delete cluster prod-eu-west -n fleetEverything above also lives in the command reference if you want it on one page.
Gotchas and failure modes
☺ Like you’re 10: Here are the ways the robot workshop goes wrong — mostly by being too keen to fix things.
Provider version skew and the contract
Core and providers talk across a versioned contract (for example v1beta1). Upgrade core without upgrading a provider that implements only an older contract and that provider quietly stops reconciling — clusters do not break, they stop responding to changes, which is worse because it is silent. Always run clusterctl upgrade plan, which prints the current and target contract per provider, then clusterctl upgrade apply. Never kubectl apply a provider’s release manifest by hand on a management cluster clusterctl manages — you will desynchronise its provider inventory.
clusterctl move — the pivot that eats your fleet
Moving CAPI objects between management clusters is the most dangerous routine operation. The target must already have the same providers at compatible versions installed, or the objects land with nothing to reconcile them. The move pauses reconciliation on the source and re-creates objects on the target; delete the source namespace too eagerly, or run move mid-provisioning, and you orphan cloud infrastructure no controller now owns. Safe sequence: quiesce changes, move with an explicit --namespace, verify on the target with clusterctl describe cluster, and only then tear down the source.
A MachineHealthCheck deletes unhealthy Machines so they get replaced — wonderful when one node has a bad disk, catastrophic when the cause is cluster-wide: a broken CNI, an expired certificate, a bad AMI. Every replacement also fails to become Ready, MHC deletes it too, and you have a churn loop that burns cloud spend and can shred the cluster. Use all three guards: maxUnhealthy (or unhealthyRange) so remediation stops once too many machines are sick — a circuit breaker, not a nicety; a nodeStartupTimeout generous enough that slow-booting nodes are not killed before they join; and the escape hatch — annotate a Machine with cluster.x-k8s.io/skip-remediation to hold it still while you debug. Land in this loop mid-incident and pausing the MHC comes before anything else; reliability & incidents has the wider pattern.
Immutable templates and the fields that surprise people
Infrastructure templates such as AWSMachineTemplate are immutable: you cannot edit the instance type or AMI in place. Create a new template with a new name and point the MachineDeployment or KubeadmControlPlane at it, triggering a rolling replacement — the same discipline as immutable container images. Two traps in the same family: version lives under spec.template.spec.version, and setting it at the top level does nothing; and an even replicas count on a KubeadmControlPlane is rejected by its validating webhook when etcd is stacked, because an even number buys no quorum advantage and a worse failure profile than an odd one — 1, 3 or 5, never 2 or 4.
The quieter operational traps
No CNI — a fresh cluster’s nodes stay NotReady until you install one; automate it with a ClusterResourceSet or CAAPH (see networking and Cilium). Skipping minors — kubeadm does one minor at a time, so 1.29 → 1.31 is two hops. Deleting the wrong object — always kubectl delete cluster; deleting Machines or infra objects directly orphans cloud resources. Quota — a maxSurge: 1 upgrade needs N+1 machines, and exhaustion appears as Machines stuck Provisioning with the real reason in the infrastructure object’s conditions, not the Cluster’s. PodDisruptionBudgets — CAPI drains before deleting, so an unsatisfiable PDB stalls an upgrade quietly and indefinitely (workload triage). When a cluster will not come up the ladder never changes: clusterctl describe cluster, then conditions on the failing object, then that provider’s logs — the troubleshooting playbook generalises it.
No cloud account needed — the Docker provider (CAPD) runs whole clusters as containers. With Docker running, create a kind cluster from a config file that adds an extraMounts entry for /var/run/docker.sock (CAPD drives Docker through that socket, so a plain kind create cluster will not do), then export CLUSTER_TOPOLOGY=true && clusterctl init --infrastructure docker. Create one with clusterctl generate cluster demo --flavor development --kubernetes-version v1.30.2 --worker-machine-count=2 | kubectl apply -f - and watch clusterctl describe cluster demo paint the tree. Pull the kubeconfig with clusterctl get kubeconfig demo; the nodes are NotReady — install a CNI and watch them flip. Then the payoff: kubectl delete machine on one worker and watch a replacement appear, and patch template.spec.version to see the rolling replacement live.
Alternatives and when to choose it
☺ Like you’re 10: There are other ways to get clusters. Here’s when each one is the sensible answer.
CAPI is powerful and not free: you now operate a management cluster and a set of controllers. Be honest about when a simpler tool wins.
The comparison that decides it
| Option | Model | Best when | Costs you |
|---|---|---|---|
| Cluster API | Kubernetes controllers reconciling Cluster/Machine objects, continuously | A fleet of clusters; hybrid or on-prem; you want one uniform, GitOps-able API across clouds and bare metal; declarative, rollback-able upgrades | You must run and upgrade a management cluster and its providers; provider maturity varies; a genuine learning curve |
| Terraform / OpenTofu (see IaC) | Run-to-completion CLI with a state file | A handful of clusters; you already run Terraform for accounts and networking; you need breadth across non-Kubernetes resources | No continuous reconciliation — drift waits for the next plan; upgrades are human-triggered runs; state file operations |
| Managed cluster services (EKS/AKS/GKE, via console or CLI) | Cloud provider owns the control plane | One cloud, a small number of clusters, no on-prem, and you want the least machinery possible | Snowflakes and click-ops unless wrapped in IaC; cloud-specific; no uniform API across environments |
CAPI with a managed control-plane provider (e.g. AWSManagedControlPlane) | CAPI objects front an EKS/AKS control plane | You want the CAPI API and GitOps workflow but would rather the cloud ran etcd | Fewer knobs than self-managed; feature parity lags the cloud’s own API |
| Crossplane | Control plane for cloud resources, including some cluster types | You are already standardising infrastructure self-service on Crossplane XRDs and want clusters in the same catalogue | Cluster lifecycle depth (rolling node upgrades, health remediation) is CAPI’s specialty, not Crossplane’s — many teams compose the two |
| kubeadm / kops by hand | Imperative CLI per cluster | Learning, or one long-lived cluster you truly hand-tend | Pets. No reconciliation, no fleet story, upgrades are events |
A practical rule
Count your clusters and your clouds. One or two on one cloud: use the managed service wrapped in Terraform and spend your energy elsewhere. Ten or more, or any mix of cloud and on-prem, or a hard requirement that clusters be reproducible from Git: adopt Cluster API — and adopt ClusterClass from day one so you never accumulate per-cluster YAML. In between, the deciding question is upgrades: if patching Kubernetes across the estate already costs a week per cycle, CAPI pays for itself immediately. See The Tool Landscape for how this sits among the platform’s other components.
Foxy: Upgrade day. I SSH’d into the nodes and ran the package manager. Only took four hours!
Benny: Four hours per cluster, Foxy. We have thirty-one. With CAPI I change spec.topology.version in a PR and the controller surges a new node, waits for Ready, drains the old one, deletes it — then does the next.
Professor Owl: And note why that’s cheap: nothing is patched. A machine is replaced. No node can drift from its template, because no node lives long enough to.
Gizmo: Love it. So I’ll set a MachineHealthCheck to nuke anything NotReady for sixty seconds. Maximum self-healing! 🤑
Timmy: …and the first time a CNI upgrade goes wrong, every replacement is also NotReady, and you’ve built a machine shredder. Set maxUnhealthy. It’s a circuit breaker, not a formality.
Dot: All I know is I asked for a load-test cluster in a pull request and got a kubeconfig. Nobody made me learn what a MachineSet is. Please keep it that way.
Exam relevance and going further
☺ Like you’re 10: You won’t be asked to run this on exam day — but you will be asked what it’s for. And you can’t look up its website.
Cluster API is not on the official CNPE tool list, so no task will hand you a management cluster and ask for a MachineDeployment. What is examinable is the thinking it represents: declarative infrastructure, continuous reconciliation versus one-shot apply, immutable infrastructure, clusters as cattle, the hub-and-spoke fleet shape — which surface in architecture-flavoured questions and anywhere the blueprint touches IaC.
The documentation allowlist — read this twice
During the CNPE the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, task-specific documentation explicitly linked in the exam’s Quick Reference box, and local man pages and /usr/share docs on the exam machine. Cluster API is a Kubernetes sub-project, but its documentation lives at cluster-api.sigs.k8s.io — a different domain from kubernetes.io — so it is not on the allowlist. Nothing about CAPI can be looked up. Anything you need must be in your head, and the manifests genuinely worth memorising are drilled on Know Cold.
⚖ CNPA vs CNPE — That allowlist is a CNPE mechanic — CNPE is hands-on, so it grants a narrow set of live lookups. CNPA is stricter still: it's a fully closed-book multiple-choice exam with zero external resources and zero lookups of any kind, so nothing here would be reachable there either. Still, the CAPI concepts above — management vs. workload clusters, immutable infrastructure, the provider roles — are exactly the kind of concept-level knowledge CNPA's closed-book recall tests.
What to be able to do without notes
Explain what CAPI is and why a platform would use it. Distinguish management from workload clusters and say why the hub must not be a runtime dependency of the spokes. Name the three provider roles with an example of each. Say what changing version does to the nodes — surge, wait, cordon, drain, delete, repeat — and why that is immutable infrastructure. Recite MachineDeployment → MachineSet → Machine and its pod-side analogue. Explain MachineHealthCheck and why maxUnhealthy exists. Know a fresh cluster has no CNI. And say where CAPI ends and GitOps begins.
Official resources for after the exam
Outside the exam the canonical source is the Cluster API Book — the Quick Start, ClusterClass and Troubleshooting chapters repay careful reading — plus github.com/kubernetes-sigs/cluster-api, cluster-api-aws.sigs.k8s.io and capz.sigs.k8s.io. For the concepts underneath, kubernetes.io on controllers is on the allowlist and is the best free revision. On this site, pair this page with Multi-Cluster & Fleet, IaC & Control Planes, Kubebuilder, scaling & scheduling, and the glossary.
1. What is the difference between a management cluster and a workload cluster, and why must the former not be a runtime dependency of the latter? 2. Name the three provider roles and one example of each. 3. Walk through exactly what CAPI does when you change version on a MachineDeployment. 4. Where does version live on a MachineDeployment, and what happens if you put it at the top level? 5. Your new cluster’s nodes are all NotReady and nothing is wrong with the machines. What did you forget? 6. What is a remediation loop, and which three settings guard against one? 7. What does clusterctl move do, and name two preconditions before running it. 8. During the exam, where can you look up the Cluster schema?
Check your answers
- The management cluster runs the CAPI controllers and holds the
Cluster/Machineobjects describing the fleet; a workload cluster is one it provisions, and runs your apps. The hub must not be a runtime dependency so that if it is down, every workload cluster keeps serving — you just cannot make fleet-level changes until it is back. - Infrastructure (CAPA/AWS, CAPZ/Azure, CAPG/GCP, vSphere, Metal3, CAPD/Docker) creates VMs and networks; bootstrap (Kubeadm/CABPK, also Talos or RKE2) turns a VM into a Kubernetes node via cloud-init; control plane (
KubeadmControlPlane, or managed variants likeAWSManagedControlPlane) owns the api-server/etcd tier and its rollout. - A rolling, immutable replacement: it provisions a new Machine at the new version (surging by
maxSurge), waits for the Node to become Ready, cordons and drains the old node respecting PodDisruptionBudgets, deletes the old Machine and its VM, then repeats for the next one. Nothing is upgraded in place. - Under
spec.template.spec.version. Setting it at the top level ofspecdoes nothing — a common and very quiet mistake. - The CNI. CAPI creates an empty cluster and deliberately installs no network plugin; nodes stay
NotReadyuntil one is applied — automate it with aClusterResourceSet, CAAPH, or your GitOps controller. - A
MachineHealthCheckdeleting machines that fail for a cluster-wide reason, so every replacement also fails and the fleet churns forever. Guards:maxUnhealthy(orunhealthyRange) as a circuit breaker, a generousnodeStartupTimeout, and thecluster.x-k8s.io/skip-remediationannotation as a manual hold. - It moves CAPI objects from one management cluster to another — the “pivot” that makes a new management cluster self-managing. Preconditions: the target must already have the same providers installed at compatible contract versions, and nothing should be mid-provisioning; back up first and verify with
clusterctl describe clusteron the target before dismantling the source. - You can’t.
cluster-api.sigs.k8s.iois notkubernetes.io, so it is off the allowlist (kubernetes.io/docs, kubernetes.io/blog, task-specific Quick Reference links, and local man//usr/sharedocs only). CAPI is not on the CNPE tool list anyway — know the concepts, and drill any memorised manifests on Know Cold.