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 AutoscalerKarpenter
Unit of scalingFixed node groups (ASGs)Individual, just-in-time nodes
Instance flexibilityPer-group, pre-decidedBroad families, chosen per pod
Bin-packing / consolidationBasic scale-downActive consolidation
Best whenStable, uniform workloadsDiverse, 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

Example · yaml
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: 1m

When 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.

Example · yaml
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-waste

Karpenter 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.

Example · yaml
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: 30s

Check Karpenter provisioning events

Shows recent Karpenter provisioning events and active NodeClaim objects to verify nodes are being launched and claimed correctly.

Example · bash
kubectl get events -n kube-system \
  --field-selector reason=ProvisioningSucceeded \
  --sort-by='.lastTimestamp' | tail -10
kubectl get nodeclaims

Discussion

  • Be the first to comment on this lesson.