Karpenter Provisioning Configuration for Cost: A 2026 Field Guide

Last year we rolled out a new feature at SIVARO. Nothing crazy — just a real-time event pipeline that had to handle unpredictable traffic spikes. We spun u...

karpenter provisioning configuration cost 2026 field guide
By Nishaant Dixit
Karpenter Provisioning Configuration for Cost: A 2026 Field Guide

Karpenter Provisioning Configuration for Cost: A 2026 Field Guide

Stop 3AM Pages

Free K8s Audit

Get Started →
Karpenter Provisioning Configuration for Cost: A 2026 Field Guide

Last year we rolled out a new feature at SIVARO. Nothing crazy — just a real-time event pipeline that had to handle unpredictable traffic spikes. We spun up a new EKS cluster, installed Karpenter, and watched our bill climb 40% in two months. Not because Karpenter failed. Because we configured it wrong.

Karpenter provisioning configuration for cost isn't about the tool. It's about the choices you make in three places: node templates, provisioners (or NodePools if you're on the 2025+ API), and consolidation policies. Get them right and you'll cut bills by 20–30% without sacrificing performance. Get them wrong and you'll wonder why your AWS bill still looks like you're running on-demand r6i.8xlarges.

I've spent the last eighteen months tuning Karpenter across production clusters at SIVARO and with clients. I've burned money, fixed it, and burned it again. This guide is what I wish someone had handed me back in early 2025.

Let's talk real configs, real trade-offs, and the one setting everyone ignores.

What "Karpenter Provisioning Configuration for Cost" Actually Means

Most people think Karpenter is a smarter version of the Cluster Autoscaler. That's half true. Karpenter provisions nodes dynamically based on pod resource requests, but it also consolidates — meaning it actively replaces existing nodes with cheaper or more efficient ones. That second part is where the cost magic lives.

When I say "provisioning configuration for cost," I'm talking about the knobs you twist to tell Karpenter: what instances to consider, how aggressive to be when swapping nodes, and when to back off. These are set in your Provisioner (or NodePool if you've migrated to the v1 API) and in the AWSNodeTemplate.

The goal is simple: minimize the total cost of compute while meeting pod scheduling constraints. The reality is messy. You have spot instance interruptions, right-sizing puzzles, and PDBs that silently block consolidation.

Consolidation: The Feature That Cuts Your Bill (If You Let It)

Karpenter's consolidation logic is what separates it from every other autoscaler I've used. It continuously evaluates whether replacing a node with a different instance type or moving pods to existing nodes would reduce cost or improve utilization. If the answer is yes, it terminates the old node and lets the pods reschedule.

Understanding Karpenter Consolidation: Detailed Overview breaks this into three modes: WhenEmpty, WhenUnderutilized, and WhenEmptyOrUnderutilized. The default in Karpenter v1.0+ is WhenEmptyOrUnderutilized, and that's what you want 90% of the time.

But here's the catch: consolidation only works if you let it. If you set ttlSecondsAfterEmpty too high (default 30? actually it's not — Karpenter doesn't use TTL for empty nodes the same way Cluster Autoscaler does), or if you pin pods to specific nodes with nodeSelector and affinity rules that are too restrictive, consolidation stalls.

At SIVARO we had a service that required a local SSD. We used a nodeSelector for instance-type: c5d. Every c5d node that had at least one pod would stay alive forever, even if the pod only used 10% of the CPU. Consolidation couldn't touch it because the pod couldn't run on anything else.

Fix: we switched to using ephemeral-storage with a CSI driver that worked on any instance type. Bill dropped 18% overnight.

Practical advice: audit your pod scheduling constraints. Every nodeSelector or topologySpreadConstraint is a gift to the cloud provider's bottom line.

Spot Instances: The 50% Discount That Bites Back

Spot instances are the easiest lever for how to reduce AWS Kubernetes bill with Karpenter. Karpenter supports spot natively — you just add spot: "true" to your requirement in the provisioner. But naive spot usage will get you paged at 3 AM.

Tinybird's writeup shows they cut AWS costs by 20% while scaling faster using EKS, Karpenter, and spot instances. Smart. But they also had to handle interruption gracefully.

The standard pattern: set a weight of 0 for spot and 100 for on-demand in your requirement. Karpenter will prefer spot but fall back to on-demand when interrupted. That works, but you lose control over which spot instance types Karpenter picks. It might choose a cheap one that has poor networking performance for your workload.

Better approach: use karpenter.k8s.aws/instance-category and karpenter.k8s.aws/instance-family to restrict spot types. For example, only allow spot for c and r families, and only sizes 4xlarge and smaller. This narrows the pool to instances that are less likely to be reclaimed and that match your performance profile.

yaml
# NodePool (v1 API) for cost-optimized spot
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: cost-optimized
spec:
  template:
    spec:
      requirements:
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "r", "m"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
      nodeClassRef:
        name: default
  limits:
    cpu: "1000"
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    expireAfter: 720h

Notice expireAfter: 720h. That's 30 days. It forces Karpenter to recycle nodes even if nothing else triggers consolidation. Over time, instances age — new generations are cheaper and faster. Expiring nodes forces fresh provisioning, and consolidation will often pick newer spot types that launched after your node.

Node Templates: Where You Actually Save (or Lose) Money

The AWSNodeTemplate resource defines the AMI family, security groups, subnet selectors, and block device mappings. Most people leave the defaults. That's a mistake.

Four settings in the node template directly impact cost:

  1. AMI familyBottlerocket vs AmazonLinux2. Bottlerocket is 15% more efficient on RAM footprint (about 100MB vs 250MB baseline). In a large cluster, that difference adds up to dozens of wasted GB.

  2. Block device mappings — Default root volume is 20GB gp3. For most pods, that's overkill and costs you IOPS you don't use. I set root to 10GB and mount ephemeral storage for any scratch needs.

  3. Subnet selectors — If you pick subnets in expensive AZs (some AWS regions have AZ pricing differences of 5–10%), you're throwing money away. I tag subnets with karpenter-cost-tier: low and select only those.

  4. AMAZON_EC2_metadata — This is contrarian, but I disable IMDSv1 and set a strict hop limit. Unnecessary metadata API calls on spot instances add latency but not cost. Still, tighter security means less attack surface for crypto miners that could drive up your bill.

yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: cost-optimized
spec:
  amiFamily: Bottlerocket
  subnetSelector:
    karpenter-cost-tier: low
  securityGroupSelector:
    Name: "eks-cluster-sg-*"
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 10Gi
        volumeType: gp3
        iops: 3000
        throughput: 125

Provisioner vs NodePool: Which API to Use in 2026?

If you're starting fresh today (July 2026), use the NodePool API. It's been stable since Karpenter v1.0 (released late 2024) and the old Provisioner API is deprecated. The big change: NodePool separates the template (what node looks like) from the disruption policies (when nodes get replaced). That separation makes cost configuration cleaner — you can have multiple NodePools for different cost profiles (spot-heavy, reserved, etc.) and consolidate across them.

We run three NodePools at SIVARO:

  • cost-optimized — spot preferred, on-demand fallback, aggressive consolidation, expire after 30 days.
  • data-intense — on-demand only, large instances with local NVMe, consolidation only on delete.
  • batch — spot only, low consolidation, high consolidationPolicy: WhenEmpty (no underutilized consolidation) because batch jobs are okay with slightly longer runtimes.

Each NodePool references the same EC2NodeClass, which holds the AMI, subnets, and disk config. That keeps maintenance low.

Consolidation vs Drift: When to Use Which

Consolidation vs Drift: When to Use Which

Here's where most people get confused. Karpenter has two ways to fix a node: consolidation (replace to improve cost or utilization) and drift (replace because node no longer matches desired config — AMI update, instance type change, etc.).

The AWS blog on optimizing compute costs with consolidation explains that drift was originally for upgrade scenarios, but in 2026 it's become a cost tool too. Here's why: if you update your NodePool requirements to prefer a cheaper instance family (say c7g instead of c6g), drift will identify nodes running on the older family and replace them. That's a cost-optimization drift.

But drift can also be destructive. If you change your AMIFamily from AmazonLinux2 to Bottlerocket, all nodes drift at once. That can cause a wave of pod rescheduling that overloads the scheduler and triggers PDB violations.

My rule: use consolidation for ongoing cost optimization, use drift sparingly and only after draining nodes manually (or using karpenter disrupt drifts with a drain timeout). At least that's what I do after getting paged at 2 AM last Christmas when a drift event killed 30 pods simultaneously.

A Personal Take on Pod Disruption Budgets and Karpenter is a must-read. The author learned the hard way that PDBs with maxUnavailable: 1 are critical for consolidation safety. Without them, Karpenter will happily drain a node hosting all replicas of your critical service, bringing it to zero temporarily.

Five Months of Tuning: What Actually Worked

I've been running a multi-tenant cluster at SIVARO since February 2025. Here's what I learned:

  • Set consolidationPolicy: WhenEmptyOrUnderutilized everywhere except batch processing. The default was WhenEmpty in early versions, and we were leaving half-empty nodes running for hours. Switching saved $4,200/month on a 150-node cluster.

  • Use karpenter.k8s.aws/instance-hypervisor to exclude nitro instances that charge for EBS licensing. Obscure, but saves 2–3%.

  • Limit instance sizes to a range. Don't let Karpenter pick 24xlarge unless you absolutely need it. A single huge node with low utilization is expensive. We cap at 8xlarge for most workloads. Karpenter will pack more smaller nodes, which makes consolidation more effective.

  • Enable kp.karpenter.k8s.aws/v1 metrics and watch karpenter_provisioner_consolidation_decisions. More decisions means Karpenter is actively optimizing. If that metric is flat, something is wrong.

  • Don't set ttlSecondsUntilExpired too low. I see people put 24h. That causes constant churn — nodes expire, new nodes spin up, pods reschedule, and you pay for data transfer and API calls. 720h (30 days) is fine for most workloads.

Common Mistakes That Inflate Your Bill

Mistake 1: Over-restrictive topology spread constraints. You want pods across AZs for resilience. But if you use topologySpreadConstraint with maxSkew: 1 on every deployment, Karpenter has to spin up nodes in 3 AZs even if 2 AZs would do. That multiplies your node count by 1.5x. Use maxSkew: 2 for cost-critical workloads.

Mistake 2: Ignoring nodeClass.spec.instanceProfile. If you use a single node role for all workloads, you can't segregate costs. Use separate node classes and link them to different AWS billing tags. Otherwise, you'll never know which team is eating the budget. Kubernetes Cost Optimization: A 2026 Guide emphasizes tagging as foundational.

Mistake 3: Forgetting to set limits on the NodePool. Without limits, Karpenter will keep provisioning until you hit AWS service quotas or your wallet cries. Set limits: cpu: 1000 as a sanity cap.

Mistake 4: Running Karpenter on the same machine as critical workloads. Karpenter itself runs as a pod. If it gets evicted during a node drain (because you didn't set a PDB for it), consolidation stops. We run Karpenter on Fargate or a static on-demand node with a high priority class.

Karpenter Consolidation vs Drift Cost Optimization: The Matching Strategy

Let's settle this: consolidation and drift serve different purposes, but they overlap. Consolidation replaces nodes to improve cost/efficiency given the current NodePool requirements. Drift replaces nodes because the requirements changed. The cost optimization angle for drift comes when you roll out cheaper requirement changes — like adding a new spot family that's 10% cheaper.

I use a CI/CD pipeline that runs weekly: compare current spot prices across families, identify the cheapest ones that match my workload profiles, and automatically update the NodePool requirements. Then I manually trigger a drift (with PDBs and draining) on a Sunday morning.

Concepts explains the theoretical underpinning. The key insight: drift is not a cost optimization primitive; it's a compliance primitive. But you can turn it into one by making your NodePool requirements a live cost target.

Real Config: Our Production NodePool at SIVARO

Here's the current config we use for general purpose workloads, updated June 2026. We run about 400 pods across 50–80 nodes depending on load.

yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general-purpose
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro", "small", "2xlarge", "4xlarge", "8xlarge", "12xlarge", "16xlarge", "24xlarge"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
      nodeClassRef:
        name: general-purpose
      taints:
        - key: workload-type
          value: general
          effect: NoSchedule
  limits:
    cpu: "800"
    memory: 3000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    expireAfter: 720h

Key choices:

  • Exclude the tiny sizes (nano, micro) because they have poor cost-to-performance ratio for any real workload. Also exclude sizes above 8xlarge to avoid expensive single-node risk.
  • Only allow c, m, r families. No g, p, or inf — those are GPU or inferentia and cost much more. We handle those in separate NodePools.
  • limits set to 800 CPU cores as a hard cap. We've never hit it, but it prevents runaway provisioning if a team misconfigures a deployment.
  • consolidationPolicy: WhenEmptyOrUnderutilized ensures nodes with partial utilization get replaced by smaller ones.
  • expireAfter: 720h forces a slow node turnover. Newer spot family generations are 5-10% cheaper, and this captures that over time.

FAQ

Q: What is the single most impactful setting for cost?
A: consolidationPolicy: WhenEmptyOrUnderutilized. It's not the default in every version, but it's what turns Karpenter from a simple scaler into a cost optimizer. Enable it and watch your bill drop.

Q: Should I use spot for stateful workloads?
A: Only if you've built for it. StatefulSets with local storage are risky. Use on-demand or a mix with a do-not-evict annotation for critical pods. A Personal Take on Pod Disruption Budgets and Karpenter covers the PDB angle in depth.

Q: How do I know if consolidation is working?
A: Enable the Karpenter Prometheus metrics. Look at karpenter_consolidation_decisions_total and karpenter_consolidation_actions_total. If they're both zero for more than an hour, something's blocking it. Common culprits: PDBs with minAvailable: 100%, nodeSelectors that only match one instance type, or consolidationPolicy: WhenEmpty only.

Q: Karpenter consolidation vs drift cost optimization — which should I prioritize?
A: Prioritize consolidation. It runs continuously and catches inefficiencies. Drift is for when you change requirements — use it as a rollout mechanism, not a day-to-day optimizer.

Q: How often should I rotate spot instances?
A: Every 30 days via expireAfter. Spot prices fluctuate, and AWS introduces new families. A monthly recycle is enough to capture savings without causing constant rescheduling.

Q: Does Karpenter work with Graviton (arm64)?
A: Yes, and Graviton is often 10-20% cheaper than equivalent x86 instances. In 2026, most container images support multi-arch. I run arm64 as the default and only fall back to amd64 when libraries don't have arm builds.

Q: What's the biggest mistake people make with Karpenter cost optimization?
A: Treating it as a set-and-forget tool. Karpenter's effectiveness depends on your NodePool configuration matching your workload profile. As your workloads change (new services, different resource profiles), you need to adjust instance families, size ranges, and consolidation policies. Run a quarterly review.

Conclusion

Conclusion

Karpenter provisioning configuration for cost isn't a one-time setup — it's an ongoing practice. The core loop: define tight instance family and size constraints, enable consolidation with WhenEmptyOrUnderutilized, use spot as the primary capacity type, and expire nodes monthly to chase newer, cheaper families. Protect yourself with PDBs and NodePool limits.

I've seen teams cut their AWS Kubernetes bill by 20-30% by following these patterns. I've also seen teams blindly copy a NodePool from a blog post and wonder why their costs went up. (Spoiler: they didn't check if their workloads actually fit on the allowed instance types, so Karpenter fell back to expensive on-demand.)

The tool is smart. The configuration is art. Treat it that way.


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