Cluster API

Cluster API (CAPI) is a Kubernetes subproject — owned by SIG-Cluster-Lifecycle — that brings declarative, Kubernetes-style lifecycle management to Kubernetes clusters themselves (The Cluster API Book). The slogan is “Kubernetes managing Kubernetes”: instead of running cloud-provider CLIs, Terraform modules, or bespoke scripts to stand up a cluster, you express the cluster as a set of Custom ResourcesCluster, MachineDeployment, MachineSet, Machine, plus infrastructure- and bootstrap-provider resources — and a set of CAPI controllers, running in a management cluster, reconcile those resources into real, running clusters and nodes by calling the underlying cloud or bare-metal APIs. The same Kubernetes Control Loop Pattern that reconciles DeploymentPod is here reconciling Clusteran actual Kubernetes cluster. CAPI’s power is uniformity: one declarative API and one control loop create, scale, upgrade, and delete clusters identically across AWS, Azure, GCP, vSphere, OpenStack, bare metal (Metal3), and dozens more, because the cloud-specific logic is isolated behind a provider contract. It is the cluster-lifecycle layer that sits underneath the rest of Multi-Cluster Kubernetes — the thing that creates the clusters that Karmada then orchestrates workloads onto — and it is the engine inside many managed-cluster and fleet platforms. As of CAPI v1.13 (latest as of May 2026), the project’s current API version is v1beta2 across all groups; v1beta1 was deprecated in CAPI v1.11 and is scheduled for removal in v1.16 (~April 2027) (Cluster API — Version Support).

Mental Model

The mental model is a Kubernetes cluster whose CRDs are other clusters. You designate one long-lived cluster as the management cluster. It runs no application workloads; it runs CAPI’s controllers. You then kubectl apply a bundle of resources describing a workload cluster (confusingly named — it is the cluster that will eventually run your workloads; the management cluster only manages). CAPI’s controllers see those resources and, by calling the cloud’s API, bring a real cluster into existence: provision VMs, attach load balancers, bootstrap the first control-plane node with kubeadm, join the rest. The desired state is the CRDs; the observed state is the actual infrastructure; the controllers close the gap — exactly the K8s reconciliation pattern, one level up.

flowchart TB
    OP["Operator / GitOps"] -->|kubectl apply| MGMT
    subgraph MGMT["Management Cluster"]
        CC["Cluster API core controller"]
        CPC["Control-plane provider<br/>(KubeadmControlPlane)"]
        BP["Bootstrap provider<br/>(Kubeadm Bootstrap)"]
        IP["Infrastructure provider<br/>(CAPA / CAPZ / CAPV ...)"]
        subgraph CRDS["Workload-cluster CRDs"]
            CL["Cluster"]
            KCP["KubeadmControlPlane"]
            MD["MachineDeployment"]
            MS["MachineSet"]
            M["Machine x N"]
            INFRA["AWSCluster / AWSMachine ..."]
        end
    end
    CL --> CC
    KCP --> CPC
    MD --> CC
    MS --> CC
    M --> CC
    INFRA --> IP
    IP -->|"cloud API calls"| CLOUD["Cloud / bare-metal API<br/>(EC2, ARM, GCE, vSphere, Metal3)"]
    BP -->|"generates cloud-init / ignition"| CLOUD
    CPC -->|"kubeadm init / join"| WL
    CLOUD -->|"VMs, LBs, networks"| WL
    subgraph WL["Workload Cluster (created & managed by CAPI)"]
        WLCP["control plane node(s)"]
        WLN["worker node(s)"]
    end

What this shows. The management cluster is the only thing the operator touches directly. It holds the workload cluster’s CRDs and runs four kinds of controller. The core controller owns the generic objects (Cluster, MachineDeployment, MachineSet, Machine). The infrastructure provider (e.g., CAPA for AWS) owns the cloud-specific objects (AWSCluster, AWSMachine) and is the only component that calls the cloud’s API to make VMs and load balancers. The bootstrap provider generates the per-machine cloud-init/ignition data that turns a bare VM into a Kubernetes node. The control-plane provider (KubeadmControlPlane) owns the special task of bringing up and managing the control-plane nodes — running kubeadm init on the first, kubeadm join on the rest, and handling control-plane upgrades. The insight to extract: CAPI’s generic objects (Cluster, Machine) carry only provider-neutral fields; everything cloud-specific lives in a separate, provider-owned object that the generic object references. That indirection — the provider contract — is what makes one API work across every infrastructure.

Mechanical Walk-through

The bootstrap problem and the management cluster. CAPI runs in a Kubernetes cluster, so the first cluster has a chicken-and-egg problem. The standard resolution: create a temporary bootstrap cluster locally with kind, install CAPI into it (clusterctl init), use it to create the real management cluster, then clusterctl move the CAPI objects from the temporary kind cluster into the new management cluster and tear the kind cluster down. From then on the management cluster is self-sufficient and long-lived; it can even manage itself (pivot).

The provider model. CAPI deliberately ships almost no cloud logic in core. Functionality is delivered by providers, of three kinds (The Cluster API Book — Glossary):

  • Infrastructure providers create the underlying compute, networking, and load-balancing. Each implements the CAPI infrastructure contract for one platform: CAPA (AWS — AWSCluster, AWSMachine), CAPZ (Azure — AzureCluster, AzureMachine), CAPG (GCP — GCPCluster, GCPMachine), CAPV (vSphere), Metal3 (bare metal via Ironic), plus OpenStack, Hetzner, Equinix Metal, and many more. The infrastructure provider is the only component that calls the cloud API.
  • Bootstrap providers turn a provisioned VM into a Kubernetes node by generating its bootstrap configuration. The dominant one is the Kubeadm Bootstrap Provider (CABPK), which emits cloud-init/ignition that runs kubeadm join. Alternatives exist for Talos, microk8s, k3s, etc.
  • Control-plane providers manage the cluster’s control-plane nodes as a unit. The standard one is KubeadmControlPlane (KCP), which handles kubeadm init, control-plane node scaling, etcd membership, certificate rotation, and rolling control-plane upgrades.

A workload cluster is assembled by picking one provider of each kind. This separation means a new cloud needs only a new infrastructure provider — the generic Cluster/Machine logic, the kubeadm bootstrap, and the kubeadm control plane are all reused unchanged.

The resource hierarchy. The CRDs nest like the workload resources they are modeled on:

  • Cluster — the top-level object. It references an infrastructure cluster object (AWSCluster, …) for the cloud-level networking/LB, and a control-plane object (KubeadmControlPlane) for the control plane.
  • Machine — the declarative representation of one node. It references an infrastructure machine (AWSMachine, …) describing the VM and a bootstrap config (KubeadmConfig) describing how it joins. A Machine is roughly to a node what a Pod is to a container — the atomic, immutable unit. Upgrading a node means replacing its Machine, not mutating it.
  • MachineSet — maintains N identical Machines. Analogous to a ReplicaSet for Pods.
  • MachineDeployment — manages MachineSets to perform rolling updates of worker nodes (change the image or K8s version → a new MachineSet is created and old Machines are rolled out). Directly analogous to a Deployment managing ReplicaSets. This is how you upgrade or rescale a worker pool declaratively.
  • MachinePool — an alternative to MachineSet/MachineDeployment that delegates the group of machines to a cloud-native scaling primitive (AWS Auto Scaling Group, Azure VMSS) instead of managing each Machine individually.
  • KubeadmControlPlane — the control-plane analogue: manages the control-plane Machines, rolling them for upgrades while preserving etcd quorum.

Reconciliation in action — creating a cluster. Apply the bundle. The core controller sees the Cluster; the infrastructure provider reconciles the AWSCluster (creates the VPC, subnets, the API-server load balancer) and reports infrastructureReady. The KubeadmControlPlane controller then creates the first control-plane Machine; the infrastructure provider provisions its EC2 instance; the bootstrap provider supplies cloud-init that runs kubeadm init; once the first API server is up, KCP scales to the desired control-plane replica count, each new Machine running kubeadm join. In parallel the MachineDeployment for workers rolls out worker Machines, each bootstrapped with kubeadm join. CAPI writes a kubeconfig for the new cluster into a Secret in the management cluster. Upgrading the cluster is then purely declarative: bump the Kubernetes version in the KubeadmControlPlane and MachineDeployment specs, and CAPI performs a rolling replacement of Machines — new nodes at the new version, old nodes drained and deleted.

ClusterClass — templated clusters. Spelling out every CRD per cluster causes template sprawl across a fleet. ClusterClass is a blueprint: a ClusterClass object captures the topology — infrastructure template, control-plane template, worker MachineDeployment classes, variables — once. Individual clusters then become tiny Cluster objects with a .spec.topology block referencing the class and supplying variable values (version, replica counts, instance sizes). One change to the ClusterClass propagates to every cluster built from it — the fleet-management leverage point. ClusterClass remains an experimental (alpha) feature as of CAPI v1.13, gated by the ClusterTopology feature gate (enabled via the CLUSTER_TOPOLOGY=true env var when running clusterctl init) and requiring management-cluster Kubernetes ≥ 1.22 (The Cluster API Book — ClusterClass). Despite the “alpha” label it is widely used in production fleets — the gate signals contract evolution risk, not implementation immaturity.

The v1beta2 status redesign. A defining change shipped through the v1beta1 → v1beta2 transition is a wholesale rework of resource status to align with mainstream Kubernetes conventions (CAPI proposal 20240916). The old CAPI convention — a single Ready condition that summarized every other condition — diverged from how Deployment, Pod, and Node use Ready in upstream Kubernetes, where Ready has a specific local meaning (e.g., a Node’s Ready says it can host Pods) and lifecycle visibility comes from separate conditions and counters. The v1beta2 redesign: (1) Machine.Ready now means specifically “this Machine can host workloads,” matching Node.Ready; (2) a new Available condition surfaces operational availability across lifecycle operations; (3) the legacy FailureReason/FailureMessage fields are dropped — terminal failures become conditions like the rest of the Kubernetes API; (4) higher-level objects (MachineSet, MachineDeployment, MachinePool, KubeadmControlPlane, Cluster) grow ReadyReplicas/AvailableReplicas/UpToDateReplicas counters and ScalingUp/ScalingDown/Remediating conditions, mirroring Deployment.status; (5) Cluster.status gains a RemoteConnectionProbe condition that surfaces whether the management cluster can still reach the workload cluster’s API. The migration is staged: v1beta1 carries the new fields under a temporary V1Beta2 substruct so consumers can adopt incrementally; v1beta2 makes them the canonical shape; v1beta1 is scheduled for removal in CAPI v1.16 (~April 2027) (Cluster API — Version Support).

Configuration / API Surface

A minimal CAPI workload-cluster definition for AWS (CAPA), abbreviated. The top-level Cluster ties together a networking object and a control-plane object:

apiVersion: cluster.x-k8s.io/v1beta2          # core CAPI as of v1.13 (May 2026)
kind: Cluster
metadata:
  name: prod-us-east
  namespace: clusters
spec:
  clusterNetwork:
    pods:
      cidrBlocks: ["192.168.0.0/16"]          # Pod CIDR for the new cluster
  infrastructureRef:                          # -> the cloud-specific networking object
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
    kind: AWSCluster
    name: prod-us-east
  controlPlaneRef:                            # -> the control-plane provider object
    apiVersion: controlplane.cluster.x-k8s.io/v1beta2
    kind: KubeadmControlPlane
    name: prod-us-east-cp
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: KubeadmControlPlane
metadata:
  name: prod-us-east-cp
  namespace: clusters
spec:
  replicas: 3                                 # 3 control-plane nodes -> etcd quorum of 2
  version: v1.32.0                            # bump this -> CAPI rolls the control plane
  machineTemplate:
    infrastructureRef:                        # -> template for control-plane VMs
      apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
      kind: AWSMachineTemplate
      name: prod-us-east-cp
  kubeadmConfigSpec:
    initConfiguration: {}                     # kubeadm init / join settings
---
apiVersion: cluster.x-k8s.io/v1beta2
kind: MachineDeployment                       # the WORKER pool — like a Deployment for nodes
metadata:
  name: prod-us-east-md-0
  namespace: clusters
spec:
  clusterName: prod-us-east
  replicas: 5                                 # 5 worker nodes
  template:
    spec:
      version: v1.32.0
      bootstrap:                              # -> bootstrap provider config
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1beta2
          kind: KubeadmConfigTemplate
          name: prod-us-east-md-0
      infrastructureRef:                      # -> template for worker VMs
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
        kind: AWSMachineTemplate
        name: prod-us-east-md-0

Line-by-line: the Cluster carries only provider-neutral fields (clusterNetwork) and referencesinfrastructureRef points at an AWSCluster (CAPA-owned, holding region/VPC/LB config), controlPlaneRef at a KubeadmControlPlane. That reference-to-a-provider-object pattern is the provider contract in YAML. The KubeadmControlPlane declares replicas: 3 (the control plane is sized for etcd quorum — see Kubernetes Cluster Architecture) and a version; changing version is the entire cluster-upgrade operation — CAPI rolls the control-plane Machines to the new version. The MachineDeployment is the worker pool: replicas: 5 worker nodes, each built from an AWSMachineTemplate (the VM shape) and a KubeadmConfigTemplate (how it joins). Scaling workers is kubectl scale machinedeployment; upgrading them is bumping template.spec.version. The *Template objects are immutable-by-convention — to change machine shape you create a new template and point the deployment at it, triggering a roll.

With ClusterClass, all of the above collapses into a reusable ClusterClass plus a tiny per-cluster Cluster with a .spec.topology referencing the class and overriding only version and replica counts.

Failure Modes

Management-cluster loss. If the management cluster dies, the workload clusters keep running — they are fully independent Kubernetes clusters — but you lose the ability to scale, upgrade, repair, or delete them declaratively until the management cluster is restored. The management cluster must therefore be backed up (its etcd, or clusterctl move to a standby) and ideally itself HA. Some operators run the management cluster as a CAPI-managed cluster for self-healing.

Provider/contract version skew. CAPI core and each provider version against a contract. Mixing a core version with a provider built for a different contract revision causes reconciliation to silently stall or error. clusterctl manages compatible version sets; hand-installing providers invites skew.

Cloud-credential / quota failures. The infrastructure provider needs cloud credentials with broad permissions (create VMs, LBs, security groups). Missing IAM permissions, or hitting a cloud quota (instance limit, EIP limit), leaves Machines stuck in Provisioning — visible only in the infrastructure object’s status and events, easy to miss.

Stuck deletions / orphaned cloud resources. Machine and Cluster deletion is finalizer-gated so cloud resources are cleaned up in order. If a provider controller is down, or a cloud resource was modified out-of-band, deletion can hang on a finalizer, and aborting it crudely (force-removing finalizers) can orphan real cloud resources — VMs and load balancers that keep billing with no CRD tracking them.

Bootstrap-data failures. If the bootstrap provider’s cloud-init/ignition is wrong (bad kubeadm config, unreachable API-server endpoint, image mismatch), the VM provisions but never becomes a Ready node. The symptom is a Machine stuck between Provisioned and Running; diagnosis means getting onto the VM and reading the cloud-init logs.

Control-plane roll breaking etcd quorum. A KubeadmControlPlane upgrade rolls control-plane Machines one at a time, managing etcd membership as it goes. A misconfiguration, an unhealthy old member, or too-aggressive settings can drop etcd below quorum mid-roll — the most dangerous CAPI failure, since it can wedge the workload cluster’s own control plane.

Alternatives and When to Choose Them

Managed Kubernetes (Amazon EKS, Google GKE, Azure AKS). The cloud provider runs the control plane; you never touch CAPI. For a single cloud, managed K8s is simpler and the MOC’s default recommendation. CAPI earns its place when you need a uniform cluster API across multiple clouds or bare metal, or when you are building a managed-cluster product yourself. Notably, CAPI can also manage managed clusters — there are managed-control-plane variants (AWSManagedControlPlane for EKS, equivalents for AKS/GKE) so a fleet of EKS clusters can still be declared as CAPI resources.

Self-Managed Kubernetes tooling — kubeadm, kOps, kubespray. kubeadm is the imperative bootstrap primitive CAPI’s Kubeadm providers wrap; kOps and kubespray are higher-level cluster installers. They create clusters but are not declarative reconcilers — there is no controller continuously enforcing desired state, no kubectl-native upgrade, no fleet API. Choose them for one-off or small-scale clusters; choose CAPI when you want clusters to be managed objects with continuous reconciliation.

Terraform / OpenTofu / Pulumi / Crossplane. General infrastructure-as-code can also stand up clusters. The difference is who reconciles: Terraform reconciles when you run apply; CAPI reconciles continuously via in-cluster controllers, and self-heals (a deleted node is recreated). Crossplane is closer in spirit — Kubernetes-native, controller-driven — and the two are sometimes combined (Crossplane for surrounding cloud resources, CAPI for the clusters). Choose CAPI specifically when cluster lifecycle — provisioning, scaling, K8s-version upgrades, repair — should be a self-healing control loop.

Production Notes

Cluster API is a mature SIG-Cluster-Lifecycle subproject (kubernetes-sigs/cluster-api) and is the engine inside many fleet and managed-cluster platforms — VMware Tanzu, Spectro Cloud Palette, Giant Swarm, Kubermatic, Red Hat’s Hypershift-adjacent tooling, and others build their cluster lifecycle on CAPI rather than reinventing it (Spectro Cloud — Getting started with CAPI). Choosing CAPI is often an implicit choice — you adopt a platform and CAPI is underneath.

The canonical fleet pattern (SuperOrbital — Fleets of Clusters with CAPA) is CAPI + GitOps: store the ClusterClass and per-cluster Cluster objects in Git, and let ArgoCD or Flux apply them to the management cluster. Creating a new cluster becomes a pull request; the fleet’s entire topology is version-controlled and auditable. Layered above, Karmada or an MCS implementation handles workloads across the clusters CAPI created — this is the clean separation of concerns in Multi-Cluster Kubernetes: CAPI owns cluster lifecycle, Karmada owns workload placement, GitOps owns delivery.

ClusterClass is the production multiplier: without it, a fleet of 50 clusters is 50 hand-maintained CRD bundles drifting apart; with it, it is one blueprint and 50 thin variable sets, and a topology change is a single edit. Most serious CAPI deployments standardize on ClusterClass early.

Version support discipline. CAPI’s published support window is two minor releases standard (N, N-1) plus a maintenance-mode tail at N-2, totaling roughly 12 months per minor (8 months standard + 4 months maintenance) (Cluster API — Version Support). As of May 2026 that means v1.13 (standard), v1.12 (standard), v1.11 (maintenance, EOL when v1.14 releases). Skip upgrades are bounded at n-3 (e.g., v1.10 → v1.13 is supported; v1.9 → v1.13 is not); downgrades are unsupported. Providers version independently against the CAPI contract — clusterctl init/upgrade is the supported path because it negotiates compatible core/provider sets; hand-installing manifests across versions is how skew bugs are born. Running a fleet means budgeting roughly one CAPI bump per quarter, ideally GitOps-driven.

See Also