By Raúl Pérez · Last updated: October 2026 · Reading time: 13 min
Kubernetes makes it easy to ship software. It also makes it easy to waste money without noticing. A developer copies a manifest with generous resource requests, the scheduler dutifully reserves that capacity, and the cluster autoscaler adds nodes to fit it. Nobody ever sees a cost line for “requested but never used.” It just shows up in the cloud invoice.
This guide explains how Kubernetes spending really works and gives you a practical, ordered playbook for reducing it: measure first, then right-size, then autoscale, then change how you buy compute, and finally build the habits that keep costs down. It applies to EKS, AKS and GKE, plus self-managed clusters, with examples you can adapt today.
Why Kubernetes Costs Spiral
There are a few structural reasons clusters tend to be expensive:
- Requests drive billing, not usage. The scheduler places pods based on resource requests. Nodes are sized and added to satisfy requests. If a pod requests 2 CPUs and uses 0.2, you still pay for the 2.
- Shared infrastructure hides ownership. In a multi-tenant cluster, one invoice covers dozens of teams. Without allocation, nobody feels responsible.
- Defaults are generous. Copy-pasted manifests, Helm chart defaults and “just to be safe” limits inflate footprints.
- Idle capacity is invisible. Empty space on nodes, unallocated CPU and memory, and forgotten namespaces do not trigger alerts.
- Autoscaling scales up eagerly and down cautiously. Without tuning, clusters ratchet upward and stay there.
Industry surveys regularly report that a large share of requested Kubernetes resources goes unused. Exact figures vary widely by organization, so measure your own cluster rather than relying on a headline number.
The Kubernetes Cost Equation
It helps to think of cluster cost in layers:
Total cost = Nodes (compute) + Storage + Networking + Control plane + Add-ons/tools
For most teams, nodes are 60 to 80 percent of the bill. So the central question is: how many nodes do we run, of what type, at what price, and how full are they?
That breaks into four levers:
- Reduce what pods request (right-sizing)
- Match node count to demand (autoscaling and bin packing)
- Pay less per node (Spot, commitments, Arm, instance selection)
- Eliminate what should not exist (idle workloads, orphaned storage, over-replication)
Step 1: Get Visibility Before You Optimize
You cannot reduce what you cannot see. Start by answering three questions: who spends what, how much of it is waste, and where is the idle capacity?
Tools for Kubernetes cost visibility
| Tool | Type | Strengths |
|---|---|---|
| OpenCost | Open source (CNCF project) | Vendor-neutral cost allocation by namespace, workload, label |
| Kubecost | Commercial with free tier, built on OpenCost | Dashboards, savings recommendations, multi-cluster views |
| Cloud provider tools | AWS split cost allocation data, Azure AKS cost analysis, GKE cost allocation | Native billing integration |
| Prometheus + Grafana | DIY | Maximum flexibility, more setup effort |
Features, limits and pricing change, so check each project’s current documentation. If you want a deeper side-by-side look, see our comparison of [OpenCost vs Kubecost].
Quick wins with plain kubectl
Before installing anything, see how requested versus actual usage looks right now (requires metrics-server):
# Actual usage per node
kubectl top nodes
# Actual usage per pod, all namespaces, sorted by CPU
kubectl top pods -A --sort-by=cpu
# What nodes have allocated (requests) vs capacity
kubectl describe nodes | grep -A 8 "Allocated resources"
If nodes show 80 percent of CPU allocated but kubectl top nodes shows 15 percent used, you have found your savings.
Build the key metrics
Track these as standing dashboards:
- Cluster utilization: used CPU and memory divided by node capacity.
- Request efficiency: used divided by requested, per workload.
- Idle cost: the cost of capacity that is neither requested nor used.
- Cost per namespace, team and environment.
- Unit cost: cost per request, per customer or per transaction, if you can attribute it.
Step 2: Right-Size Requests and Limits
This is usually the single highest-impact change, and the one most often skipped.
Understand requests vs limits
- Requests are what the scheduler reserves for a pod. They influence node count and cost directly.
- Limits are the maximum a container may use. CPU limits cause throttling; memory limits cause OOM kills.
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 512Mi
A widely used practice is to set CPU requests based on realistic usage, set memory requests equal to memory limits (memory is not compressible, so overcommitting it is risky), and consider omitting CPU limits for latency-sensitive services to avoid unnecessary throttling. This is a topic of real debate in the community, so test against your own workloads. We cover the trade-offs in depth in our article on [preventing waste from over-provisioned pods and CPU limits].
Use real data to size
Collect at least one to two weeks of usage, including peaks, then set requests near the 90th to 95th percentile of observed usage plus a safety margin. Prometheus queries help:
# 95th percentile CPU usage per container over 14 days
quantile_over_time(0.95,
rate(container_cpu_usage_seconds_total{container!=""}[5m])[14d:5m])
# Peak memory working set per container over 14 days
max_over_time(container_memory_working_set_bytes{container!=""}[14d])
Automate with the Vertical Pod Autoscaler
The Vertical Pod Autoscaler (VPA) observes usage and recommends, or applies, new requests. Start in recommendation mode so you can review before changing anything:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: checkout-api
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout-api
updatePolicy:
updateMode: "Off" # recommendations only
kubectl describe vpa checkout-api
Two cautions. First, do not combine VPA with an HPA on the same CPU or memory metric for the same workload, because they will fight. Second, applying VPA changes in Auto mode traditionally restarts pods to update resources, so use it deliberately on stateless services with disruption budgets. Newer Kubernetes versions are adding in-place pod resize capabilities, so check what your version supports.
Open-source tools such as Goldilocks (which runs VPA in recommendation mode and shows a dashboard) make this easier for teams that prefer not to hand-edit manifests.
Enforce sane defaults with LimitRange and ResourceQuota
Prevent the next oversized deployment before it happens:
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: team-a
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
memory: 256Mi
max:
cpu: "4"
memory: 8Gi
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
requests.cpu: "40"
requests.memory: 160Gi
persistentvolumeclaims: "20"
Quotas turn an invisible shared pool into a visible budget per team.
Step 3: Autoscale at Three Levels
Effective Kubernetes autoscaling works on pods, nodes and sometimes the clock.
Horizontal Pod Autoscaler (HPA)
Scale replicas with demand instead of running peak capacity all day:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: checkout-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout-api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
behavior:
scaleDown:
stabilizationWindowSeconds: 300
For event-driven or queue-based workloads, KEDA can scale on external signals (queue length, Kafka lag, cron schedules) and can scale deployments to zero, which is excellent for batch and non-production services.
Node autoscaling
Pods are only half the story. If pods shrink but nodes stay, you save nothing. Two main options exist:
- Cluster Autoscaler: the long-standing, widely supported approach that adjusts the size of predefined node groups.
- Karpenter: a newer node provisioner that launches right-sized instances directly based on pending pods and actively consolidates underused nodes. It is most mature on AWS, and Azure offers a Karpenter-based node auto provisioning feature for AKS.
We compare them in detail in [Karpenter vs Cluster Autoscaler: Cost Efficiency Comparison]. For cost, the key capability to look for is consolidation: automatically replacing several half-empty nodes with fewer, fuller ones.
Time-based scaling
Non-production clusters rarely need to run at night or on weekends. Options include scaling user node pools to zero on a schedule, using KEDA’s cron scaler, or stopping dev clusters entirely outside working hours. A dev environment running 50 hours a week instead of 168 can see a large drop in compute spend.
Step 4: Improve Bin Packing
Bin packing is how efficiently pods fill nodes. Poor packing leaves stranded capacity: a node with 1 free CPU and 10 GB free memory cannot host a pod that needs 2 CPUs.
Practical improvements:
- Right-size requests first. Packing quality depends on honest requests.
- Use fewer, larger nodes where appropriate. Each node carries overhead (kubelet, system daemons, DaemonSets). Larger nodes dilute that overhead, though they increase the blast radius of a failure.
- Choose node shapes that match your workloads’ CPU-to-memory ratio. Memory-heavy services on CPU-heavy instances strand CPU.
- Audit DaemonSets. A DaemonSet requesting 500m CPU on 100 nodes reserves 50 CPUs. Trim logging and monitoring agents.
- Use topology spread and affinity rules sparingly. Strict anti-affinity and spread constraints can prevent dense packing.
- Set Pod Disruption Budgets sensibly. Overly strict PDBs can block node consolidation and scale-down.
Step 5: Pay Less Per Node
Once you run the right amount of compute, reduce the unit price.
Spot and preemptible instances
Spot capacity is typically offered at a steep discount compared with on-demand, in exchange for the provider’s ability to reclaim it on short notice (about two minutes on AWS). It is a strong fit for stateless web tiers with multiple replicas, CI/CD runners, batch jobs and data processing.
Safe Spot patterns:
- Diversify across many instance types and availability zones.
- Run critical, stateful or singleton workloads on on-demand nodes.
- Handle interruption signals (the AWS Node Termination Handler or Karpenter’s built-in handling).
- Set
terminationGracePeriodSecondsand readiness checks so pods drain cleanly.
# Steer a tolerant workload onto Spot nodes
spec:
nodeSelector:
karpenter.sh/capacity-type: spot
tolerations:
- key: "spot"
operator: "Exists"
Read [Spot Instances for Kubernetes: Risks, Savings, and Best Practices] before moving production traffic.
Commitments for the baseline
For the always-on core, use Savings Plans or Reserved Instances (AWS), reservations or savings plans (Azure) or committed use discounts (GCP). Commit only to the stable floor that you are confident will exist, after right-sizing. Autoscaled, bursty capacity should stay flexible.
Arm-based instances
Arm processors (AWS Graviton, Azure Cobalt/Ampere, GCP Tau T2A and Axion) often deliver better price-performance for many workloads. If your images are multi-architecture (or can be built that way), test Arm node pools. Always benchmark your own application, because gains vary.
Pick the right instance families
Newer generations are often cheaper per unit of performance. Avoid burstable instances for sustained workloads, and avoid GPU instances for anything that does not need a GPU.
Step 6: Eliminate Waste and Orphans
Hunt for things that cost money while doing nothing:
- Idle namespaces and workloads: deployments with near-zero CPU and network for 30 days.
- Orphaned PersistentVolumes: volumes left behind when claims were deleted.
- Unused load balancers: each cloud load balancer has an hourly cost. Consolidate with an ingress controller where possible.
- Over-replication: three replicas in dev, or a replica count of 10 for a service that peaks at 2 pods of work.
- Old container images and logs: storage and log ingestion can quietly become major line items.
- Oversized observability: high-cardinality metrics and verbose debug logging can cost as much as the compute they monitor.
# Find PVs that are not bound to any claim
kubectl get pv | grep -E 'Released|Available'
# Find Services of type LoadBalancer (each may cost money)
kubectl get svc -A | grep LoadBalancer
# Find deployments scaled to zero that may be candidates for removal
kubectl get deploy -A -o json | jq -r '.items[] | select(.spec.replicas==0) | "\(.metadata.namespace)/\(.metadata.name)"'
Put a label such as owner and an optional ttl on every namespace so ownership is clear when it is time to clean up.
Step 7: Watch Networking, Storage and Cross-Zone Traffic
Compute gets the attention, but hidden line items add up:
- Cross-AZ data transfer: in many clouds, traffic between availability zones is billed. Chatty microservices spread across zones can generate substantial charges. Topology-aware routing can keep traffic local where it is safe to do so.
- NAT gateway and egress: pulling images and calling external APIs through NAT can be costly. Use VPC endpoints or private registries and caches where possible.
- Storage classes: use the cheapest class that meets performance requirements and enable volume expansion rather than overprovisioning on day one.
- Control plane fees: managed clusters often charge per cluster per hour. Fifty tiny clusters cost more in fees than five well-organized ones, so weigh isolation needs against overhead.
Step 8: Build a FinOps Operating Model for Kubernetes
Tools alone do not hold the savings. The FinOps Foundation describes a cycle of Inform, Optimize and Operate, and it maps cleanly onto Kubernetes:
- Inform: label everything (
team,app,env,cost-center), allocate costs per namespace and workload, and send teams showback reports. - Optimize: right-size, autoscale, adopt Spot, and use commitments.
- Operate: set budgets and alerts, review costs in sprint or monthly meetings, and add cost checks to CI.
Concrete habits that work:
- Standard labels enforced by an admission policy (Kyverno or OPA Gatekeeper).
- Budgets per team tied to ResourceQuotas.
- Cost in pull requests, using tools that estimate the cost impact of infrastructure changes.
- A monthly efficiency review of the top 10 least efficient workloads.
- A published unit-cost metric so improvements are visible.
# Kyverno policy: require a team label on Deployments
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-team-label
spec:
validationFailureAction: Enforce
rules:
- name: check-team-label
match:
any:
- resources:
kinds: ["Deployment"]
validate:
message: "The label 'team' is required for cost allocation."
pattern:
metadata:
labels:
team: "?*"
A Prioritized Kubernetes Cost Optimization Checklist
Week 1: Measure
- Install OpenCost or Kubecost, or enable your cloud provider’s cost allocation.
- Record baseline cost, utilization and request efficiency.
- Identify the top 10 costly namespaces and workloads.
Weeks 2 to 3: Right-size
- Deploy VPA in recommendation mode and review results.
- Reduce requests on the largest over-provisioned workloads.
- Add LimitRanges and ResourceQuotas.
Week 4: Autoscale and consolidate
- Configure HPAs and consider KEDA for event-driven services.
- Enable node consolidation (Karpenter, or tune Cluster Autoscaler scale-down).
- Review PDBs and DaemonSets blocking consolidation.
Month 2: Change the price
- Move fault-tolerant workloads to Spot.
- Test Arm node pools.
- Cover the stable baseline with commitments.
Ongoing
- Schedule non-production shutdowns.
- Clean orphaned volumes and load balancers.
- Run monthly efficiency reviews and publish team showback.
Common Kubernetes FinOps Mistakes
- Optimizing nodes before pods. Autoscalers cannot fix inflated requests.
- Cutting requests without headroom. Aggressive trimming causes OOM kills and latency, which cost more than the savings.
- Putting everything on Spot. Interruptions on a single-replica stateful service are outages.
- Ignoring non-production. Dev and staging often make up 30 to 50 percent of cluster spend in some organizations.
- One-time cleanups. Without governance, waste returns within a quarter.
- Chasing savings without a reliability guardrail. Track SLOs alongside cost so optimization never silently degrades users’ experience.
Frequently Asked Questions
What is Kubernetes FinOps? Kubernetes FinOps applies FinOps principles, meaning shared accountability, visibility and continuous optimization, to containerized workloads, so engineering, finance and product teams can make informed cost decisions.
How much can I realistically save on Kubernetes? It depends on your starting point. Clusters that have never been optimized often have substantial headroom from right-sizing, consolidation and Spot adoption, while mature clusters will see smaller gains. Measure your baseline and set targets from your own data.
Should I remove CPU limits? Many teams do for latency-sensitive services because CPU limits can cause throttling even when the node has spare CPU. Others keep them for multi-tenant fairness. Test with your workload, and always keep memory limits.
Is OpenCost enough, or do I need Kubecost? OpenCost provides the core allocation engine for free. Kubecost adds a richer UI, recommendations and enterprise features. Many teams start with OpenCost and upgrade only if they need more.
Do service meshes and sidecars affect cost? Yes. Each sidecar requests CPU and memory per pod, which multiplies across thousands of pods. Review sidecar resource requests, and evaluate sidecar-less or ambient modes where they fit.
Conclusion
Kubernetes cost optimization is not a one-time project. It is a loop: measure who spends what, right-size what pods ask for, let autoscalers match capacity to demand, buy that capacity more cheaply, and keep teams accountable with budgets and showback. Start with visibility and right-sizing, since those deliver the biggest gains with the lowest risk, then layer on consolidation, Spot and commitments.
