Static infrastructure sizing is inherently inefficient. Over-provisioning compute capacity to survive peak traffic spikes wastes substantial cloud expenditure during idle hours. Conversely, under-provisioning risks capacity starvation: incoming workloads sit stalled in Pending with scheduling errors when user demand surges.
In cloud environments, engineers often reach for cloud-native autoscaling mechanisms like AWS Auto Scaling Groups (ASGs) or Azure Virtual Machine Scale Sets (VMSS). However, cloud autoscalers operate strictly on hypervisor-level metrics (e.g., average CPU or memory utilization of the virtual machine). This creates a fatal blind spot in Kubernetes: a cluster may have 80% idle hypervisor CPU, yet a new pod requiring 32 GB of memory or an attached GPU remains unschedulable due to memory fragmentation, specific node affinity rules, taints, or storage topology zone constraints.
The Kubernetes Cluster Autoscaler (CA) solves this by observing the cluster from the inside out: it monitors the kube-scheduler queue for unschedulable pods rather than watching VM metrics.
When integrated with Cluster API through the Cluster API cloud provider (--cloud-provider=clusterapi), Cluster Autoscaler brings cross-cloud, declarative autoscaling to any infrastructure provider without vendor lock-in.
1. The Separation of Concerns: Sizing vs. Provisioning
The foundational mental model for autoscaling in Cluster API is the strict architectural boundary between capacity calculation and infrastructure reconciliation:
flowchart TD
subgraph WorkloadCluster["Workload Cluster (Workload & Scheduling Plane)"]
PENDING["Pending Pods<br/>(Reason: Unschedulable)"] --> CA["Cluster Autoscaler<br/>(Evaluates Scheduling Feasibility)"]
UNUSED["Underutilized Nodes<br/>(Request sum < 50% for 10m)"] --> CA
SCHED["kube-scheduler"] -.-> PENDING
end
subgraph ManagementCluster["Management Cluster (Declarative Infrastructure Plane)"]
CA -->|"1. Mutates spec.replicas<br/>(over Management API)"| MD["MachineDeployment<br/>(Annotated with min/max bounds)"]
MD -->|"2. Reconciles replica drift"| MS["MachineSet"]
MS -->|"3. Manages Machine lifecycles"| M["Machines"]
M -->|"4. Launches cloud instances"| INFRA["Infrastructure Provider<br/>(CAPA, CAPZ, CAPV)"]
end
INFRA -.->|"5. New Nodes join cluster"| WorkloadCluster
To reason about autoscaling, anchor on what each system owns:
| Component | Operational Domain | Primary Responsibility | Input Signal |
|---|---|---|---|
kube-scheduler | Workload Cluster | Assigns pending pods to existing nodes with matching resources | Pod resource requests, taints, tolerations, affinities |
Cluster Autoscaler | Workload / Management | Calculates how many nodes of a specific shape are needed | Pending unschedulable pods, underutilized nodes |
Cluster API | Management Cluster | Executes the asynchronous machine lifecycle (VM creation, cloud-init, node join, graceful drain) | MachineDeployment.spec.replicas drift |
| Infrastructure Provider | Cloud / Hypervisor | Interfaces with cloud APIs to allocate or destroy compute, disks, and network interfaces | InfrastructureMachine custom resources |
Cluster Autoscaler never provisions a virtual machine directly. It simply patches the desired spec.replicas on a MachineDeployment. Cluster API then executes the concrete infrastructure lifecycle.
2. Deployment Topologies & API Access
Cluster Autoscaler requires access to two distinct Kubernetes APIs:
- Workload Cluster API: To watch
Podscheduling states,Noderesource requests, and evaluate eviction feasibility. - Management Cluster API: To discover scalable
MachineDeploymentresources and mutate theirspec.replicas.
Depending on your security and operational requirements, Cluster Autoscaler can be deployed in two topologies:
flowchart LR
subgraph InWorkload["Topology A: Autonomous (In-Workload Cluster)"]
W_CA["Cluster Autoscaler Pod"]
W_SA["In-Cluster ServiceAccount<br/>(Watches Workload Pods/Nodes)"]
W_SEC["Kubeconfig Secret<br/>(Talks to Management Cluster API)"]
W_CA --> W_SA
W_CA --> W_SEC
end
subgraph InManagement["Topology B: Centralized (In-Management Cluster)"]
M_CA["Cluster Autoscaler Pod"]
M_SA["In-Cluster ServiceAccount<br/>(Mutates local CAPI CRDs)"]
M_SEC["Target Kubeconfig Secret<br/>(Watches remote Workload Cluster)"]
M_CA --> M_SA
M_CA --> M_SEC
end
Auto-Discovery by Cluster Name
Cluster Autoscaler discovers scalable worker pools dynamically without hardcoding static resource names. When started with --node-group-auto-discovery, it targets only the MachineDeployments that belong to the specific cluster:
cluster-autoscaler \
--cloud-provider=clusterapi \
--node-group-auto-discovery=clusterapi:clusterName=prod-cluster,namespace=default \
--scale-down-unneeded-time=10m \
--scale-down-delay-after-add=10m \
--balance-similar-node-groups=true
3. Node Group Contract: Annotations & Configuration
To enable Cluster Autoscaler on a MachineDeployment, annotate the resource with minimum and maximum size boundaries:
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
name: prod-worker-general
namespace: default
annotations:
# Define autoscaling bounds for Cluster Autoscaler discovery
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "2"
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "15"
spec:
clusterName: prod-cluster
replicas: 2
selector:
matchLabels:
cluster.x-k8s.io/cluster-name: prod-cluster
template:
# Machine template specification...
The Critical ClusterClass / Managed Topology Gotcha
In Part 11, we established that in managed topologies, Cluster.spec.topology is the single source of truth. This introduces a critical operational conflict if not configured correctly:
THE DRIFT CONFLICT:
1. Operator sets: Cluster.spec.topology.workers.machineDeployments[0].replicas: 2
2. Pending pods arrive -> Cluster Autoscaler scales MachineDeployment.spec.replicas: 2 -> 5
3. Topology Controller detects drift between Cluster.spec.topology (2) and MachineDeployment (5)
4. Topology Controller overwrites MachineDeployment.spec.replicas back to 2!
The Golden Rule for Autoscaling Managed Topologies: When a
MachineDeploymentis managed by Cluster Autoscaler, leavespec.topology.workers.machineDeployments[].replicascompletely UNSET in theClusterresource.
# Correct Managed Topology Configuration for Autoscaling:
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
name: prod-cluster
spec:
topology:
class: production-aws-class
version: v1.31.0
workers:
machineDeployments:
- class: general-purpose
name: md-general
# replicas: intentionally omitted!
When omitted, the Topology Controller delegates replica management to the Cluster Autoscaler, preventing state flapping.
4. Scale-From-Zero Mechanics & GPU Workloads
A major cost optimization pattern is maintaining specialized worker pools (e.g., expensive GPU compute, high-memory instances, or FPGA nodes) configured with min-size: 0.
However, scaling from zero presents an architectural challenge:
THE COLD-START DILEMMA:
When replicas = 0, no Node exists in the workload cluster.
How does Cluster Autoscaler know whether scaling this empty MachineDeployment
will satisfy a pending pod requiring 8 vCPUs, 32GB RAM, and an NVIDIA A10G GPU?
In typical cloud providers, CA queries cloud instance APIs to discover VM specifications. In Cluster API, the autoscaler inspects the MachineDeployment spec, infrastructure templates, and capacity annotations:
sequenceDiagram
autonumber
participant Pod as Pending Workload Pod
participant CA as Cluster Autoscaler
participant MD as MachineDeployment (replicas: 0)
participant CAPI as Cluster API
participant Node as Workload Node
Pod->>Pod: Requires nvidia.com/gpu: 1 & memory: 32Gi
Pod-->>CA: Status: Pending (Unschedulable)
CA->>MD: Inspects node group annotations & InfrastructureMachineTemplate
Note over CA: Simulates scheduling using capacity metadata
CA->>CA: Confirms scaling md-gpu from 0 to 1 satisfies Pod
CA->>MD: Patches spec.replicas = 1
MD->>CAPI: Stamped out Machine & cloud VM instance
CAPI->>Node: Boots VM, runs GPU driver bootstrap, registers Node
Node-->>CA: Node Ready with allocatable nvidia.com/gpu: 1
CA->>Pod: kube-scheduler binds Pod to new GPU Node
Declaring Capacity for Scale-From-Zero
To enable Cluster Autoscaler to simulate scheduling accurately without running nodes, annotate the MachineDeployment with capacity definitions:
metadata:
name: gpu-worker-pool
annotations:
cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size: "0"
cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size: "8"
# Explicit capacity annotations for scale-from-zero simulation:
capacity.cluster-autoscaler.kubernetes.io/cpu: "16"
capacity.cluster-autoscaler.kubernetes.io/memory: "64Gi"
capacity.cluster-autoscaler.kubernetes.io/gpu-type: "nvidia-a10g"
capacity.cluster-autoscaler.kubernetes.io/gpu-count: "1"
Many modern infrastructure providers (like CAPA for AWS) automatically expose instance type specifications to CAPI, allowing the autoscaler to extract CPU and memory allocations directly from the referenced AWSMachineTemplate.
5. Scale-Down & The Graceful Deletion Handshake
Scale-down is where Cluster Autoscaler and Cluster API coordinate closely to prevent workload disruption:
flowchart TD
EVAL["1. CA evaluates underutilized node<br/>(Request sum < 50% for scale-down-unneeded-time)"] --> CHECK{"2. Can pods be safely evicted?"}
CHECK -->|"No: PDB violation / Local storage"| BLOCK["Abort scale-down for this node"]
CHECK -->|"Yes: All pods can reschedule"| MARK["3. Annotate Machine CR:<br/>cluster.x-k8s.io/delete-machine=yes"]
MARK --> DECR["4. Decrement MachineDeployment.spec.replicas"]
DECR --> MS["5. MachineSet Controller prioritizes<br/>annotated Machine for scale-down"]
MS --> CORDON["6. Cordon Workload Node<br/>(spec.unschedulable = true)"]
CORDON --> DRAIN["7. Evict pods via Eviction API<br/>(Honors PodDisruptionBudgets)"]
DRAIN --> DETACH["8. Detach Persistent Volumes"]
DETACH --> DESTROY["9. Terminate Cloud VM & Delete Machine CR"]
The Coordinated Deletion Handshake
- Candidate Identification: CA identifies a node whose utilization has remained below the threshold (default 50%) for the duration of
--scale-down-unneeded-time(default 10 minutes). - Eviction Simulation: CA verifies that all pods currently running on the candidate can be scheduled on remaining nodes without violating any
PodDisruptionBudgets. - The Victim Mark: CA does not delete the node randomly. It adds the annotation:directly to the target
cluster.x-k8s.io/delete-machine: "yes"Machineresource in the management cluster. - Replica Decrement: CA decrements
MachineDeployment.spec.replicasby 1. - CAPI Deletion Priority: When the
MachineSetcontroller reconciles the reduced replica count, its selection algorithm specifically picks the machine marked withcluster.x-k8s.io/delete-machine="yes". - Drain Execution: CAPI executes the multi-phase drain sequence: cordoning the node, invoking the Eviction API, detaching CSI volumes, and destroying the cloud VM.
6. Diagnostic Workflows: Decision Trees & Debugging
When autoscaling fails to expand or contract capacity, isolate whether the problem is an Autoscaler Decision Failure or a Cluster API Provisioning Failure:
flowchart TD
START["Autoscaling Problem Detected"] --> SYM{"What is the Symptom?"}
SYM -->|"Pending Pods but Replicas Not Increasing"| CA_FAIL{"Inspect Cluster Autoscaler"}
CA_FAIL -->|"Node group missing"| CA_Q1["Verify min-size & max-size annotations<br/>and --node-group-auto-discovery flags"]
CA_FAIL -->|"Max size reached"| CA_Q2["Current replicas == max-size: Increase max bound"]
CA_FAIL -->|"Simulation failed"| CA_Q3["Pod requests (CPU/Memory/GPU/Affinity) exceed<br/>the capacity of defined worker classes"]
SYM -->|"Replicas Increased but Nodes Not Joining"| CAPI_FAIL{"Inspect Cluster API Layer"}
CAPI_FAIL -->|"Machine stuck in Pending"| CAPI_Q1["Check Cloud Quota / VPC subnet IP exhaustion<br/>in InfraMachine status"]
CAPI_FAIL -->|"Machine stuck in Provisioning"| CAPI_Q2["Inspect cloud-init bootstrap logs on VM<br/>for join token or port 6443 timeouts"]
CAPI_FAIL -->|"Node NotReady"| CAPI_Q3["Verify CNI plugin is functional on the node"]
SYM -->|"Scale-Down Blocked"| SD_FAIL{"Inspect Scale-Down Blockers"}
SD_FAIL -->|"PDB violations"| SD_Q1["Inspect PodDisruptionBudgets blocking eviction"]
SD_FAIL -->|"Unreplicated pods"| SD_Q2["Check for pods not managed by Deployment/ReplicaSet"]
Essential Diagnostic Commands
1. Inspect Autoscaler Status ConfigMap
Cluster Autoscaler writes its internal decision state to a ConfigMap inside the workload cluster:
kubectl --kubeconfig workload.kubeconfig -n kube-system get configmap cluster-autoscaler-status -o yaml
Look for:
Health: Confirms the autoscaler loop is executing.ScaleUp: Lists whether node groups are ready, unready, or reached maximum size.ScaleDown: Lists candidate nodes and reasons why nodes are not currently scalable.
2. Check Autoscaler Annotations on MachineDeployments
kubectl get machinedeployments -o custom-columns=\
NAME:.metadata.name,\
REPLICAS:.spec.replicas,\
MIN:.metadata.annotations."cluster\.x-k8s\.io/cluster-api-autoscaler-node-group-min-size",\
MAX:.metadata.annotations."cluster\.x-k8s\.io/cluster-api-autoscaler-node-group-max-size"
3. Inspect Autoscaler Pod Logs
kubectl --kubeconfig workload.kubeconfig -n kube-system logs -l app=cluster-autoscaler -f
Filter for:
Scale-up: Shows simulated scheduling calculations and node group selection.Not scheduleable: Details which constraints (CPU, memory, affinity) prevented a pending pod from fitting into an existing node group.
7. Key Takeaways
- Decoupled Roles: Cluster Autoscaler observes pod scheduling pressure in the workload cluster and determines capacity sizing. Cluster API executes the declarative machine lifecycle on the management cluster.
- Unified Cross-Cloud Interface: The Cluster API cloud provider (
--cloud-provider=clusterapi) allows the same autoscaler binary and configuration to scale AWS, Azure, GCP, VMware, and bare-metal fleets consistently. - Managed Topology Rule: When using
ClusterClass, leavespec.topology.workers.machineDeployments[].replicasunset so the Topology Controller does not fight the Autoscaler’s replica changes. - Scale-From-Zero Requires Capacity Metadata: For 0-replica node groups (especially GPU pools), use capacity annotations or provider metadata so CA can simulate scheduling without running nodes.
- The Victim Handshake: During scale-down, CA marks victim machines with
cluster.x-k8s.io/delete-machine="yes", ensuring that CAPI safely drains and terminates the exact node chosen by the autoscaler.
Next in the series
Now that we understand how Cluster Autoscaler dynamically drives worker fleet sizing, explore Part 13: Bootstrap Clusters, Management Clusters, and clusterctl move to understand how management planes are bootstrapped from local Kind clusters and pivoted into production management clusters.