- Karpenter optimizes infrastructure supply, not workload demand, so it faithfully provisions capacity for whatever pods request, even when those requests are inflated.
- The most common sources of waste are the request-to-reality gap, pod placement fragmentation that blocks consolidation, and oversized storage attached to every node.
- Fixing these requires closing the gap between what workloads ask for and what they actually use, at the pod level, the placement level, and the storage level.
- Zesty addresses each of these gaps directly: Multi-Dimensional Autoscaling closes the request-to-reality gap, Adaptive Pod Placement solves the fragmentation Karpenter can’t reach on its own, PV Autoscaling removes oversized storage, and FastScaler makes it safe to run leaner clusters by absorbing traffic spikes in seconds.
Karpenter has become the autoscaler of choice for many Kubernetes platform teams, and for good reason. It provisions nodes in real time, selects efficient instance types, consolidates underutilized capacity, and removes much of the operational burden associated with traditional node groups.
On paper, the value proposition is compelling: the right nodes, at the right time, for the right price. Many teams expect that adopting Karpenter will significantly reduce their infrastructure costs. Often, the cloud bill barely changes.
If you’ve automated scaling but haven’t seen meaningful savings, the issue usually isn’t Karpenter itself. More often, it’s a combination of request inflation, placement fragmentation, and storage overprovisioning that erode the expected benefits before they reach the invoice.
This is a common enough pattern that it’s worth naming clearly: teams migrate from static node groups to Karpenter, watch the automation work exactly as advertised, and then open the bill a month later to find it looks almost identical to before. The automation isn’t broken. It’s optimizing the wrong input. This article walks through where the money actually goes, how to detect each source of waste in your own cluster, and what closes each gap.
First Things First: Is Karpenter Actually Saving You Money?
Before diving into root causes, check a few simple signals.
Signs your cluster is optimized:
- Node CPU utilization typically above roughly 50%
- Node counts drop during off-peak hours
- Frequent node consolidation events
- Spot usage increases after migrating to Karpenter
Signs savings are limited:
- Nodes show high allocation but low actual usage
- Node counts stay similar day and night
- Many large instances running at low CPU utilization
- Consolidation rarely occurs
If the second pattern describes your cluster, the causes below are usually why.
Karpenter’s Strength and Its Blind Spot
Karpenter is excellent at optimizing infrastructure supply. It reacts quickly to pending pods, selects efficient instance types, and consolidates nodes when it can. But Karpenter is intentionally literal: it provisions capacity based on what Kubernetes workloads request, not what they actually use.
That design is powerful, and it’s also where the cost problem starts. If workloads request more than they need, Karpenter will faithfully provision infrastructure to satisfy those requests. The scheduler is doing its job correctly. The requests it’s reading are the problem.
It helps to be precise about what Karpenter is and isn’t responsible for here. Karpenter’s job ends at translating pending pod requests into the most efficient set of nodes that can satisfy them. It has no visibility into whether those requests reflect real usage, no mechanism for questioning a request that’s five times larger than what the pod actually consumes, and no way to move a pod that’s already scheduled somewhere inconvenient. Those are workload-level and placement-level problems, sitting one layer above where Karpenter operates, and they need to be solved there rather than by tuning Karpenter’s own configuration further.
The Request-to-Reality Gap
The most common source of waste is simple: workloads ask for far more resources than they consume. If a service requests 2 vCPUs but consistently uses 0.2, Karpenter will still provision capacity for the full request. From the scheduler’s perspective, the node is full. From the cloud provider’s perspective, the instance is mostly idle.
How to detect it: compare requested resources against actual usage over time.
Relevant metrics:
kube_pod_container_resource_requests
container_cpu_usage_seconds_total
Example comparison:
sum(rate(container_cpu_usage_seconds_total[5m]))
vs
sum(kube_pod_container_resource_requests{resource="cpu"})
Classic waste signal: nodes sitting at roughly 90-95% allocated, with instance CPU utilization around 10-20%. That gap is ghost capacity, and it’s one of the largest hidden cost drivers in Kubernetes environments.
This pattern tends to get worse over time rather than better, for a structural reason: requests are usually set once, at initial deployment, based on a rough estimate or a copy-pasted value from a similar service. From that point on, they rarely move unless something forces the issue. An incident that traces back to throttling prompts an increase. A migration to a new instance family prompts a round number. Nobody goes back afterward to check whether the new value is still accurate months later, once traffic patterns and code paths have shifted. The result is a slow, one-directional drift where requests climb but almost never come back down, even as the workloads they describe change shape underneath them.
Closing the gap: pod rightsizing
This is a pod-level problem, and it needs a pod-level fix that keeps pace with how workload behavior actually changes over time. Static requests, set once at deployment and rarely touched again, are the root cause. Manually re-tuning them is possible, but it doesn’t scale past a handful of services and tends to drift back toward over-provisioning after the next incident.
Zesty’s Multi-Dimensional Autoscaling (MDA) addresses this directly. It continuously analyzes real-time usage per pod and automatically adjusts requests to match actual consumption, coordinating both the vertical sizing and the horizontal replica count so the two don’t work against each other. The gap between what’s requested and what’s used narrows, Karpenter is fed accurate numbers, and it responds by provisioning smaller, cheaper nodes for the same workloads, with no developer intervention required to get there.
Pod Placement Fragmentation and Bin-Packing Losses
Even with accurate requests, poor placement can strand capacity. Karpenter solves a complex bin-packing problem when it launches nodes, but pods that can’t be evicted or moved create what shows up as jagged, unusable gaps that accumulate across the cluster.
Example: a node has 16 GB of memory. Pod A uses 7 GB, Pod B uses 7 GB. That leaves 2 GB free. If the next workload requests 3 GB, the scheduler can’t place it on this node, even though the cluster overall has capacity elsewhere. A new node gets created instead.
Across dozens or hundreds of nodes, these small gaps add up to real cluster size.
A related pattern shows up as overly restrictive NodePool configuration. Karpenter’s power comes from choosing across hundreds of instance types, but many teams unintentionally limit that flexibility by carrying over rules from an older Auto Scaling Group setup: restricting to specific instance families, avoiding newer generations, limiting Spot diversification. Broadening those constraints helps, but it doesn’t solve the underlying placement problem on its own, because Karpenter still can’t move or evict pods that are already scattered in ways that block consolidation.
It’s worth separating these two things clearly, because they get treated as the same issue and they aren’t. Loosening NodePool constraints gives Karpenter more instance types to choose from when it’s deciding what to launch next. It does nothing for pods that are already running and already blocking a node from being reclaimed. A pod with a local storage dependency, a restrictive PodDisruptionBudget, or a scheduling constraint that pins it to a specific node can sit there indefinitely, keeping that node alive regardless of how many better instance options Karpenter now has access to. Broader NodePools improve what gets provisioned next. They don’t touch what’s already placed.
Closing the gap: pod placement
This is where Karpenter’s design runs into a real structural limit rather than a tuning problem. Karpenter can only consolidate what it’s able to move, and pods with local dependencies, restrictive scheduling constraints, or disruption protections are effectively frozen in place. No amount of NodePool tuning fixes pods that can’t be relocated.
Zesty’s Adaptive Pod Placement (APP) is built specifically for this gap. It identifies pods that are blocking consolidation and repositions them so the cluster can actually reclaim the capacity Karpenter can see but can’t act on. Rather than replacing Karpenter’s consolidation logic, it clears the obstacles in front of it, closing the fragmentation gap that node-level tooling alone can’t reach.
The Hidden Storage Tax
Compute isn’t the only place costs accumulate. Storage attached to nodes is routinely oversized and rarely adjusted after initial setup. Every node Karpenter launches includes a root volume, and if the default image uses a 100 GB disk while workloads need a fraction of that, the cluster pays for unused storage on every single node.
Example math: 100 nodes at 100 GB provisioned each is 10 TB total. Actual usage is often a small fraction of that.
This pattern extends beyond root volumes to persistent volumes for stateful workloads, which tend to get sized once for a worst-case scenario and never adjusted as usage patterns change. Oversized storage doesn’t just inflate the storage line item either: it can tie pods to specific nodes and block the exact consolidation Karpenter is trying to achieve, generating compute waste on top of the storage waste.
The worst-case sizing habit is understandable. Nobody wants to be the person who under-provisioned a database volume and caused an outage, so teams round up generously and move on. But actual data growth rarely matches the pessimistic estimate used at provisioning time, and the gap between allocated and used storage tends to widen for the same reason pod requests do: there’s no ongoing feedback loop pushing the number back down once the initial risk has passed. Multiply that gap across every stateful workload in a cluster, and storage becomes a cost center that gets far less scrutiny than compute, despite often being just as inflated.
Closing the gap: storage
Right-sizing the root volume baseline and preferring gp3 over gp2 helps at the margins, but it’s a one-time fix for a problem that keeps recurring as workloads and data volumes shift.
Zesty’s PV Autoscaling closes this gap continuously. It automatically rightsizes persistent volumes based on actual usage, keeping storage aligned with real consumption instead of the original worst-case estimate, and reducing the node-pinning side effect that oversized volumes create for cluster consolidation.
Is Your Cluster Actually Elastic?
One of the simplest ways to check your optimization is the off-peak test: look at node counts during low-demand windows, like overnight. If capacity stays high when workloads drop, something is preventing consolidation.
Common blockers:
- Pod Disruption Budgets (PDBs)
- Local storage dependencies
- Overuse of protection labels
- Conservative consolidation policies
Any one of these is enough to keep a node alive well past the point where its workload could have been consolidated elsewhere. Usually it’s not a single blocker but a mix, which is part of why the off-peak test is a useful diagnostic: it doesn’t tell you which blocker is responsible, but it tells you clearly whether one exists at all, before you spend time investigating further.
A particularly important setting:
consolidationPolicy: WhenEmptyOrUnderutilized
Without it, nodes are only replaced when completely empty, which limits potential savings substantially.
The objection this test surfaces
Once the request-to-reality gap, placement fragmentation, and storage overprovisioning are addressed, clusters end up running with much less padding than before. That naturally raises the next question: what happens if there’s a traffic spike and the cluster no longer has that padding to absorb it?
Closing the gap: spike safety
This is the specific problem FastScaler is built to solve. It maintains a pool of hibernated nodes with preloaded container images, ready to reactivate the moment a spike hits, cutting application boot time by up to 5 times compared to a cold scale-up. That speed is what makes it safe to run with reduced pod buffers and a leaner baseline cluster in the first place: the fear that’s been driving over-provisioning gets addressed directly, rather than by leaving the padding in place. Spot capacity can play a supporting role here too, but it’s a secondary detail, not the core mechanism.
How These Gaps Compound
Each of these problems is worth understanding on its own, but they rarely show up in isolation, and they tend to make each other worse. Inflated pod requests mean more nodes get provisioned in the first place. Placement fragmentation means those nodes are harder to consolidate even once requests come down. Oversized storage volumes tie some of those pods to specific nodes, adding another obstacle to consolidation on top of the scheduling constraints already in play. A cluster carrying all three problems at once won’t show meaningful improvement from fixing just one of them, because the other two are still holding the node count up.
This is a common reason teams fix the most visible problem, request inflation, and still don’t see the bill move as much as expected. The nodes that were freed up by smaller pod requests may still be occupied by other pods that can’t be moved, or pinned in place by a persistent volume that was sized for a workload that no longer needs that much space. Addressing all three together, rather than sequentially and in isolation, is what tends to produce compounding rather than partial savings.
Where Workload Intelligence Helps
At this point, a pattern becomes clear: Karpenter optimizes infrastructure supply, but most of the waste originates from workload demand, placement, and storage decisions that were made once and never updated as conditions changed.
Closing that gap requires systems that continuously observe workload behavior and adjust infrastructure decisions accordingly:
- Automatic pod rightsizing that keeps requests aligned with real usage
- Pod placement that clears the way for consolidation Karpenter can’t achieve alone
- Storage that scales with actual data, not initial estimates
- Fast, safe scale-up that makes leaner baselines possible
Zesty layers all four onto Karpenter as a coordinated system rather than four separate tools bolted on independently, so the gains from one don’t get undone by gaps in another.
Frequently Asked Questions
Why isn’t my cloud bill going down after switching to Karpenter? Karpenter optimizes the nodes it provisions based on what your workloads request, not what they actually use. If pod requests are inflated, if pods are scattered in ways that block consolidation, or if storage is oversized, Karpenter will provision efficiently around those inputs, but the inputs themselves stay expensive. The fix happens at the workload and storage level, not by adjusting Karpenter further.
Is it safe to reduce pod requests if I’m worried about traffic spikes? That’s the specific concern FastScaler is built to address. By keeping a pool of hibernated nodes with preloaded container images ready to go, it can absorb a spike and scale up to roughly 5 times faster than a cold start, which is what makes it possible to run with reduced buffers day-to-day without leaving the cluster exposed when demand jumps.
What’s the difference between fixing NodePool constraints and fixing pod placement? NodePool constraints control which instance types Karpenter can choose from for new capacity. Pod placement determines whether pods already running on the cluster can be moved or evicted to allow consolidation. Widening NodePools helps with future provisioning; it does nothing for pods that are already stuck in place blocking a node from being reclaimed.
How much of typical Kubernetes waste comes from storage versus compute? It varies by workload mix, but storage is frequently under-scrutinized relative to compute because it’s provisioned once, for a worst-case scenario, and rarely adjusted afterward. For clusters running a meaningful number of stateful workloads, oversized persistent volumes can represent a significant and easily overlooked share of total waste.
The Bottom Line
Karpenter delivers infrastructure agility. But many Kubernetes cost inefficiencies originate above the infrastructure layer, in the decisions about what workloads request, where pods sit, and how storage is sized. Request inflation, placement fragmentation, and static storage allocation all contribute to wasted capacity that Karpenter, by design, can’t see.
None of these gaps show up as a Karpenter misconfiguration, which is exactly why they’re easy to miss during a rollout. The autoscaler will report healthy consolidation events, efficient instance selection, and responsive scaling, all of which are true, while the underlying inputs feeding those decisions stay inflated. Checking node utilization alone won’t surface this. It takes looking at the request-to-reality gap per pod, checking whether pods are actually movable when the cluster tries to consolidate, and checking whether storage allocations match real data volumes, not the original estimate.
When infrastructure optimization and workload intelligence operate together, clusters can achieve higher utilization, improved reliability, and cost reduction that actually holds over time rather than reverting after the next few deploys. If your cluster scales beautifully but your bill hasn’t moved, the next place to look isn’t node provisioning. It’s the workloads, the placement, and the storage sitting on top of it. That’s the layer Zesty is built to run continuously, so the gains Karpenter makes possible actually reach the bill.
FAQs
Why isn't my cloud bill going down after switching to Karpenter?
Karpenter optimizes the nodes it provisions based on what your workloads request, not what they actually use. If pod requests are inflated, if pods are scattered in ways that block consolidation, or if storage is oversized, Karpenter will provision efficiently around those inputs, but the inputs themselves stay expensive. The fix happens at the workload and storage level, not by adjusting Karpenter further.
Is it safe to reduce pod requests if I'm worried about traffic spikes?
That’s the specific concern FastScaler is built to address. By keeping a pool of hibernated nodes with preloaded container images ready to go, it can absorb a spike and scale up to roughly 5 times faster than a cold start, which is what makes it possible to run with reduced buffers day-to-day without leaving the cluster exposed when demand jumps.
What's the difference between fixing NodePool constraints and fixing pod placement?
NodePool constraints control which instance types Karpenter can choose from for new capacity. Pod placement determines whether pods already running on the cluster can be moved or evicted to allow consolidation. Widening NodePools helps with future provisioning; it does nothing for pods that are already stuck in place blocking a node from being reclaimed.
How much of typical Kubernetes waste comes from storage versus compute?
It varies by workload mix, but storage is frequently under-scrutinized relative to compute because it’s provisioned once, for a worst-case scenario, and rarely adjusted afterward. For clusters running a meaningful number of stateful workloads, oversized persistent volumes can represent a significant and easily overlooked share of total waste.
