Overview
The Cluster Autoscaler automatically adjusts the number of nodes in your EKS cluster based on pending pods and resource utilization. When combined with HPA, it provides end-to-end autoscaling from application load to infrastructure capacity.
How It Works
Flow :
HPA scales pods based on metrics
New pods enter “Pending” state (insufficient resources)
Cluster Autoscaler detects pending pods
Adds nodes to Auto Scaling Group
Pods scheduled on new nodes
After scale-down period, removes underutilized nodes
Prerequisites
IAM Role
Create IAM role with autoscaling permissions (see IAM & IRSA )
Node Group Tags
Ensure node groups have proper tags: k8s.io/cluster-autoscaler/<cluster-name>: owned
k8s.io/cluster-autoscaler/enabled: true
Service Account
IRSA-enabled service account for Cluster Autoscaler
Installation
Using Helm Chart
The Smallest Self-Host chart includes Cluster Autoscaler as a dependency:
cluster-autoscaler :
enabled : true
rbac :
serviceAccount :
name : cluster-autoscaler
annotations :
eks.amazonaws.com/role-arn : arn:aws:iam::YOUR_ACCOUNT_ID:role/cluster-autoscaler-role
autoDiscovery :
clusterName : smallest-cluster
awsRegion : us-east-1
extraArgs :
balance-similar-node-groups : true
skip-nodes-with-system-pods : false
scale-down-delay-after-add : 5m
scale-down-unneeded-time : 10m
Deploy:
helm upgrade --install smallest-self-host smallest-self-host/smallest-self-host \
-f values.yaml \
--namespace smallest
Standalone Installation
Install Cluster Autoscaler separately:
helm repo add autoscaler https://kubernetes.github.io/autoscaler
helm repo update
helm install cluster-autoscaler autoscaler/cluster-autoscaler \
--namespace kube-system \
--set autoDiscovery.clusterName=smallest-cluster \
--set awsRegion=us-east-1 \
--set rbac.serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::ACCOUNT_ID:role/cluster-autoscaler-role
Configuration
Auto-Discovery
Auto-discover Auto Scaling Groups by cluster name:
autoDiscovery :
clusterName : smallest-cluster
tags :
- k8s.io/cluster-autoscaler/enabled
- k8s.io/cluster-autoscaler/smallest-cluster
Manual Configuration
Explicitly specify Auto Scaling Groups:
autoscalingGroups :
- name : eks-cpu-nodes
minSize : 1
maxSize : 10
- name : eks-gpu-nodes
minSize : 0
maxSize : 20
Scale-Down Configuration
Control when and how nodes are removed:
extraArgs :
scale-down-enabled : true
scale-down-delay-after-add : 10m
scale-down-unneeded-time : 10m
scale-down-utilization-threshold : 0.5
max-graceful-termination-sec : 600
Parameters :
scale-down-delay-after-add: Wait time after adding node before considering scale-down
scale-down-unneeded-time: How long node must be underutilized before removal
scale-down-utilization-threshold: CPU/memory threshold (0.5 = 50%)
max-graceful-termination-sec: Max time for pod eviction
Node Group Priorities
Scale specific node groups first:
extraArgs :
expander : priority
priorityConfigMapAnnotations :
cluster-autoscaler.kubernetes.io/expander-priorities : |
10:
- .*-spot-.*
50:
- .*-ondemand-.*
Priorities:
Higher number = higher priority
Regex patterns match node group names
Useful for preferring spot instances
Verify Installation
Check Cluster Autoscaler Pod
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-cluster-autoscaler
Check Logs
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-cluster-autoscaler -f
Look for:
Starting cluster autoscaler
Auto-discovery enabled
Discovered node groups: [eks-gpu-nodes, eks-cpu-nodes]
Verify IAM Permissions
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-cluster-autoscaler | grep -i "error\|permission"
Should show no permission errors.
Testing Cluster Autoscaler
Trigger Scale-Up
Create pods that exceed cluster capacity:
kubectl run test-scale-up-1 \
--image=nginx \
--requests= 'cpu=1,memory=1Gi' \
--replicas=20 \
--namespace=smallest
Watch nodes:
watch -n 5 'kubectl get nodes'
Watch Cluster Autoscaler:
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-cluster-autoscaler -f
Expected behavior:
Pods enter “Pending” state
Cluster Autoscaler detects pending pods
Logs show: “Scale-up: setting group size to X”
New nodes appear in kubectl get nodes
Pods transition to “Running”
Trigger Scale-Down
Delete test pods:
kubectl delete deployment test-scale-up-1 -n smallest
After scale-down-unneeded-time (default 10 minutes):
Cluster Autoscaler marks underutilized nodes
Drains pods gracefully
Terminates EC2 instances
Node count decreases
GPU Node Scaling
Tag GPU node groups for autoscaling:
managedNodeGroups :
- name : gpu-nodes
instanceType : g5.xlarge
minSize : 0
maxSize : 10
desiredCapacity : 1
tags :
k8s.io/cluster-autoscaler/smallest-cluster : "owned"
k8s.io/cluster-autoscaler/enabled : "true"
k8s.io/cluster-autoscaler/node-template/label/workload : "gpu"
Prevent Cluster Autoscaler on GPU Nodes
Run Cluster Autoscaler on CPU nodes to avoid wasting GPU:
cluster-autoscaler :
nodeSelector :
workload : cpu
tolerations : []
Scale to Zero
Allow GPU nodes to scale to zero during off-hours:
managedNodeGroups :
- name : gpu-nodes
minSize : 0
maxSize : 10
Cluster Autoscaler will:
Add GPU nodes when Lightning ASR pods are pending
Remove GPU nodes when all GPU workloads complete
First startup after scale-to-zero takes longer (node provisioning + model download).
Spot Instance Integration
Mixed Instance Groups
Use spot and on-demand instances:
managedNodeGroups :
- name : gpu-nodes-mixed
minSize : 1
maxSize : 10
instancesDistribution :
onDemandBaseCapacity : 1
onDemandPercentageAboveBaseCapacity : 20
spotAllocationStrategy : capacity-optimized
instanceTypes :
- g5.xlarge
- g5.2xlarge
- g4dn.xlarge
Configuration :
Base capacity: 1 on-demand node always
Additional capacity: 20% on-demand, 80% spot
Multiple instance types increase spot availability
Handle Spot Interruptions
Configure Cluster Autoscaler for spot:
extraArgs :
balance-similar-node-groups : true
skip-nodes-with-local-storage : false
max-node-provision-time : 15m
Add AWS Node Termination Handler:
helm repo add eks https://aws.github.io/eks-charts
helm install aws-node-termination-handler eks/aws-node-termination-handler \
--namespace kube-system \
--set enableSpotInterruptionDraining= true
Advanced Configuration
Multiple Node Groups
Scale different workloads independently:
cluster-autoscaler :
autoscalingGroups :
- name : cpu-small
minSize : 2
maxSize : 10
- name : cpu-large
minSize : 0
maxSize : 5
- name : gpu-a10
minSize : 0
maxSize : 10
- name : gpu-t4
minSize : 0
maxSize : 5
Scale-Up Policies
Control scale-up behavior:
extraArgs :
max-nodes-total : 50
max-empty-bulk-delete : 10
new-pod-scale-up-delay : 0s
scan-interval : 10s
Resource Limits
Prevent runaway scaling:
extraArgs :
cores-total : "0:512"
memory-total : "0:2048"
max-nodes-total : 100
Monitoring
CloudWatch Metrics
View Auto Scaling Group metrics in CloudWatch:
GroupDesiredCapacity
GroupInServiceInstances
GroupPendingInstances
GroupTerminatingInstances
Kubernetes Events
kubectl get events -n smallest --sort-by= '.lastTimestamp' | grep -i scale
Cluster Autoscaler Status
kubectl get configmap cluster-autoscaler-status -n kube-system -o yaml
Grafana Dashboard
Import Cluster Autoscaler dashboard:
Dashboard ID: 3831
See Grafana Dashboards
Troubleshooting
Nodes Not Scaling Up
Check pending pods :
kubectl get pods --all-namespaces --field-selector=status.phase=Pending
Check Cluster Autoscaler logs :
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-cluster-autoscaler --tail=100
Common issues :
Max nodes reached (max-nodes-total)
IAM permission denied
Auto Scaling Group at max capacity
Node group not tagged properly
Nodes Not Scaling Down
Check node utilization :
Check for blocking conditions :
kubectl describe node < node-nam e > | grep -i "scale-down disabled"
Common causes :
Pods without PodDisruptionBudget
Pods with local storage
System pods (unless skip-nodes-with-system-pods: false)
Nodes below utilization threshold
Permission Errors
Check service account :
kubectl describe sa cluster-autoscaler -n kube-system
Verify IAM role :
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-cluster-autoscaler | grep AccessDenied
Update IAM policy if needed (see IAM & IRSA )
Best Practices
Always tag Auto Scaling Groups: k8s.io/cluster-autoscaler/smallest-cluster: owned
k8s.io/cluster-autoscaler/enabled: true
Configure appropriate min/max for each node group: gpu-nodes :
minSize : 0 # Save costs
maxSize : 10 # Prevent runaway
Protect critical workloads during scale-down: apiVersion : policy/v1
kind : PodDisruptionBudget
metadata :
name : lightning-asr-pdb
spec :
minAvailable : 1
selector :
matchLabels :
app : lightning-asr
Track scaling decisions in Grafana Set alerts for scale failures
Periodically test scale-up and scale-down: kubectl scale deployment lightning-asr --replicas=20
Watch for proper node addition/removal
What’s Next?