Autoscaling: Karpenter vs Cluster Autoscaler
Cluster Autoscaler resizes fixed node groups; Karpenter provisions right-sized nodes on demand. Know when each shines.
Both tools add and remove nodes when pods can't be scheduled, but they think about the problem very differently.
Cluster Autoscaler (CA)
CA works within the node groups you defined. When a pod is Pending, it bumps the desired size of an Auto Scaling group; when nodes sit idle, it scales them down. Simple and predictable — but you're locked into the instance types and AZs baked into each group, and you often end up maintaining many groups to cover different shapes.
Karpenter
Karpenter skips node groups entirely. You give it a NodePool and an EC2NodeClass describing acceptable instance families, capacity types, and limits; it then looks at the actual pending pods and launches the cheapest node that fits — the right size, in the right AZ, in seconds. It also consolidates: it will replace two half-empty nodes with one cheaper node, or remove nodes it no longer needs.
| Cluster Autoscaler | Karpenter | |
|---|---|---|
| Unit of scaling | Fixed node groups (ASGs) | Individual, just-in-time nodes |
| Instance flexibility | Per-group, pre-decided | Broad families, chosen per pod |
| Bin-packing / consolidation | Basic scale-down | Active consolidation |
| Best when | Stable, uniform workloads | Diverse, spiky, cost-sensitive workloads |
My default
For anything with varied pod shapes or a real cost target, I run Karpenter and let it own compute, keeping one small managed node group only for Karpenter itself and other cluster-critical add-ons.
Example
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: "1000"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1mWhen to use it
- A platform team migrates from Cluster Autoscaler to Karpenter to reduce node launch latency from 4 minutes to under 60 seconds for burst workloads.
- An engineering team uses Karpenter's disruption consolidation to automatically bin-pack workloads and eliminate half-empty nodes, cutting monthly EC2 spend.
- A data science team uses Karpenter NodePools to provision GPU nodes exactly sized to each training job's requirements without pre-defining fixed node groups.
More examples
Cluster Autoscaler Deployment
The Cluster Autoscaler scales existing fixed node groups and requires pre-defined ASG min/max bounds, contrasting with Karpenter's dynamic provisioning.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
containers:
- name: cluster-autoscaler
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.30.0
command:
- --cloud-provider=aws
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/prod
- --expander=least-wasteKarpenter NodePool for burst capacity
A Karpenter NodePool that provisions Spot nodes of any medium-or-larger size and consolidates underutilized nodes after just 30 seconds.
apiVersion: karpenter.sh/v1
kind: NodePool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: karpenter.k8s.aws/instance-size
operator: NotIn
values: ["nano", "micro", "small"]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30sCheck Karpenter provisioning events
Shows recent Karpenter provisioning events and active NodeClaim objects to verify nodes are being launched and claimed correctly.
kubectl get events -n kube-system \
--field-selector reason=ProvisioningSucceeded \
--sort-by='.lastTimestamp' | tail -10
kubectl get nodeclaims
Discussion