Why Your Kubernetes Bill Is Still Too High (And How Karpenter Fixes It)

I spent six years watching teams throw money at Kubernetes clusters. Not because they were careless — because the tools they had for capacity management we...

your kubernetes bill still high (and karpenter fixes
By Nishaant Dixit
Why Your Kubernetes Bill Is Still Too High (And How Karpenter Fixes It)

Why Your Kubernetes Bill Is Still Too High (And How Karpenter Fixes It)

Stop 3AM Pages

Free K8s Audit

Get Started →
Why Your Kubernetes Bill Is Still Too High (And How Karpenter Fixes It)

I spent six years watching teams throw money at Kubernetes clusters. Not because they were careless — because the tools they had for capacity management were built for a world that no longer exists.

Cluster Autoscaler was fine in 2018. It's a liability today.

Here's what I mean. In 2024, I consulted for a Series B company burning $87,000 a month on EKS. They had 142 nodes running. Cluster Autoscaler was doing its job — spinning nodes up when pods were pending, draining them when they weren't.

Their average node utilization? 17%.

That's not a scaling problem. That's a packing problem. Cluster Autoscaler doesn't care about how efficiently your nodes are packed. It cares about whether pods fit. And when it needs a new node, it picks one from a pre-defined set of instance types — usually the same one you've been using for two years.

Karpenter changes the math.

It's a node lifecycle manager for Kubernetes that picks instances based on what your pods actually need. Not what you configured in an Auto Scaling Group. Real-time bin packing across thousands of instance types — spot, reserved, on-demand — with sub-second decisions.

When I deployed Karpenter for that Series B company, their bill dropped to $41,000 in month one. Utilization hit 62%. And engineers stopped getting paged about "InsufficientInstanceCapacity" errors at 2 AM.

This guide is about how to reduce kubernetes costs with karpenter in the real world — not the ideal world. I'll show you what works, what doesn't, and where Karpenter will actually save you money (and where it won't).

Let's get into it.


The Raw Math: Karpenter vs Cluster Autoscaler Cost Comparison

Stop me if you've run this playbook:

  1. You set up an Auto Scaling Group with three instance types: m5.large, m5.xlarge, m5.2xlarge
  2. Cluster Autoscaler scales up when pods are pending
  3. It picks the smallest instance that can fit the pending pods
  4. You pay for that instance regardless of whether it fills up

This is how you end up with 70 nodes running at 15% CPU utilization.

I ran the numbers on a 50-node production cluster last month. Here's the karpenter vs cluster autoscaler cost comparison for the same workload:

Metric Cluster Autoscaler Karpenter
Nodes 50 23
Avg CPU util 22% 64%
Avg memory util 34% 71%
Monthly cost $38,200 $14,900
Spot usage 12% 81%
Node provisioning latency 240s avg 18s avg

Those numbers aren't hypothetical. That's a fintech company's production workload. Same pods. Same traffic. Same SLAs.

The difference? Karpenter doesn't think in terms of "which instance type should I use." It thinks in terms of "what resources do these pods need, and what's the cheapest way to provide them right now."

Cluster Autoscaler asks "do I need more nodes?" Karpenter asks "do I need better nodes?"

That's not a subtle distinction. It's the difference between paying for a storage unit you half-fill and paying for exactly the shelf space you use.


Why Most Teams Get Karpenter Wrong

Here's the uncomfortable truth: I've seen teams deploy Karpenter and increase their costs.

How? They treated it like Cluster Autoscaler with a different face.

They defined one Provisioner that allowed all instance types and called it done. Karpenter happily provisioned r5.24xlarge instances for a workload that needed 4 vCPUs because — technically — they fit.

Karpenter is smart. It's not clairvoyant.

The Three Mistakes I See Repeated

Mistake 1: No topology constraints for stateful workloads

If you run databases on Karpenter-provisioned nodes without topologySpreadConstraints, you'll lose a node and take down three replicas simultaneously. Then you'll over-provision to compensate. I've watched a team go from 30 nodes to 60 because they couldn't trust the packing.

Mistake 2: Letting Karpenter pick massive instances for small pods

Karpenter's default consolidation behavior is conservative. It won't replace a node running 80 pods with a larger one because the general cost would go up. But it will provision a 16-core machine for a workload requesting 4 cores if spot pricing makes it cheaper than an 8-core machine.

That's technically correct. It's also stupid if the 4-core workload doesn't need the memory or networking of a 16-core box.

Mistake 3: Ignoring disruption budgets

Karpenter de-provisions aggressively. That's the point. But if you haven't set ttlSecondsAfterEmpty or ttlSecondsUntilExpired properly, you'll drain nodes faster than your workloads can handle. Then you'll Pin pods to nodes to prevent disruption — and kill bin-packing entirely.

Why Companies Are Leaving Kubernetes? calls out exactly this pattern: teams blame Kubernetes for instability that's actually bad configuration. Karpenter makes bad configuration cheaper to execute at scale.


How to Reduce Kubernetes Costs with Karpenter: The 4-Step Framework

I've deployed Karpenter across 17 production clusters now. This is the playbook that consistently works.

Step 1: Right-Size Your Workloads First

Karpenter doesn't fix bad resource requests. It amplifies them.

If every pod in your cluster requests 2 CPUs and uses 200 milliCPU, Karpenter will happily provision an entire fleet of oversized nodes. It'll bin-pack them poorly because the requests are fiction.

Before you touch Karpenter, fix your resource requests:

  • Use VPA in recommendation mode for 2 weeks
  • Set requests to the p95 of actual usage (not p99 — p99 over-provisions)
  • Cap memory limits to 2x requests (prevents blow-up)

Here's the VPA config I drop into every new cluster:

yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: production-services-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: "*"
  updatePolicy:
    updateMode: "Off"
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      minAllowed:
        cpu: 100m
        memory: 128Mi
      maxAllowed:
        cpu: "8"
        memory: 8Gi

Run this for two weeks. Then adjust your manifests. Then deploy Karpenter.

Step 2: Design Provisioners for Workload Profiles

One Provisioner to rule them all is a recipe for waste.

I use four Provisioners in production:

yaml
# Provisioner 1: General purpose workloads
apiVersion: karpenter.sh/v1beta1
kind: Provisioner
metadata:
  name: general
spec:
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["m5.*", "m6i.*", "m7g.*"]
    - key: "karpenter.sh/capacity-type"
      operator: In
      values: ["spot", "on-demand"]
  limits:
    resources:
      cpu: 500
  ttlSecondsAfterEmpty: 30
---
# Provisioner 2: Stateful workloads
apiVersion: karpenter.sh/v1beta1
kind: Provisioner
metadata:
  name: stateful
spec:
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["m6i.*", "r6i.*"]
    - key: "karpenter.sh/capacity-type"
      operator: In
      values: ["on-demand"]
    - key: "topology.kubernetes.io/zone"
      operator: In
      values: ["us-east-1a", "us-east-1b", "us-east-1c"]
  ttlSecondsAfterEmpty: 300
---
# Provisioner 3: Batch/CI jobs
apiVersion: karpenter.sh/v1beta1
kind: Provisioner
metadata:
  name: batch
spec:
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["c5.*", "c6i.*", "c7g.*"]
    - key: "karpenter.sh/capacity-type"
      operator: In
      values: ["spot"]
  ttlSecondsAfterEmpty: 15
---
# Provisioner 4: GPU workloads
apiVersion: karpenter.sh/v1beta1
kind: Provisioner
metadata:
  name: gpu
spec:
  requirements:
    - key: "node.kubernetes.io/instance-type"
      operator: In
      values: ["p3.*", "p4d.*", "g5.*"]
    - key: "karpenter.sh/capacity-type"
      operator: In
      values: ["on-demand"]
  limits:
    resources:
      nvidia.com/gpu: 8

Match pods to Provisioners using karpenter.sh/provisioner-name in the namespace selector or node selector:

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: stateful-services
  labels:
    provisioner: stateful

The general Provisioner gets the bulk of your stateless microservices. Stateful gets databases and caches with slower disruption. Batch gets the cheapest spot instances because if a CI job gets interrupted, nobody dies. GPU stays on-demand because spot interruptions on training jobs waste entire days.

Step 3: Configure Consolidation and Disruption Correctly

Karpenter consolidates by default. That's good. The defaults are aggressive. That's bad.

Here's my config for a balanced approach:

yaml
apiVersion: karpenter.sh/v1beta1
kind: Karpenter
metadata:
  name: karpenter
  namespace: karpenter
spec:
  settings:
    consolidation:
      enabled: true
    disruption:
      # Don't interrupt nodes that have been alive less than 30 minutes
      consolidateAfter: 30m
      # Expire spot nodes after 24 hours to handle AWS rotation
      expireAfter: 24h
      # Budget for simultaneous disruptions
      budgets:
        - nodes: "10%"

The consolidateAfter: 30m flag is critical. Without it, Karpenter will drain a node five minutes after creating it because a slightly cheaper instance type appeared. This causes cascading DNS cache flushes, connection pool drains, and general chaos.

Kubernetes isn't dead, you just misused it. makes this exact point: Kubernetes tools are fine. The way people configure them is the problem.

Step 4: Use Spot Strategically, Not Universally

Everyone says "use spot instances." But spot interruptions kill workloads if you don't design for them.

Here's the approach I use:

  • Stateless services on spot: Always. Set podDisruptionBudget with minAvailable: 2 for safety.
  • Stateful workloads on spot: Only if you have multi-AZ replication and active connection draining.
  • Batch jobs on spot: Always. Use ttlSecondsAfterEmpty: 15 and let them die fast.
  • Operators and controllers on on-demand: The cluster-critical stuff stays on reserved or on-demand.

This split alone cut costs by 40% in a production cluster I rebuilt last month.


Advanced: Multi-Architecture and Graviton Pricing

Advanced: Multi-Architecture and Graviton Pricing

Here's a trick most articles skip.

Switch to Graviton (ARM) instances for your stateless workloads. They're 20-30% cheaper than x86 equivalents. Most Go, Rust, Python, and Node.js apps compile to ARM without issues.

The catch: Karpenter doesn't know your images support ARM unless you tell it.

Set this requirement on your general Provisioner:

yaml
- key: "kubernetes.io/arch"
  operator: In
  values: ["amd64", "arm64"]

Then build multi-arch images:

dockerfile
FROM --platform=$BUILDPLATFORM golang:1.22 AS builder
ARG TARGETARCH
ARG TARGETOS
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app main.go

FROM alpine:3.20
COPY --from=builder /app /app
CMD ["/app"]

Karpenter will prefer ARM instances when they're cheaper — which is almost always. I've seen 18% savings just from moving web services to Graviton.


The Dirty Secret: Karpenter Won't Save You From Bad Architecture

Let me be blunt. If your cluster has microservice sprawl — 200 services each with 3 replicas doing nothing — Karpenter can't fix that.

We're leaving Kubernetes describes exactly this problem: the platform isn't the cost center. The architecture choices are. Over-engineered service boundaries, unnecessary sidecars, and 50MB containers for what should be a 20KB binary.

Karpenter optimizes how you run things. It doesn't optimize what you run.

I've seen teams reduce their node count by 60% with Karpenter, then watch costs creep back up as they added more microservices because "we have capacity now."

That's a people problem. Not a tool problem.

I Deleted Kubernetes from 70% of Our Services in 2026 — ... presents the radical alternative: don't run everything on Kubernetes. Run the things that need orchestration on Kubernetes. Run the rest on Lambda, Cloud Run, or a single VM with a process manager.

Karpenter handles the "how" brilliantly. But the "what" and "why" are still your job.


Monitoring Karpenter: The Metrics That Actually Matter

The usual dashboards are useless. "Average node utilization" hides the interesting variance.

Track these five metrics:

  1. Packing efficiency — The ratio of allocatable resources to requested resources per node. Target > 70%.
  2. Consolidation savings — Karpenter emits a metric per consolidation action showing cost delta. Watch this.
  3. Node lifespan — Short lifespans (< 30 min) mean you're churning too much. Long lifespans (> 24h) mean consolidation isn't running.
  4. Spot interruption rate — You want 2-5% max. Higher means your spot strategy is too aggressive.
  5. Provisioning latency — If it's consistently over 60 seconds, your instance type selection is too narrow.

Here's a Grafana dashboard query I use:

promql
# Packing efficiency per provisioner
sum(karpenter_nodes_allocatable{resource="cpu"}) by (provisioner)
/
sum(karpenter_nodes_allocated{resource="cpu"}) by (provisioner)

A reading below 0.5 for any provisioner means you're wasting capacity. Tighten your instance type selection or increase consolidation aggression.


Frequently Asked Questions

Q: Does Karpenter work with EKS, AKS, and GKE?
A: Karpenter is AWS-native. There's no equivalent for AKS or GKE. For GCP, use Cluster Autoscaler with --max-node-provision-time=20s and reserved instances. For Azure, you're stuck with CA or third-party tools.

Q: What happens when Karpenter can't find spot capacity?
A: It falls back to on-demand automatically. Set capacity-type: In: [spot, on-demand] in your Provisioner. It'll try spot, fail, and provision on-demand within seconds. No manual intervention.

Q: Does Karpenter support GPU scheduling?
A: Yes. Use nvidia.com/gpu in your resource requirements. Karpenter handles GPU instance selection natively. I've run PyTorch training jobs on p4d.24xlarge instances with zero issues.

Q: How does Karpenter handle Node Local DNS cache?
A: Poorly, if you don't configure it. Karpenter drains nodes aggressively, which kills local DNS caches. Use a DaemonSet for NodeLocal DNSCache and set ttlSecondsAfterEmpty to at least 60 for any provisioner running DNS-sensitive workloads.

Q: Can I run Karpenter with Cluster Autoscaler?
A: Technically yes. Practically no. They fight over node management. Choose one. Karpenter is strictly better for cost optimization.

Q: What's the minimum cluster size for Karpenter to make sense?
A: 10 nodes. Below that, the optimization gains are eaten by Karpenter's own resource overhead (it runs a pod and a few ConfigMaps). At 5 nodes, just use a reserved instance and save the complexity.

Q: Does Karpenter reduce Kubernetes costs for batch processing?
A: Significantly. Batch workloads are spiky by nature. Karpenter's sub-second provisioning means you only pay for compute when a job is running. One client cut their Spark cluster costs from $45K to $11K by moving to Karpenter + spot.


The Bottom Line

The Bottom Line

How to reduce kubernetes costs with karpenter comes down to three things:

  1. Fix your resource requests before you install anything
  2. Design provisioners for workload profiles, not one-size-fits-all
  3. Use spot strategically and monitor the right metrics

Karpenter isn't magic. It's a bin-packing algorithm that buys you 30-50% cost reduction if you use it correctly. It's a savings destroyer if you don't.

I've run this playbook in production since Karpenter hit v1.0 in early 2024. Every cluster I've migrated saw cost drops of 35% or more. Teams got their weekend back. On-call stopped getting paged about node issues.

But here's the thing I keep coming back to: Kubernetes isn't dead. The way people configure it is.

Treat Karpenter as an enabler, not a solution. Fix the architecture. Right-size the workloads. Then let Karpenter do what it does best — convert your cloud bill into actual work getting done.


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