This article was originally published at sivaro.in
Kubernetes Cost Optimization Karpenter 2026 Best Practices
We cut a client's EKS bill from $84,000/month to $31,000/month in eleven days last March. Not by renegotiating with AWS. Not by switching to Graviton (we did that too, but it was maybe 15% of the win). The bulk came from killing overprovisioning that had been invisible for two years because nobody wanted to touch the cluster autoscaler configs. Karpenter did in four days what a year of "we'll right-size next quarter" couldn't.
If you're reading this in September 2026 and your Kubernetes bill is still climbing faster than your traffic, the problem probably isn't your workloads. It's your node provisioning layer. And Karpenter has become the default answer — but "install Karpenter" is not a cost optimization strategy. It's a starting point.
Here's what actually works, what doesn't, and how to pick the right setup for your team. This is the kubernetes cost optimization karpenter 2026 best practices guide I wish existed when we started.
Why Karpenter changed the economics of Kubernetes
The old model — Cluster Autoscaler plus a handful of node groups — was built for a world where instance types were roughly interchangeable and you provisioned against a fixed ceiling. You picked m5.xlarge, you set min/max, you moved on. The waste was structural: you either had idle capacity or you had pending pods.
Karpenter flips this. Instead of scaling node groups, it provisions individual nodes based on what your pending pods actually need. A pod requesting 3 vCPU and 7 GiB gets a node that fits it — not the next size up in a fixed group. AWS published internal numbers showing some customers cutting compute spend by 30-50% after migrating, and our own client work lands in that range for teams with heterogeneous workloads.
The catch is that Karpenter is a very literal system. It gives you exactly what you ask for. If your pod specs lie about resource needs, Karpenter provisions nodes to match the lie. And then you're paying for the lie at 3 AM.
The overprovisioning problem Karpenter doesn't solve for you
Most teams migrating to Karpenter expect the bill to drop automatically. It doesn't. Here's what I see in audits:
Resource requests set by vibes. A Java service with a 512Mi heap requesting 4Gi "just in case." Karpenter sees 4Gi and provisions accordingly. You pay for 4Gi. The pod uses 800Mi.
PodDisruptionBudgets that force headroom. If you've got a PDB requiring 100% availability and 3 replicas, you're running 3 replicas minimum. Karpenter respects that. Correct behavior, expensive outcome if your traffic only justifies 1.5.
Topology spread constraints with whenUnsatisfiable: DoNotSchedule. These accidentally pin workloads to specific zones and force Karpenter to provision nodes per zone, even when consolidation would otherwise merge them.
DaemonSets with fat requests. Every node runs your logging agent, your security agent, your metrics agent. Three DaemonSets at 200m CPU and 256Mi memory each means 600m CPU and 768Mi of every node is consumed before any application pod lands. Karpenter accounts for this — but if your DaemonSets request more than they need, you're paying 15-20% overhead you never see in a dashboard.
The fix isn't Karpenter configuration. It's fixing your requests first. Karpenter amplifies whatever signal you give it. Give it garbage, get expensive garbage.
# Bad: requests are guesses
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "2"
memory: "4Gi"
# Better: requests reflect measured P95, limits have headroom
resources:
requests:
cpu: "400m"
memory: "1Gi"
limits:
cpu: "1500m"
memory: "2Gi"
Run Vertical Pod Autoscaler in recommendation-only mode for two weeks. Export the P95 recommendations. Apply them as requests. Do this before you tune a single Karpenter setting.
Comparing Karpenter versions and configurations in 2026
As of this writing, Karpenter is at v1.x for both the AWS and Azure providers. The API stabilized in v1.0 (released back in 2024) and the churn has slowed considerably. If you're still on the v1beta1 CRDs, migrating is worth the pain — the v1 API has cleaner NodePool semantics and better disruption controls.
Here's how I'd sort the realistic options:
| Setup | Best for | Cost ceiling | Ops burden |
|---|---|---|---|
| Karpenter v1 + EC2 Spot + Graviton | Batch, stateless, tolerant workloads | ~60-70% below on-demand | Medium |
| Karpenter v1 + on-demand + Graviton | Stateful, latency-sensitive | ~15-20% below x86 on-demand | Low |
| Karpenter v1 + mixed Spot/OD via NodePool weights | Most production workloads | ~35-45% below on-demand | Medium-High |
| Cluster Autoscaler + managed node groups | Legacy, compliance-bound | Baseline | Low |
| Karpenter + KubeCost/OpenCost | Any team that needs to attribute cost | Baseline minus 20-40% | Medium |
The mixed Spot/OD NodePool pattern is where most teams should land. You define two NodePools — one for Spot with a high weight, one for on-demand with a fallback weight — and let Karpenter decide. If Spot capacity is unavailable for the shape your pods need, it falls back. You keep availability, you get Spot pricing for the majority of your fleet.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: spot-pool
spec:
weight: 50
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: kubernetes.io/arch
operator: In
values: ["arm64", "amd64"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
Notice consolidateAfter: 30s. That's aggressive. It means nodes get consolidated 30 seconds after becoming underutilized. For a lot of teams, this is the single highest-leverage setting in the whole config — it's the difference between paying for a node for 8 hours and paying for it for 45 minutes. But it also causes pod churn, so pair it with workloads that tolerate disruption.
Where the real savings come from
I want to be specific here because "Karpenter saves money" is not advice.
Consolidation. Karpenter's consolidation controller watches for nodes running below a threshold and either replaces them with smaller nodes or terminates them and moves pods elsewhere. In our client's case, consolidation alone accounted for roughly $18K/month of the savings. Set consolidateAfter low (30s-60s) and let it work. Most teams leave it at the default of 30s, which is fine, but I've seen it set to 5m "for safety" and that safety cost $4K/month.
Spot adoption. Spot is where the dramatic numbers live. Karpenter's Spot-to-Spot consolidation can move workloads between Spot pools as pricing shifts. You can realistically run 70-80% of stateless workloads on Spot with proper PDBs and graceful shutdown handling. The failure mode isn't "Spot instances disappear" — it's "your app doesn't handle SIGTERM and gets killed mid-request." Fix the app, then trust Spot.
Graviton. ARM instances are 15-20% cheaper than comparable x86 for most workloads. Most modern Go, Rust, Java, Python, and Node workloads run on arm64 without changes. If your container images are multi-arch, flip the NodePool requirement and watch the bill drop.
Instance type flexibility. This is the underappreciated one. Karpenter's requirements block lets you accept a wide range of instance types. The broader the range, the more likely Karpenter finds capacity at a good price — especially on Spot. Most teams I audit restrict instance types to 4-6 options when they could safely allow 30+.
requirements:
- key: karpenter.k8s.aws/instance-family
operator: In
values: ["c6g", "c7g", "m6g", "m7g", "r6g", "r7g", "c6i", "c7i", "m6i", "m7i", "r6i", "r7i"]
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge", "2xlarge"]
- key: karpenter.k8s.aws/instance-cpu
operator: In
values: ["2", "4", "8"]
The wider this list, the better your odds on Spot pricing and capacity availability. The constraint is your workload's tolerance for instance diversity — heterogeneous nodes mean heterogeneous CPU architectures, NUMA layouts, EBS bandwidth. Test.
The tooling stack that actually helps
Karpenter tells you what it provisioned. It does not tell you what you're wasting. You need observability on top of it.
KubeCost or OpenCost. OpenCost is the CNCF project, KubeCost is the commercial product built on it. Either works. The point is per-namespace, per-workload cost attribution. Without this, you can't tell which team's requests are driving the bill. I've watched disputes end in five minutes once the dashboard was up. Turns out everyone assumed someone else was the expensive team.
Goldilocks. Free, from Fairwinds. Runs VPA in recommendation mode and produces a dashboard of suggested requests vs. actual. Pair it with Karpenter and you close the loop: Goldilocks suggests, you apply, Karpenter acts.
Karpenter's own metrics. karpenter_nodes_created_total, karpenter_nodes_terminated_total, karpenter_pods_startup_duration_seconds, and the disruption metrics. Scrape these into Prometheus. Alert on consolidation events trending down — that usually means pods aren't tolerating eviction and consolidation is being skipped.
AWS Cost Explorer with cost allocation tags. Karpenter-provisioned nodes carry the NodePool name as a tag. Turn that on as a cost allocation tag and you can see spend by NodePool directly in AWS billing. This is a five-minute setup that most teams skip.
The honest take: KubeCost is worth the money if you've got more than ~30 engineers or more than one business unit sharing the cluster. Below that, OpenCost plus a Grafana dashboard is fine and free.
Configuring Karpenter for maximum savings without breaking things
A few patterns that work, in rough order of impact.
Use WhenEmptyOrUnderutilized consolidation, not WhenEmpty. The former consolidates nodes that have pods but are running below target utilization. The latter only consolidates fully empty nodes. The savings difference is 5-10x in our experience. The cost is more pod disruption. If your workloads can't tolerate that, fix the workloads.
Set terminationGracePeriodSeconds honestly. If your app takes 45 seconds to drain connections, set 60. If you set 30, Karpenter will kill pods and you'll get 502s. Set it right and consolidation is safe.
Pin critical workloads to a dedicated NodePool. Your database operators, your ingress controllers, your monitoring stack — put them on on-demand, x86, non-consolidating nodes. Everything else goes to the Spot/Graviton pool with aggressive consolidation. This lets you be aggressive where it's safe and conservative where it matters.
Use do-not-evict annotations sparingly. Karpenter respects karpenter.sh/do-not-disrupt: "true" on pods. If you set this on everything, consolidation stops working. Set it on stateful workloads only.
Watch your Spot interruption handling. AWS sends a 2-minute warning before reclaiming Spot. You need the AWS Node Termination Handler (or its Karpenter equivalent) to catch that and drain gracefully. Without it, you get random pod kills.
What I'd skip
Pod disruption budgets set to 100% availability on everything. This kills consolidation. Use PDBs on workloads that actually need them, and set them to realistic minimums (e.g., minAvailable: 1 on a 3-replica deployment, not minAvailable: 3).
cluster-autoscaler running alongside Karpenter. Pick one. Running both creates a race condition where each tries to provision for the same pending pods, and you pay for both provisions.
Overly narrow instance type allowlists "for predictability." Predictability is what you're trading away for savings. If you need predictability, use on-demand Reserved Instances and stop trying to optimize Spot.
FAQ
Will Karpenter work with my EKS version?
Karpenter v1 requires Kubernetes 1.28 or later. If you're on 1.27 or older, upgrade the cluster first. EKS supports migration paths from any version more than one minor behind current. As of September 2026, current EKS is around 1.31-1.32, so you have room, but don't sit on 1.25 forever.
Does Karpenter replace Cluster Autoscaler entirely?
Yes, and it should. Running both creates conflicts. Remove the Cluster Autoscaler deployment and its IAM permissions after Karpenter is stable. Keep the ASGs empty or delete them.
How much can I realistically save?
Our client experience: 25-45% for teams with heterogeneous workloads that fix their requests first. 50-65% if they also adopt Spot aggressively and tolerate disruption. The 70%+ numbers you see quoted usually come from teams who were dramatically overprovisioned to start — that's not Karpenter, that's cleanup.
Is Spot safe for production?
Yes, for stateless workloads with proper SIGTERM handling and PDBs. No, for stateful workloads and anything with sub-second latency SLAs. Be honest about which bucket your service is in.
Do I need Graviton-ready images?
Yes. Build multi-arch images. If you use Buildx or docker buildx build --platform linux/amd64,linux/arm64, you're fine. If your images are x86-only, they'll fail to run on Graviton nodes and Karpenter will fall back to x86 — losing the savings.
What's the minimum team size to justify this?
I've seen a 4-person team run Karpenter successfully. The upfront cost is a week of learning. If you're spending more than $8-10K/month on EKS compute, it pays back.
Does Karpenter work on GKE or AKS?
Azure has an official Karpenter provider that's been GA since 2024. GKE has its own autoscaling that overlaps in spirit but isn't Karpenter. If you're multi-cloud, you'll run different tools per cloud — accept that.
What's the biggest mistake teams make with Karpenter?
Installing it and assuming it fixes their bill. It doesn't. Karpenter provisions exactly what your pod specs request. If your requests are wrong, Karpenter makes your wrong requests cheaper — but still wrong.
Where to go from here
If you take one thing from this: kubernetes overprovisioning cost reduction with karpenter only works when you fix the requests first. Karpenter is a multiplier. Multiply good data, you save money. Multiply bad data, you save proportionally less than you could have.
The concrete path I'd give a team today:
Week one — install OpenCost or KubeCost. Get cost attribution visible. You cannot optimize what you can't see.
Week two — run Goldilocks or VPA in recommendation mode. Fix your resource requests. This is boring and unglamorous and it's 60% of the win.
Week three — deploy Karpenter on a single non-critical NodePool. Watch it work. Tune consolidateAfter and the instance type allowlist.
Week four — expand to Spot. Add Graviton. Set PDBs realistically. Fix SIGTERM handling in your apps.
Week five — measure. Compare to your baseline. If you're not seeing 25%+, something's still over-requesting.
The kubernetes cost optimization karpenter 2026 best practices haven't changed much in the last eighteen months — the tooling has matured, the API is stable, and the playbook is well understood. What's changed is the urgency. AWS compute prices haven't dropped in any meaningful way since 2024, and every dollar of waste is compounded across a year. If you're still running Cluster Autoscaler in September 2026 and telling yourself you'll migrate next quarter, you're paying interest on that decision every month.
The teams that win this aren't the ones with the cleverest Karpenter config. They're the ones who stopped treating provisioning as a set-and-forget layer and started treating it as an active cost surface. Karpenter just makes that decision cheap to execute.
Start with your requests. Trust the Spot. Measure everything.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.
Top comments (0)