Cluster Autoscaler Explained

Khimananda Oli 8 min read Virtualization
Cluster Autoscaler Explained

By Khimananda Oli | Last reviewed: August 2026

Your pods are stuck in Pending because the scheduler cannot find a node with enough CPU or memory, yet your cloud bill keeps climbing during quiet hours. This mismatch between resource demand and provisioned capacity is exactly what Cluster Autoscaler solves by dynamically adjusting node count. Understanding how Cluster Autoscaler works—and where it fails—is essential before you enable it in any production environment.

Cluster Autoscaler Decision LoopPending PodsInsufficient CPU/MemoryScale UpAdd Nodes via Cloud APIScheduled PodsAll Pods RunningUnderutilized Node<50% Usage + Grace PeriodScale DownDrain & Terminate NodeStable StateNo Action NeededRe-evaluation every 10 seconds (default)
Cluster Autoscaler evaluates pending pods for scale-up and underutilized nodes for scale-down independently every scan interval.

How does Cluster Autoscaler decide when to add or remove nodes?

Cluster Autoscaler operates on two independent control loops: scale-up and scale-down. Unlike the Horizontal Pod Autoscaler, which responds to CPU/memory metrics or custom metrics, Cluster Autoscaler watches the Kubernetes scheduler directly. It asks one question on each loop: "Are there pods that cannot be scheduled right now?" If yes, it calculates the minimum number of nodes needed to fit those pods and requests them from the cloud provider API.

Scale-up logic

When the scheduler marks a pod as Unschedulable with a condition like Insufficient cpu or Insufficient memory, Cluster Autoscaler simulates scheduling that pod onto hypothetical new nodes from your configured node groups. It selects the node group that can accommodate the pending pods most efficiently, respecting constraints like max nodes per group, availability zones, and instance types. On AWS EKS, this means calling the Auto Scaling Group API; on GKE, it calls the Managed Instance Group API; on Azure AKS, it uses the VMSS API.

Scale-down logic

Scale-down is more conservative. A node is considered underutilized only if its total requested resources (sum of all pod requests, not actual usage) fall below the --scale-down-utilization-threshold (default 0.5). Crucially, the node must remain underutilized for the entire --scale-down-delay-after-add (default 10 minutes) and --scale-down-unneeded-time (default 10 minutes) windows. This prevents flapping when workloads briefly dip. Before terminating a node, Cluster Autoscaler drains it gracefully, respecting PodDisruptionBudgets and evicting pods in priority order.

What is the difference between Cluster Autoscaler and HPA?

A common mistake I see in teams new to Kubernetes is conflating pod scaling with node scaling. These operate at fundamentally different layers and serve different purposes. Getting this wrong leads to either stranded pods or wasted spend.

DimensionCluster AutoscalerHorizontal Pod Autoscaler
ScopeNode-level infrastructurePod-level application replicas
TriggerUnschedulable pods (scheduler events)Metric thresholds (CPU, memory, custom)
ActionAdd/remove cloud VM instancesIncrease/decrease Deployment/StatefulSet replicas
LatencyMinutes (node boot + kubelet ready)Seconds (pod creation only)
Cost impactDirect (VM billing)Indirect (more pods may trigger CA later)
DependencyCloud provider API accessMetrics Server availability

In practice, you almost always need both. HPA scales your application within existing capacity; Cluster Autoscaler ensures that capacity exists. For example, during a flash sale, HPA might scale your Laravel API from 3 to 20 replicas. Once those 20 replicas exhaust current node resources, pending pods trigger Cluster Autoscaler to add three more nodes. When traffic subsides, HPA scales back to 3 replicas, and after the grace period, Cluster Autoscaler removes the extra nodes. Without proper resource requests and limits on your pods, neither system can make correct decisions.

HPA → Cluster Autoscaler Interaction FlowTraffic SpikeCPU > 70% targetHPA Scales Pods3 → 20 replicasPods PendingInsufficient MemoryCA Adds Nodes+3 m5.xlargeTraffic DropsCPU < 30%HPA Scales Down20 → 3 replicasNodes Idle 10mBelow thresholdCA Removes NodesDrain + TerminateNote: Scale-down delay prevents premature node removal during transient dips
HPA reacts to metrics in seconds; Cluster Autoscaler reacts to scheduler pressure in minutes, creating a two-tier autoscaling response.

How do you configure Cluster Autoscaler safely in production?

Default settings rarely suit production workloads. I have seen clusters oscillate wildly or fail to scale during critical events because teams skipped these tuning steps. Start with explicit resource requests on every pod—without them, Cluster Autoscaler cannot accurately simulate scheduling and will make poor scaling decisions.

  1. Set appropriate scale-down delays. For stateless web APIs, --scale-down-unneeded-time=5m and --scale-down-delay-after-add=10m balance responsiveness with stability. For batch or ML workloads with long startup times, increase both to 15–30 minutes to avoid killing nodes mid-job.
  2. Define node group boundaries. Always set --nodes=min:max:group-name or use auto-discovery with tags. Never leave max unbounded in production; a misconfigured HPA combined with unlimited CA can spin up hundreds of nodes in minutes. Set max based on your budget ceiling and quota limits.
  3. Enable Pod Disruption Budgets. Without PDBs, scale-down can terminate nodes running single-replica databases or critical services. Create PDBs for every non-trivial workload to ensure at least one replica remains available during drain operations.
  4. Use priority classes for critical workloads. Mark system components and business-critical pods with high PriorityClasses. During scale-down, lower-priority pods are evicted first. During scale-up failures, high-priority pods preempt lower ones, ensuring essential services get resources even when capacity is constrained.
  5. Monitor CA health explicitly. Deploy Prometheus rules for cluster_autoscaler_unschedulable_pods_count and cluster_autoscaler_scaled_up_nodes_total. Alert if unschedulable pods persist beyond 5 minutes—that indicates CA cannot fulfill demand due to quota exhaustion, missing instance types, or misconfiguration. See the four golden signals of monitoring for framing saturation correctly.
# Example Helm values for EKS production cluster
autoscalingGroups:
  - name: eks-ng-app-m5xlarge
    minSize: 3
    maxSize: 20
    desiredCapacity: 5

extraArgs:
  scale-down-unneeded-time: 5m
  scale-down-delay-after-add: 10m
  scale-down-utilization-threshold: "0.6"
  skip-nodes-with-local-storage: true
  skip-nodes-with-system-pods: true
  balance-similar-node-groups: true
  expander: priority

rbac:
  create: true
  pspEnabled: false

resources:
  requests:
    cpu: 200m
    memory: 512Mi
  limits:
    cpu: 500m
    memory: 1Gi

Why isn't Cluster Autoscaler scaling my nodes as expected?

Troubleshooting CA requires understanding its simulation-based approach. The most frequent issues I encounter stem from mismatches between what CA thinks fits and what the scheduler actually accepts.

Pods lack resource requests

If your Deployment omits resources.requests, CA treats the pod as requesting zero resources. It may believe a tiny node can fit ten such pods, request one node, and then watch all pods stay pending because the scheduler enforces default quotas or node allocatable reservations. Always define explicit CPU and memory requests. Refer to Kubernetes resource limits and requests for sizing guidance.

Node group constraints block scaling

Check your ASG/MIG/VMSS max size, vCPU quotas, and spot instance availability. CA logs Unable to scale up: max size reached or Quota exceeded when hitting these walls. On AWS, verify Service Quotas for EC2 instances in your region; on Azure, check subscription compute quotas. These are invisible to Kubernetes but fatal to autoscaling.

Taints and tolerations mismatch

If your node group has taints (e.g., nvidia.com/gpu=true:NoSchedule) but pending pods lack matching tolerations, CA will never consider that node group as a valid target. Conversely, if all nodes have taints and no untainted node group exists, general workloads will remain permanently pending. Audit taint/toleration pairs across node groups and workloads systematically.

Local storage or system pods block scale-down

By default, CA skips nodes with local storage volumes or system pods (kube-proxy, CNI agents). Enable --skip-nodes-with-local-storage=false only if you accept data loss risk, or migrate stateful workloads to PVCs. For system pods, use --skip-nodes-with-system-pods=false cautiously—ensure critical daemons have PDBs or run on dedicated node pools.

Should you use Cluster Autoscaler or Karpenter in 2026?

Karpenter has matured significantly since its CNCF graduation and now serves as the preferred node autoscaler for many greenfield EKS clusters. However, Cluster Autoscaler remains relevant, especially outside AWS or in regulated environments requiring proven audit trails.

Cluster Autoscaler vs Karpenter (2026)DimensionCluster AutoscalerKarpenterProvisioning SpeedMinutes (ASG/MIG latency)Seconds (direct EC2 API)Instance FlexibilityFixed node groupsDynamic instance selectionMulti-Cloud SupportAWS, GCP, Azure, OpenStackAWS primary, others emergingBin Packing EfficiencyBasic (per node group)Advanced (cross-instance)Audit & ComplianceMature, SOC 2 acceptedImproving, newer evidence
Operational trade-offs between Cluster Autoscaler and Karpenter across speed, flexibility, compliance, and multi-cloud support in 2026.

Choose Cluster Autoscaler if you run on GKE/Azure/OpenStack, require extensive audit documentation for ISO 27001 or SOC 2, or manage heterogeneous node groups with strict placement rules. Choose Karpenter if you are AWS-native, prioritize provisioning speed for bursty AI/ML workloads, or want automatic instance type optimization without maintaining dozens of ASGs. Many organizations run both during migration periods, using Karpenter for new workloads and CA for legacy systems until full transition.

Making Cluster Autoscaler Work Reliably

Cluster Autoscaler explained properly means acknowledging it is a powerful but blunt instrument. It excels at ensuring pods eventually get scheduled, but it does not optimize for cost, bin packing, or provisioning speed out of the box. Treat it as one layer in your autoscaling stack, paired with HPA/VPA for application efficiency and strong observability for early failure detection. Before enabling it, validate resource requests across all workloads, set conservative scale-down timers, cap node group sizes, and establish alerts for sustained unschedulable pods. If you need help designing an autoscaling strategy that balances reliability, cost, and compliance for your specific workload profile, reach out to discuss your infrastructure.

Frequently Asked Questions

Cluster Autoscaler automatically adjusts node count based on pending pods and resource utilization. It adds nodes when scheduling fails and removes underutilized nodes after a configurable grace period to optimize costs.

Yes, they serve different scaling layers.

Not natively for the entire cluster.

Scale-up occurs when pods remain pending due to insufficient CPU, memory, or GPU resources across existing nodes. The autoscaler evaluates unschedulable pods every ten seconds and provisions the smallest node group capable of satisfying pending resource requests immediately.

Nodes with local storage, system pods, or PDB-protected workloads block removal. Check annotations like cluster-autoscaler.kubernetes.io/safe-to-evict and verify no pods prevent eviction. Also confirm the scale-down-delay-after-add timer has expired before expecting node termination events.

Annotate pods with cluster-autoscaler.kubernetes.io/safe-to-evict set to false to prevent node removal. For StatefulSets, ensure persistent volumes use retain policies. Test eviction behavior in staging first, as improper configuration causes data loss during automatic scale-down cycles in production environments.

AWS EKS, GCP GKE, Azure AKS, and DigitalOcean all provide native integrations. Each requires specific IAM roles and node group configurations. Verify your provider’s autoscaler version matches your Kubernetes control plane version to avoid API incompatibilities during cluster upgrades or maintenance windows.

It treats spot nodes identically to on-demand unless configured otherwise. Use node taints and tolerations to isolate interruptible workloads. Enable spot interruption handlers alongside autoscaler to gracefully drain nodes before termination, preventing pod rescheduling failures during capacity rebalancing events.

Required IAM policies include autoscaling:DescribeAutoScalingGroups, ec2:TerminateInstances, and eks:DescribeNodegroup. Attach these to the IRSA role bound to the autoscaler service account. Avoid wildcard permissions; scope actions to specific resource ARNs to maintain least-privilege security posture in multi-tenant clusters.

Inspect autoscaler logs using kubectl logs deployment/cluster-autoscaler. Look for "no unschedulable pods" or "node group min size reached" messages. Verify RBAC bindings, cloud provider credentials, and that expanders are correctly prioritized. Misconfigured expander strategies often cause unexpected scaling behavior during load spikes.

Yes, it honors PDBs during scale-down evaluation. Nodes hosting pods violating PDB constraints are marked unsafe for eviction. Ensure PDBs define realistic minAvailable or maxUnavailable values; overly restrictive budgets permanently block node removal and inflate infrastructure costs despite low actual utilization rates.

Default ten-second intervals suit most workloads. Increase to thirty seconds for large clusters exceeding five hundred nodes to reduce API server load. Decrease only for latency-sensitive batch jobs. Monitor API request rates via Prometheus metrics to balance responsiveness against control plane overhead during peak operations.

They conflict if deployed simultaneously. Karpenter replaces Cluster Autoscaler entirely with faster, provisioner-aware scaling. Migrate by disabling autoscaler, installing Karpenter, and converting node groups to NodePools. Do not run both; competing controllers cause race conditions, duplicate nodes, and unpredictable scaling behavior.

Yes, set --nodes=min:max:auto_scaling_group_name flag during deployment. This caps scaling within defined boundaries per group. Combine with resource quotas and LimitRanges to prevent runaway costs. Review limits quarterly as workload patterns shift to avoid artificial bottlenecks during legitimate traffic surges.

Export metrics to Prometheus using the /metrics endpoint. Track cluster_autoscaler_scaled_up_nodes_total, scale_down_failures, and unneeded_nodes_count. Create Grafana dashboards correlating scaling events with pending pod duration. Alert on sustained unschedulable pods exceeding five minutes to detect configuration drift or quota exhaustion early.