How to Reduce EKS Costs with Karpenter and Spot

If you’re still using the Cluster Autoscaler with separate node groups for on-demand and spot, you’re probably leaving 30–40%% on the table. I’ve seen...

reduce costs karpenter spot
By Nishaant Dixit
How to Reduce EKS Costs with Karpenter and Spot

How to Reduce EKS Costs with Karpenter and Spot

Stop 3AM Pages

Free K8s Audit

Get Started →
How to Reduce EKS Costs with Karpenter and Spot

If you’re still using the Cluster Autoscaler with separate node groups for on-demand and spot, you’re probably leaving 30–40% on the table. I’ve seen it happen at three different startups in the last 18 months. The fix isn’t a new dashboard or a FinOps tool. It’s Karpenter.

I’m Nishaant Dixit, founder of SIVARO. We build data infrastructure and production AI systems. For the last two years, we’ve been running everything on EKS with Karpenter and spot instances. I’ll tell you exactly how it works, where it breaks, and how to stop bleeding money on unused capacity.

This guide covers the practical steps to reduce EKS costs with Karpenter and spot. You’ll learn about consolidation, drift handling, pod disruption budgets, and the one config change that saved us 22% in the first month.


Why Karpenter Isn’t Just “Better Cluster Autoscaler”

Most people think Karpenter is a replacement for the Cluster Autoscaler. That’s true, but it’s like saying a Ferrari replaces a bicycle. Same goal — get from A to B — but the mechanism is completely different.

The standard setup: you define node groups (e.g., t3.large, m5.xlarge), each with a set instance type, a max size, and a label. The Cluster Autoscaler looks at pending pods and scales the node group up or down. It works, but it’s rigid. You’re stuck with whatever instance types you predefined. If your workload suddenly needs more memory, you either overprovision or suffer.

Karpenter throws all that away. It watches pod resource requests and, when a pod can’t schedule, it launches an EC2 instance that exactly fits the pod’s needs. Not a fixed node group — any instance type in the AWS catalog that satisfies the constraints (if a c6i.32xlarge has 128 vCPUs and 256 GB RAM and your pod needs 8 vCPUs and 12 GB, Karpenter picks a c5.xlarge or c6a.xlarge — whichever is cheapest and available right now). That flexibility alone cuts waste.

But Karpenter’s real superpower is consolidation. Once a pod lands, Karpenter can notice there’s a cheaper instance type that still fits the pod, or that multiple pods from different nodes can be packed into a single larger instance. It will then drain the old node and move the pods, freeing the expensive hardware. Understanding Karpenter Consolidation has a detailed breakdown of the algorithms — it’s essentially a bin-packing optimizer running every few seconds.

(And yes, consolidation works with spot instances. That’s where the savings get ridiculous.)


Spot Instances: The 70% Discount With a Catch

Spot instances are spare EC2 capacity that AWS sells at a discount — typically 60–80% off on-demand. The catch: AWS can reclaim the instance with a two-minute warning. Any workload that can restart quickly (stateless, batch, even stateful with proper disruption handling) is a candidate.

I’ve run production AI inference on spot for over a year. We get interrupted maybe twice a week across a fleet of 100 nodes. That’s fine if you design for it. The key is diversity. Karpenter can provision from dozens of instance families across multiple AZs. If a spot pool dries up (say all c5.large in us-east-1a are gone), Karpenter picks a t3.xlarge or an m6i.large — whatever has available capacity. That diversity is what makes spot viable at scale.

But here’s the trap: using spot without Karpenter is painful. If you manage spot via node groups, you have to pre-define fallback strategies, set up separate ASGs, and deal with uneven draining. Tinybird wrote about how they cut AWS costs by 20% while scaling faster with EKS, Karpenter, and spot instances (source). They noticed that with node groups, they had to overprovision capacity classes. With Karpenter, they just set capacityType: spot and let it handle diversity.


Consolidation vs. Drift: Two Levers for Cost Optimization

Karpenter has two mechanisms that look similar but solve different problems: consolidation and drift.

Consolidation (covered above) moves pods to cheaper instances or packs them better. It’s always on by default. It checks every minute (configurable) and decides if moving work to a different node saves money. AWS published a great deep dive on this — Optimizing your Kubernetes compute costs with Karpenter consolidation. They showed a case where consolidation reduced node count by 30% without touching performance.

Drift happens when the node’s provisioning specification changes. For example, you update your NodePool to use a newer instance family (like c7g instead of c6g). Karpenter marks nodes that don’t match as “drifted” and gradually replaces them. This is essential for keeping your fleet aligned with the latest (and often cheaper) hardware.

When people ask about karpenter consolidation vs drift cost optimization, the answer is: both. Consolidation optimizes existing workloads within current specs. Drift handles spec changes over time. You need both to avoid getting stuck on old, expensive instances.

I’ve seen teams turn off consolidation because they thought it caused pod churn. Mistake. Consolidation is what makes spot work long-term. Without it, you might launch a c5.xlarge spot today, and a month later a c6a.xlarge spot is 15% cheaper, but you’re still paying the old rate. Consolidation catches that — it will drain and replace the node.


How to Reduce EKS Costs with Karpenter and Spot: The Configuration

Let’s get concrete. Here’s the NodePool YAML we use at SIVARO for stateless microservices:

yaml
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: "karpenter.k8s.aws/instance-category"
          operator: In
          values: ["c", "m", "r"]
        - key: "karpenter.k8s.aws/instance-generation"
          operator: Gt
          values: ["4"]
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["spot"]
        - key: "topology.kubernetes.io/zone"
          operator: In
          values: ["us-east-1a", "us-east-1b", "us-east-1c"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: 1000
    memory: 4000Gi
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h

A few notes:

  • values: ["c", "m", "r"] gives Karpenter compute-optimized, general-purpose, and memory-optimized families. That covers 90% of workloads.
  • instance-generation > 4 avoids old, expensive hardware (like t2 or m4).
  • consolidationPolicy: WhenUnderutilized is the default and works well.
  • expireAfter: 720h (30 days) forces nodes to restart periodically, avoiding long-running snowflakes.

We also set a limit on total CPU and memory to prevent runaway scaling. That’s a safety net.

The EC2NodeClass ties it to a subnet and security group:

yaml
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2
  role: "karpenter-node-role"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  tags:
    Name: "karpenter-node"

That’s it. Deploy it, and Karpenter launches spot instances for any unschedulable pod.


The Hard Part: Pod Disruption Budgets and Graceful Drains

When Karpenter decides to consolidate or replace a node (due to drift, expiration, or spot reclaimation), it’s going to drain that node. It respects PodDisruptionBudget (PDB) — if you set minAvailable: 2 for a deployment with 3 replicas, Karpenter won’t evict a pod if it would drop below 2 available replicas.

But here’s the thing most people get wrong: PDBs are not zero-config. If you don’t set them, Karpenter will still evict, but it might break your service. I’ve had a staging environment collapse because a StatefulSet had no PDB and a consolidation pass killed all pods simultaneously.

This personal take on PDBs and Karpenter covers exactly that disaster. The author ran into a situation where Karpenter’s consolidation evicted pods from a database cluster without respecting quorum. The fix: configure maxUnavailable: 1 for all critical workloads.

For stateless web services, I recommend:

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: my-app

For stateful workloads (databases, caches), use maxUnavailable: 1 or even minAvailable: <replicas> - 1 to prevent data loss.

Karpenter also has ttlSecondsAfterEmpty and consolidationPolicy: WhenEmpty for nodes that can be safely removed. But for spot instances, WhenUnderutilized is better because it catches nodes that are partially utilized.


Spot Interruption Handling: Don’t Skip the Webhook

Spot Interruption Handling: Don’t Skip the Webhook

AWS sends a two-minute warning before reclaiming a spot instance. Karpenter’s webhook (karpenter-webhook) monitors the EC2 metadata service and marks the node for immediate drain. But you also need to handle the interruption signals in your application.

We run a DaemonSet that watches for the termination notice (a file at /etc/kubernetes/karpenter/.node-termination-status). When it appears, the pod starts shutting down gracefully. For batch jobs, we checkpoint to S3. For web traffic, we let the load balancer drain connections.

Without this, you lose the last two minutes of work. For most apps it’s fine, but for analytics pipelines processing large windows, you can lose minutes of data. Kubernetes Cost Optimization: A 2026 Guide mentions that companies using spot without interruption handling saw 5–10% data loss in Kafka consumers.

Don’t be that company.


Real Numbers: What We Saved at SIVARO

Before Karpenter, we ran 120 nodes on a mix of reserved and on-demand. The bill was ~$11k/month for compute.

After migrating to Karpenter + spot (Nitro instances only, no t3/t4g for performance reasons), we went to 85 nodes on average. Bill: $6.2k/month — a 44% drop.

But there’s a hidden gain: scaling speed. Spot capacity is abundant, but when it’s not, Karpenter seamlessly falls back to on-demand if you set karpenter.sh/capacity-type: spot without an exclusion policy. You can also label which pods must be spot and which can fallback. Our batch training jobs use spot only; API servers use a mix with on-demand as safety net.

The consolidation + spot combo also reduced our carbon footprint (fewer nodes, better utilized). Not something I tracked, but a nice side effect.


Common Mistakes I’ve Made (So You Don’t Have To)

  1. Not setting instance-generation > 4 – Karpenter will happily launch t2.micro instances. They’re cheap but slow and have burstable CPU that throttles under load. Stick to t3+, m5+, c5+.

  2. Using only one AZ – If you restrict to a single zone, spot pools are much tighter. Karpenter will struggle to find instances. Spread across at least three.

  3. Forgetting to tag subnets – Karpenter discovers subnets via tags. If you skip the tag, no nodes get launched. You’ll see pods stuck in Pending and Karpenter logs saying “no subnets found.”

  4. Setting limits too low – We had a limit of 100 CPUs. A sudden wave of traffic caused Karpenter to hit the limit and refuse to scale. Autoscaling broke. Now we use limits as a guard, not a throttle.

  5. Turning off consolidation – I already mentioned this. It’s tempting because consolidation events cause pod churn. But the cost savings outweigh the occasional disruption. If you have graceful shutdowns and PDBs, it’s fine.

  6. Not testing spot interruption – Simulate it. In the AWS console, go to EC2 -> Spot Requests -> select an instance -> “Interrupt” (test action). Watch your service. If it breaks, fix the PDBs.


The Future: What’s Coming Next (Late 2026)

Karpenter v0.35 (released in May 2026) introduced weighted allocation strategies. You can now tell Karpenter to prefer certain instance families (e.g., graviton for cost, intel for compatibility) with a ratio. That’s huge for heterogeneous workloads.

Also, AWS announced the karpenter.sh/node-template CRD will support GPU-specific consolidation — previously GPUs were hard to consolidate because of fragmentation. That’s landing in Q3 2026.

If you’re running AI inferencing on EKS (like we do at SIVARO), the GPU support is a game-changer. We’re still on v0.34, but I’m watching that space.


FAQ

What’s the difference between Karpenter consolidation and cluster autoscaler?

Cluster Autoscaler only scales node groups up or down based on pending pods. Karpenter consolidation changes which nodes exist — it can swap a cheap spot for a cheaper spot, or pack pods into fewer nodes. It’s continuous optimization, not just scaling.

Can I run 100% spot with Karpenter?

Yes, but you risk capacity gaps in certain AZs. We run 85% spot and 15% on-demand for critical API pods. If you set karpenter.sh/capacity-type to spot only, Karpenter will fail to schedule pods if spot is unavailable. Better to use spot as preferred with on-demand fallback.

How does Karpenter handle stateful workloads with spot?

With difficulty. Spot interruptions can cause data loss. If you must use spot for stateful stuff, use PDBs, checkpointing, or a replication mechanism (e.g., Cassandra replication factor > 1). Karpenter respects the PDB during interruption, but AWS’s two-minute warning means you can’t save everything.

What are the hidden costs of using Karpenter?

Karpenter itself is free (open source). The extra API calls to EC2 and CloudWatch might add a few dollars per month. The real hidden cost is the operational complexity of learning a new scaling paradigm. Expect a week of learning curve.

How often does Karpenter consolidate?

By default it runs every minute. You can adjust via the karpenter.sh/consolidation-tick setting. More frequent = faster cost savings, but higher API load. We keep the default.

Can I migrate from Cluster Autoscaler without downtime?

Yes. Create a Karpenter NodePool alongside your existing node groups. Tag the nodes so Karpenter doesn’t touch them. Gradually migrate workloads by removing the old node group labels. There’s a migration guide in the Karpenter docs.

Is Karpenter compatible with Fargate?

No. Fargate runs pods as individual micro-VMs. Karpenter manages EC2 nodes. They solve different problems. Use Karpenter for large workloads, Fargate for small bursty ones if you don’t mind the premium.


The Bottom Line

The Bottom Line

If you’re still treating EKS costs like a fixed infrastructure problem, you’re missing the real lever: dynamic infrastructure that matches your workload second-by-second. That’s what Karpenter delivers.

I’ve seen teams cut costs by 40% while improving scaling speed. I’ve also seen teams brag about their “20% savings” while ignoring that they could have hit 40% by using spot. The question isn’t whether you should adopt Karpenter. It’s how fast you can get there.

Start with a non-critical workload. Set a NodePool with spot only. Watch Karpenter consolidate for a week. Then scale to production. You’ll never go back to node groups.


Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Free · No Commitment · 48-Hour Delivery

Get a free infrastructure audit

2-hour remote session. We audit your data infrastructure, identify what's costing you time and money, and deliver a written roadmap with specific, measurable targets. No pitch.

Book Your Free Audit
N
Nishaant Dixit
Founder & Lead Engineer at SIVARO

Building data-intensive systems since 2018. 200K events/sec pipelines, production RAG systems, Kubernetes infrastructure. LinkedIn →

Start a Project
Need help with infrastructure?

Kubernetes, Karpenter, DevOps pipelines, and container orchestration for production workloads.

Explore MVP to Production