Back to blog

Kubernetes

EKS node autoscaling with Karpenter: just-in-time capacity without node groups

Karpenter changes EKS node autoscaling by launching right-sized EC2 nodes from pending pods, then consolidating and replacing them as the fleet drifts.

July 3, 2026 Platform Engineering 12 min read

Most people arrive at Karpenter after a slow build-up of small frustrations rather than one dramatic outage. Managed node groups start out simple, and then one day you notice you are spending real time deciding which group should be a little bigger, which instance family to add next, and how much headroom to leave “just in case”. It starts to feel less like running a cluster and more like tending a spreadsheet.

Karpenter offers a gentler mental model. Instead of pre-sizing groups and hoping one of them fits, it watches the pods that cannot be scheduled, works out the capacity they actually need, and launches EC2 nodes to match. Two things shift as a result. Scaling up follows the workload rather than a template you wrote weeks ago, and scaling down becomes a policy you set once rather than a clean-up job you keep meaning to get to. On a busy cluster, that is often the difference between paying for a permanent buffer and only asking for capacity when there is real work waiting.

How Karpenter changes EKS node autoscaling

The simplest way to describe the change is this: Cluster Autoscaler works at the level of node groups, while Karpenter works at the level of individual pending pods. On paper that reads like a minor implementation detail. In day-to-day operation it turns out to be most of the story.

With Cluster Autoscaler, you make the important decisions up front — which instance families exist, how much spare room each group keeps, and which group grows when pods start to queue. Karpenter takes those same decisions and turns them into guardrails, then chooses the actual node shape at launch time, once it can see what is really being asked for.

That tends to suit clusters where the workloads do not all look alike:

  • web services that spike during deployments
  • queue workers whose demand follows traffic rather than CPU
  • short-lived CI or batch pods
  • teams mixing Spot and on-demand capacity in the same cluster

None of this means node groups are a mistake. They are simply static by design, and that is perfectly fine right up until your workload mix changes often enough that static sizing starts to feel expensive or brittle. That is usually the moment Karpenter starts to earn its place.

NodePool and EC2NodeClass split policy from AWS plumbing

On EKS, two Karpenter resources do most of the work: NodePool and EC2NodeClass.

  • NodePool describes scheduling and disruption policy — what kinds of nodes are allowed, how aggressively Karpenter can consolidate, and which workloads are welcome to land there.
  • EC2NodeClass describes the AWS side — subnets, security groups, AMI family, the IAM role or instance profile, storage, and the other EC2-specific details.

Keeping these separate is more useful than it first appears, because it holds your capacity intent apart from the cloud plumbing underneath it. A single EC2NodeClass can back several NodePools, and each NodePool can express a different set of rules about the workloads it serves.

A minimal setup looks like this:

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ['c', 'm', 'r']
        - key: karpenter.sh/capacity-type
          operator: In
          values: ['spot', 'on-demand']
        - key: kubernetes.io/arch
          operator: In
          values: ['amd64']
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 10m

Two things are worth remembering here, and the docs are clear about both. Karpenter will not do anything at all until at least one NodePool exists — there is no implicit default waiting in the background. And a pod’s own scheduling constraints are intersected with the NodePool requirements, so if a workload asks for something the pool does not allow — an instance family, a zone, an architecture — Karpenter simply will not launch a node for it. It sounds obvious written down, but it is one of the more common ways for a pending pod to look like a capacity shortage when it is really a policy mismatch.

Why just-in-time provisioning beats a larger buffer

The traditional habit on EKS is to keep enough spare capacity around that the next deployment or burst will probably fit. The trouble with “probably” is that it quietly shows up on the bill every month, whether or not you ever use it.

Karpenter lets you run a little closer to the line, because it provisions from real pending demand rather than a guess. If a rollout suddenly needs six more replicas, it can pick capacity that fits those pods instead of inflating a general-purpose group and hoping the shape is close. When the burst passes, the same controller can quietly remove or replace whatever it over-provisioned.

This helps most when your requests are uneven. A memory-heavy worker, an ARM-only build job, and a stateless frontend rollout do not really want to share a node. Node groups force you to approximate and accept some waste, whereas Karpenter can stay much closer to what each workload actually asked for.

The catch is that your constraints have to be explicit. Instance family, architecture, capacity type, disruption tolerance — all of it needs to be written down properly. Karpenter takes away the provisioning ceremony, but it does not take away the thinking.

Consolidation, drift, and interruption handling are the real day-two features

Fast provisioning is the headline, but the part you live with day to day is what happens after a node has landed.

Karpenter’s disruption controller looks after two cases that quietly save a lot of effort:

  • Consolidation removes empty nodes, or swaps an underutilised one for a cheaper shape when the same pods can comfortably run elsewhere.
  • Drift replaces nodes that have fallen out of step with the current NodePool or EC2NodeClass — a new AMI selection, say, or changed launch requirements.

In a node-group-heavy setup, those two jobs — right-sizing and AMI rollovers — usually turn into separate pieces of scheduled maintenance. Karpenter keeps comparing what you asked for with what is actually running, so the fleet does not slowly drift out of shape between maintenance windows.

There is an honest caveat here, and the Karpenter docs do not hide from it: consolidation is only ever as good as the disruption rules you allow. Preferred anti-affinity and topology spread can quietly stop nodes from consolidating even when the scheduler could place the pods elsewhere, and PodDisruptionBudgets can do the same. So when someone says Karpenter is not saving them any money, the controller is rarely the first place to look. More often it is the workload policy wrapped around the nodes it is trying to drain.

Interruption handling matters too, especially if you lean on Spot. Karpenter can react to a Spot interruption warning, bring up replacement capacity ahead of time, and drain the old node in parallel — but all of that still depends on the workload tolerating disruption. Two minutes is not long when graceful shutdown, PDBs, and placement rules are all pulling in different directions.

The cost story here is the unglamorous one: a cluster that already had plenty of capacity in aggregate, just trapped in the wrong shape. Consolidation and drift are what turn that from a tidy architecture diagram into an actual lower bill.

Spot works well when the fallback path is real

Karpenter and Spot are often mentioned in the same breath, and for good reason — it can choose from a far wider range of instance types than a hand-maintained node-group layout, which is exactly what makes Spot workable. The part worth dwelling on is that Spot only pays off when the fallback is genuinely there.

A sensible EKS pattern usually looks something like:

  • prefer Spot for workloads that can tolerate interruption
  • allow on-demand in the same NodePool for when the service cannot wait for spare Spot capacity to appear
  • keep topology spread and PDBs sensible, so replacing one node does not set off a second incident
  • test your interruption handling before you actually need it

That last point carries more weight than it might seem. “We allow on-demand fallback” is a line of configuration; proving that a constrained workload can still schedule when a Spot pool dries up is a different thing entirely, and a good deal more reassuring when it happens at an awkward hour.

Where Karpenter is better than Cluster Autoscaler, and where it is not

Karpenter tends to win out over Cluster Autoscaler when an EKS cluster carries a genuine mix of workloads and their shapes change often. It is good at turning pending work into right-sized nodes without asking you to pre-bake every possible capacity combination into managed groups ahead of time.

It is a less obvious choice when the fleet is already settled — the node groups are well understood, the workload mix barely moves, and nothing about the current setup is causing pain. If what you have today is simple and cheap, Karpenter can end up being a sophisticated answer to a question nobody was really asking.

So the useful question is not whether Karpenter is newer or cleverer, because it is comfortably both. It is whether managing node groups has quietly become a drag on your time. If it has, the added complexity is usually a fair trade. If it has not, a well-kept Cluster Autoscaler setup may still be all you need.

Takeaway

Karpenter moves EKS node autoscaling away from managing node groups and towards provisioning that follows the workload. You describe the policy once through NodePool and EC2NodeClass, and from then on capacity is launched to match the pods actually in front of the scheduler, rather than the guess someone made last quarter.

The real payoff is not only quicker scale-up. It is the combination of right-sized launches, consolidation, drift replacement, and Spot fallback that holds together under pressure. Between them, consolidation and Spot can take a meaningful bite out of node spend without making scale-up feel fragile, though the exact number will always depend on your particular workload mix.

If you are already spending your week wrestling oversized node groups, slow scale-up, or a fleet that drifts out of shape between maintenance windows, Karpenter is well worth a serious look on EKS.