Managing Amazon EKS at Fleet Scale · Part 12 of 18
Autoscaling an EKS Fleet: Karpenter, Consolidation, and the Spot Question
Part 7 committed the fleet to Karpenter as the compute model. This one is about running it in production: how it turns a pending pod into a right-sized node, how you express capacity with NodePool and EC2NodeClass, how consolidation and Spot really behave, how far to push Spot — and the two places Cluster Autoscaler or EKS Auto Mode still win.
TL;DR
- Karpenter is the fleet autoscaler you run yourself. Groupless and workload-aware: it watches unschedulable pods and provisions best-fit EC2 directly, blends Spot and On-Demand in one NodePool, and consolidates idle nodes.
- AWS signs no SLA for it. Karpenter sits under Kubernetes SIG Autoscaling and, as of 2026-06-30, is not CNCF-graduated. You own the controller — its upgrades, permissions, and failures. That is the price of the control and the savings.
- Express capacity with two v1 objects. NodePool (what instance families, sizes, capacity types, limits, disruption rules) and EC2NodeClass (AMI, subnets, IAM). No custom launch templates in v1.
- Consolidation pays the fleet back — budget it. Emptiness-first, then underutilized (v1.5, July 2025). A disruption budget caps how many nodes churn at once so a repack never drains too much of prod in parallel.
- Push Spot to your workloads’ interruption tolerance, no further. A wide instance menu makes Spot survivable; one team (Grover) reached around 80% Spot in production — a practitioner outcome, not a target to assume.
- Speed and savings figures here are practitioner / vendor-reported, current as of 2026-06-30 — directional, never an AWS guarantee.
Run it this way
- Give one NodePool a wide instance menu and blend Spot with On-Demand.
- Turn on consolidation — emptiness-first, then underutilized.
- Set disruption budgets so prod never loses too many nodes at once.
- Own the controller: upgrades, IAM, failure modes — there is no AWS SLA.
Don’t do this
- Don’t push Spot past what a workload’s interruption tolerance allows.
- Don’t run consolidation wide open on prod — budget the churn.
- Don’t reach for custom launch templates — v1 uses EC2NodeClass fields.
- Don’t read ~55s as a guarantee — it’s practitioner data, not an SLA.
Why you run Karpenter yourself — and the sentence AWS won’t sign
The verdict is already set: on a fleet, Karpenter is the default autoscaler, and Part 7 committed to it as the compute model. This article is about operating it — how it schedules, how you hand it capacity, how consolidation and Spot behave when it’s your production on the line, and the two cases where something else wins.
What makes Karpenter the standard is that it is groupless and workload-aware. Instead of scaling pre-defined node groups up and down, it watches the pods the scheduler can’t place and provisions best-fit EC2 directly. One NodePool can span dozens of instance types and blend Spot with On-Demand; it consolidates idle or underused nodes, and it replaces nodes on drift.1
Now the honesty line, up front, because everything below inherits it: AWS states there is no SLA for Karpenter. It sits under Kubernetes SIG Autoscaling and, as of 2026-06-30, is not CNCF-graduated. You run the controller. You own its upgrades, its IAM permissions, and its failure modes. For most fleets that is a fair trade for the control and the cost savings — but it is a real sentence, and nothing in this piece should read as a managed guarantee.
NodePool / EC2NodeClass APIs), with emptiness-first consolidation added in v1.5 (July 2025). Version and consolidation semantics roll forward — re-verify the controller version and its disruption behavior at publish (current as of 2026-06-30).How a pending pod becomes a right-sized node — the scheduling loop
The loop is short, and worth holding in your head because every later decision hangs off it. A pod goes Pending because no current node can hold it. The kube-scheduler can’t place it; Karpenter notices, and instead of resizing some group, it reads your capacity intent and asks EC2 for the cheapest node that fits the pending pods’ combined requests — CPU, memory, architecture, and any topology or nodeSelector constraints they carry (Figure 1).
Because it evaluates the actual set of pending pods, Karpenter can bin-pack the launch: one larger node for a batch of small pods, or a single GPU node for the one pod that asked for an accelerator — rather than adding another copy of some group’s fixed instance type and hoping the pods fit. That is the core difference from group-based scaling, and it’s where most of the cost advantage comes from.
The capacity it’s allowed to choose from is exactly what you declared, and nothing else. Which is the next question: how do you write that menu down?
How you write your capacity intent down — NodePool and EC2NodeClass
Two v1 objects express everything Karpenter is permitted to do. Keep the split straight and the rest is configuration:
- NodePool — the what: which instance families and sizes Karpenter may pick, which capacity types (
spot,on-demand), the limits that cap the pool’s total CPU/memory, plus taints, labels, and disruption rules. - EC2NodeClass — the how, on AWS: the AMI family (AL2023, Bottlerocket), subnets, security groups, the IAM role / instance profile, and block-device mapping.
The v1 change worth flagging before it surprises you: there are no custom launch templates. The old provisioner API let you point at a launch template; v1 removed that and you express node shape through EC2NodeClass fields instead. If you’re migrating from a pre-v1 Karpenter or from Cluster Autoscaler’s launch-template-backed Auto Scaling groups, that’s the migration to plan for (current as of 2026-06-30; re-verify against the release notes).
The design instinct that pays off is counter-intuitive if you come from fixed node groups: give one NodePool a wide instance menu. The more families and sizes you allow, the more freedom Karpenter has to find a cheap best-fit — and, critically for Spot, the more distinct capacity pools it can fall back to when one gets reclaimed. A NodePool pinned to a single instance type throws away most of what you came for.
Use limits and separate NodePools as the guardrails. A limit caps spend and blast radius per pool. Separate pools let you apply different disruption rules to different risk tiers — the common shape is one On-Demand pool for system and stateful workloads and one Spot-heavy pool for stateless ones. A minimal v1 NodePool makes the shape concrete:
apiVersion: karpenter.sh/v1
kind: NodePool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"] # one pool, mixed capacity
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"] # wide menu = cheaper best-fit
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: "1000" # cap the pool
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
budgets:
- nodes: "10%" # never churn >10% at once
That last block — disruption — is where the money and the risk live, so it gets its own section.
How Karpenter claws the waste back — consolidation, guarded by a budget
Provisioning fast is half the value; giving capacity back is the other half, and it’s the half that shows up on the bill. Consolidation is the loop that watches for nodes it can eliminate and does. Since v1.5 (July 2025) it is emptiness-first: it removes empty nodes first — the cheapest, safest win — then looks at underutilized nodes whose pods could be repacked onto fewer or cheaper nodes, and replaces them (Figure 2).1
On a fleet, that is real money. Per-group autoscalers strand idle capacity because each one only knows its own group; Karpenter sees the whole running-and-pending picture and can repack across instance types. The waste it reclaims is the waste those groups couldn’t see.
The risk is obvious once you say it out loud: consolidation moves pods, moving pods means draining nodes, and draining nodes wholesale on prod at 2pm is exactly what you don’t want. The guard is the disruption budget — a cap on how many nodes Karpenter may disrupt at once, as a count or a percentage, and optionally scheduled to business hours. Set it and consolidation stops being a thundering herd; leave it default-wide and a large repack can drain more of prod in parallel than you would ever choose by hand.
Pair the NodePool budget with the workload-side controls that already exist: pod disruption budgets (PDBs) bound per-app availability during the churn, and a do-not-disrupt annotation protects a pod that genuinely can’t be moved. The NodePool budget bounds node churn; the PDB bounds app availability; together they let consolidation run without you watching it.
How far to push Spot before it bites — one pool, mixed capacity
The reason Karpenter and Spot belong together is the wide menu from the last two sections. One NodePool can request both capacity types, and the breadth of instance types you allowed is exactly what makes Spot survivable. AWS reclaims Spot capacity with a two-minute warning; if your pool can fall back across many instance pools, a reclaim in one is a re-provision in another rather than an outage. Karpenter watches the interruption signal and starts the replacement before the node goes.
So how far can you push it? As far as each workload’s interruption tolerance, and no further. Stateless, replicated, retry-friendly services tolerate Spot well. Anything with long-lived local state, in-flight work that can’t be checkpointed, or a tight availability SLO wants On-Demand. The clean way to express that is the split from Section 3: a Spot-heavy NodePool for stateless workloads, an On-Demand NodePool for system and stateful ones, each with its own disruption rules.
The practitioner data point, labeled plainly as one: Grover reported increasing Spot usage by about 25% to reach around 80% Spot in production (per the AWS Containers Blog).2 That is one team’s outcome, not an AWS benchmark and not a target you should assume. It tells you 80% is achievable for the right workload mix — not that it’s safe for yours. Your own interruption tolerance is the ceiling; the number just shows the ceiling can be high.
How fast it really is — and what the field reports
Two questions land next, every time: how fast, and does it actually save money. Both answers here are practitioner or vendor-reported, and I’m flagging that hard, because there is no AWS SLA behind either — you saw that in Section 1.
| What the field reports | Figure (practitioner / vendor-reported) |
|---|---|
| Karpenter — pending pod to node online | ~45–60s (about 55s) for CPU-bound pods3 |
| Cluster Autoscaler — scale an Auto Scaling group, then let a generic node join | 3–4 min3 |
| Salesforce — fleet migrated to Karpenter | 1,000+ EKS clusters4 |
| Salesforce — cost savings | ~5% in FY2026 (further 5–10% anticipated FY2027)4 |
| Salesforce — operational overhead | ~80% lower4 |
| Salesforce — scaling latency | “from minutes to seconds”4 |
Speed. The Karpenter figures are practitioner benchmarks — Tasrie IT, aligned with ScaleOps and CloudPilot AI data — and Cluster Autoscaler is slower because it has to scale an Auto Scaling group and let a generic node join before pods can schedule. Directional, not a guarantee: your image pull time, bootstrap, and instance availability all move the number. But the direction is consistent across independent reports, and it’s the difference between a batch job that waits a minute and one that waits five.
Outcomes. The Salesforce numbers are as reported by InfoQ in January 2026 — a vendor-reported result from a very large fleet, evidence that the model pays off at scale, not a number you can bank for your own.
The honest read: the speed and the savings are real and consistent across independent practitioner reports, but every figure here is someone else’s production, not an AWS commitment. Prove it on your own workloads before you promise it to finance — which is exactly what Part 15, on cost management and showback, is for.
When Cluster Autoscaler still wins — and the Auto Mode escape hatch
Karpenter is the default, not the only answer, and a reference architecture has to say when it steps aside. Two alternatives earn their place (Figure 3).
Cluster Autoscaler still fits when capacity is stable and predefined, or when a regulated AMI-upgrade workflow — fixed, pre-approved images promoted on a controlled cadence — matters more than best-fit provisioning. It scales the node groups you defined; it doesn’t consolidate or blend Spot the way Karpenter does, and practitioner benchmarks put it at 3–4 minutes to scale. But for a team that wants predictable, per-group capacity and tight control over the AMI, that predictability is the feature — and it’s the same exception Part 7 named when it made Karpenter the default rather than a mandate.
EKS Auto Mode is the escape hatch when you’d rather not run Karpenter at all. It is the same engine — Karpenter, plus the AWS Load Balancer Controller and the EBS CSI driver — run off-cluster by AWS on Bottlerocket nodes with a 21-day maximum runtime, for a management fee of roughly 12% on top of the underlying EC2 cost (the exact percentage varies by instance; current as of 2026-06-30).5 You’re buying the operations, not a different capability. The trade is real: reduced log and node visibility because the components run off-cluster, and once you’re past 150 nodes org-wide the pricing becomes a conversation with your account team rather than a public number. Part 7 sets up the choice; Part 15 does the full cost math.
So, the thresholds — stated as decisions, not description:
- Default: Karpenter, self-managed — for best-fit provisioning, consolidation, cost visibility, and control across the fleet.
- Push Spot to each workload’s interruption tolerance, split by NodePool — not one blanket setting, and not further than the workload allows.
- Cluster Autoscaler when capacity is stable and predefined, or a regulated AMI workflow outweighs best-fit.
- EKS Auto Mode when you’ll happily pay ~12% and give up some visibility to stop operating Karpenter yourself.
Whichever you land on, the compute model is also a cost-shape choice: Karpenter and managed node groups spend into EC2, Auto Mode adds its fee on top, and worker compute is only one of four independent EKS billing streams — the subject Part 15 picks up. Get the NodePool menu wide, the disruption budgets set, and Spot matched to interruption tolerance, and the fleet scales fast and cheap. Just remember who’s holding the pager: you, not AWS.
Sources
- Karpenter — groupless, workload-aware node provisioning; the
NodePool/EC2NodeClassv1 APIs (no custom launch templates); consolidation, with emptiness-first added in v1.5 (July 2025); under Kubernetes SIG Autoscaling, and AWS states there is no SLA for Karpenter. karpenter.sh - AWS Containers Blog, reporting Grover — increased Spot usage by about 25% to reach around 80% Spot instances in production. Practitioner outcome, not an AWS benchmark. aws.amazon.com/blogs/containers
- Tasrie IT, aligned with ScaleOps and CloudPilot AI data — Karpenter brings CPU-bound pods online in roughly 45–60 seconds (about 55s) versus Cluster Autoscaler’s 3–4 minutes. Practitioner benchmarks, directional, not an AWS SLA.
- InfoQ (January 2026), reporting Salesforce — migrated 1,000+ EKS clusters to Karpenter and reported about 5% cost savings in FY2026 (with a further 5–10% anticipated in FY2027), roughly 80% lower operational overhead, and scaling latency reduced “from minutes to seconds.” Vendor-reported, not a guaranteed outcome. infoq.com
- AWS — EKS Auto Mode: runs Karpenter, the AWS Load Balancer Controller, and the EBS CSI driver off-cluster on Bottlerocket nodes (21-day max runtime) for a management fee of roughly 12% on top of EC2; over 150 nodes org-wide, pricing is via your account team. Current as of 2026-06-30; re-verify. aws.amazon.com/eks/pricing