Cloud bills rarely explode overnight.
They grow slowly - one oversized deployment, one forgotten development namespace, one idle node pool, one persistent volume that nobody remembers creating. By the time finance starts asking questions, engineering teams are often spending tens of thousands of dollars every month on infrastructure they don’t actually need.
Kubernetes isn’t expensive. Poor Kubernetes engineering is.
Over the last few years I’ve worked with engineering teams running workloads ranging from a handful of services to hundreds of microservices across multiple production clusters. The pattern is almost always the same: requests are copied from Stack Overflow, limits are never reviewed, Cluster Autoscaler is installed but can’t remove nodes, Spot instances are considered “too risky,” and storage volumes live forever.
The result is predictable: a cluster using 40–60% more infrastructure than necessary.
The good news is that reducing Kubernetes costs rarely requires buying new tools. It requires understanding how Kubernetes schedules workloads, how cloud providers charge for compute, and where engineering teams unintentionally waste resources.
This guide covers 15 production-tested strategies that consistently reduce Kubernetes infrastructure costs without sacrificing reliability. Most teams see savings between 20% and 50% within the first few months.
Before You Optimize: Measure First
If someone tells you “our Kubernetes bill is too high,” your first question should always be: which workloads are responsible? Without that answer, you are guessing.
Start by installing one of these cost visibility tools:
| Tool | Best For |
|---|---|
| OpenCost | Open-source Kubernetes cost allocation |
| Kubecost | Enterprise FinOps and chargeback |
| AWS Cost Explorer | Overall AWS billing breakdown |
| Azure Cost Management | Azure Kubernetes Service spend |
| Google Cloud Billing | GKE environment costs |
Once you have visibility, you’ll typically find the spend distributed like this:
| Cost Area | Typical Waste Before Optimization |
|---|---|
| CPU requests | 30–70% over-provisioned |
| Memory requests | 20–60% over-provisioned |
| Persistent storage | 15–40% unused or oversized |
| Network transfer | 5–15% from unoptimized egress |
| Idle workloads | 10–25% from forgotten environments |
With that baseline established, every strategy below has a measurable target.
1. Right-Size Resource Requests
If I could only implement one optimization, this would be it. Most organizations dramatically overestimate CPU and memory requirements - often because the values were copied from documentation or set conservatively years ago and never revisited.
Run this command to see what’s actually happening:
kubectl top pods --all-namespaces
A typical result:
NAME CPU(cores) MEMORY(bytes)
payment-api-xyz 180m 420Mi
auth-service-abc 95m 210Mi
notification-svc 42m 180Mi
Now check what those pods actually requested:
resources:
requests:
cpu: "2" # Reserved: 2000m
memory: "4Gi" # Reserved: 4096Mi
limits:
cpu: "4"
memory: "8Gi"
The payment API requested 2000m and is using 180m - 90% wasted reservation. Kubernetes schedules on requests, not actual usage. Every unused reservation is a slot another pod could occupy, which means fewer nodes needed.
Adjust requests to match P95 observed usage:
resources:
requests:
cpu: "250m" # Matches typical load + headroom
memory: "512Mi"
limits:
cpu: "1" # Allows bursting during traffic spikes
memory: "1Gi"
| Request Setting | Effect on Scheduling | Effect on Cost |
|---|---|---|
| Requests = Limits | Node fills quickly, low utilization | High - more nodes needed |
| Requests too high | Same as above | High - wasted reservation |
| Requests at P95 usage | Efficient bin-packing | Low - fewer nodes |
| Requests too low | OOMKilled or CPU throttled | Risk - instability |
2. Requests vs Limits
Many engineers treat these values as the same thing. They are not. Requests and limits solve completely different problems, and conflating them is one of the most common sources of unnecessary cluster cost.
The problematic pattern:
resources:
requests:
cpu: "2"
limits:
cpu: "2"
When requests equal limits, every pod fully reserves its limit on the node - nothing can burst, scheduling becomes inefficient, and utilization stays low. You end up needing more nodes to run the same workloads.
The efficient pattern:
resources:
requests:
cpu: "300m" # What the pod typically needs
limits:
cpu: "1000m" # Maximum it can use under load
Now multiple pods share the same node cores efficiently, and each can burst when demand spikes - without needing the node to have 1000m exclusively reserved per pod.
| Configuration | Scheduling Efficiency | Burst Capability | Noisy Neighbor Risk |
|---|---|---|---|
| Requests = Limits | Low | None | Low |
| No Limits | High | Unlimited | High |
| Requests < Limits (correct) | High | Controlled | Low |
3. Dedicated Node Pools
A surprising number of production clusters run every workload on identical nodes. The reasoning is usually “simplicity” - but a single node pool means you’re paying the highest applicable cost for every workload, regardless of its actual requirements.
Splitting by workload type is straightforward:
# Assign workloads to specific pools using nodeSelector
nodeSelector:
workload-type: batch
# Or node affinity for more control
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload-type
operator: In
values: ["general"]
| Workload Type | Instance Family | Capacity Type | Why |
|---|---|---|---|
| API servers, general | m6i / m7i | On-Demand | Consistent, latency-sensitive |
| Batch jobs, CI runners | Any (diversified) | Spot | Fault-tolerant, interruptible |
| Memory-heavy apps | r7g / r6i | On-Demand | Right-sized RAM, avoid waste |
| Compute-heavy workloads | c7g / c6i | On-Demand or Spot | CPU optimized |
4. Cluster Autoscaler Tuning
Installing Cluster Autoscaler is easy. Getting it to actually scale down is where most teams fail. The most common symptom: CPU utilization at 18%, node count stays at 22, and the autoscaler logs are full of “cannot remove node” messages.
Inspect what’s happening:
kubectl logs deployment/cluster-autoscaler \
-n kube-system --tail=100 | grep "cannot remove"
Common blockers and their fixes:
# Check for pods with local storage blocking scale-down
kubectl get pods --all-namespaces \
-o json | jq '.items[] | select(.spec.volumes[]?.emptyDir != null) | .metadata.name'
# Review PodDisruptionBudgets that may block eviction
kubectl get pdb --all-namespaces
# Check nodes with no evictable pods
kubectl describe node <node-name> | grep -A5 "Non-terminated Pods"
| Scale-Down Blocker | Why It Blocks | Fix |
|---|---|---|
emptyDir local storage |
Default flag skips these nodes | --skip-nodes-with-local-storage=false |
| PodDisruptionBudget | PDB prevents pod eviction | Review PDB minAvailable values |
| DaemonSets | Non-evictable by design | Expected - factor into capacity |
| Overprovisioned requests | Node looks “needed” | Right-size requests (Strategy 1) |
| System pods | kube-system pods block removal | Annotate with safe-to-evict |
5. Karpenter
Cluster Autoscaler scales node groups. Karpenter scales individual instances. The difference - especially on Amazon EKS - translates directly into cost.
A basic Karpenter NodePool that enables mixed On-Demand and Spot provisioning:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
disruption:
consolidationPolicy: WhenUnderutilized
consolidateAfter: 30s
The consolidationPolicy: WhenUnderutilized setting is critical - Karpenter continuously removes underutilized nodes and repacks workloads, even when the cluster appears healthy. Cluster Autoscaler only removes nodes when pods cannot be scheduled.
| Feature | Cluster Autoscaler | Karpenter |
|---|---|---|
| Scaling unit | Node Groups (ASGs) | Individual instances |
| Bin packing | Basic | Excellent |
| Spot support | Good | Excellent |
| Instance flexibility | Limited to ASG config | Any instance family |
| Launch speed | 2–4 min | ~30–60 sec |
| Continuous consolidation | No | Yes |
| AWS-native integration | Good | Native (uses EC2 Fleet) |
6. Spot Instances
Many teams avoid Spot instances because of reliability concerns. Spot instances are only unreliable for workloads that cannot tolerate interruption - and most workloads can, if designed correctly.
AWS offers Spot discounts of 60–90% compared to On-Demand. That is not a rounding error - it is the difference between $15,000/month and $4,500/month for the right workloads.
Enable Spot alongside On-Demand in Karpenter:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values:
- spot
- on-demand
Kubernetes handles the rest - if Spot is available, Karpenter provisions it. If not, it falls back to On-Demand automatically.
For safe Spot usage, every eligible workload needs:
# Graceful shutdown handling (minimum 60 seconds)
terminationGracePeriodSeconds: 60
# At least 2 replicas across AZs
replicas: 3
# PodDisruptionBudget to prevent mass eviction
---
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 1
selector:
matchLabels:
app: my-service
| Workload Type | Spot Safe? | Reason |
|---|---|---|
| CI/CD runners | ✅ Yes | Jobs restart cleanly |
| Background workers | ✅ Yes | Stateless, queue-backed |
| Image/video processing | ✅ Yes | Idempotent, restartable |
| Kafka consumers | ✅ Yes | Offset tracking handles restarts |
| Stateless API replicas (3+) | ✅ Yes | Other replicas absorb traffic |
| Single-instance databases | ❌ No | Data integrity risk |
| Leader-elected components | ❌ No | Re-election takes time |
| Control plane components | ❌ No | Cluster stability dependency |
7. Horizontal Pod Autoscaler
HPA is widely enabled but rarely tuned correctly. The default CPU-based configuration works for simple workloads but causes expensive flapping on anything with bursty traffic patterns.
Use autoscaling/v2 with explicit stabilization:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # Wait 5 min before scaling down
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 60
For queue-based workloads, custom metrics via KEDA give far more accurate scaling signals:
# KEDA ScaledObject - scale on SQS queue depth
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/...
targetQueueLength: "5"
awsRegion: us-east-1
| Scaling Trigger | Best For | Cost Efficiency |
|---|---|---|
| CPU utilization | Compute-bound apps | Moderate - lags real demand |
| Memory utilization | Memory-bound apps | Moderate - lags real demand |
| Queue depth (SQS, RabbitMQ) | Worker pools | High - direct demand signal |
| Kafka consumer lag | Event-driven workers | High - direct demand signal |
| HTTP requests/sec | API services | High - reflects user load |
| Custom app metric | Any workload | Highest - you define the signal |
8. Vertical Pod Autoscaler
VPA does not replace HPA. It solves a different problem: ensuring that resource requests reflect actual application behaviour, rather than what someone guessed when writing the deployment YAML.
Start with VPA in Off mode to generate recommendations without touching running pods:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: payment-api-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-api
updatePolicy:
updateMode: "Off" # Recommend only - no automatic changes
After a week or two, check the recommendations:
kubectl describe vpa payment-api-vpa
A typical result:
Recommendation:
Container Recommendations:
Container Name: payment-api
Lower Bound:
cpu: 50m
memory: 200Mi
Target:
cpu: 120m # Was: 1000m
memory: 380Mi # Was: 2Gi
Upper Bound:
cpu: 500m
memory: 900Mi
Use Target as your new request value. Validate in staging, then apply to production.
| VPA Mode | What It Does | When to Use |
|---|---|---|
Off |
Shows recommendations only | Always start here |
Initial |
Sets requests at pod creation | Good for new workloads |
Recreate |
Restarts pods to apply changes | Useful for non-critical services |
Auto |
Continuously applies - may restart | Only after thorough staging validation |
9. Idle Workload Detection
Every Kubernetes cluster accumulates forgotten resources. Preview environments, test namespaces, abandoned deployments, CronJobs from experiments, and PVCs from deleted databases - they all add to the monthly bill without delivering any value.
Audit your cluster systematically:
# List all namespaces - look for anything unfamiliar
kubectl get namespaces
# Find deployments across all namespaces
kubectl get deployments --all-namespaces
# Check actual resource consumption - zero or near-zero is suspect
kubectl top pods --all-namespaces
# Find PVCs that have no active pod claiming them
kubectl get pvc --all-namespaces | grep -v Bound
# Find completed/failed jobs consuming PVC storage
kubectl get jobs --all-namespaces | grep -v "1/1"
For each unfamiliar workload, ask: has this received any traffic recently? Who provisioned it? Is there a corresponding ticket or PR? If nobody can answer confidently, schedule it for deletion.
| Resource Type | How to Find Idle Ones | Safe to Delete When |
|---|---|---|
| Namespaces | kubectl get ns + check workload activity |
No active traffic, team confirms |
| Deployments | kubectl top pods showing ~0 CPU/memory |
Zero traffic for 2+ weeks |
| PersistentVolumes | kubectl get pv with Released status |
No active PVC bound |
| CronJobs | Check last execution time via kubectl get cronjobs |
Never ran or last ran 3+ months ago |
| Services | No endpoints, no traffic via access logs | Pod selector matches nothing |
10. Container Image Optimization
Container image size affects more than deployment speed. During autoscaling events, every second of image pull time is a second where new pods aren’t serving traffic - which often triggers HPA to add even more replicas, compounding the cost.
A typical unoptimized Dockerfile:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
build-essential python3 python3-pip nodejs npm curl wget git
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["python3", "app.py"]
A production-optimized multi-stage build:
# Stage 1: Build
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: Runtime only
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY app/ ./app/
CMD ["python3", "-m", "app"]
11. Persistent Storage Cleanup
Persistent Volumes are one of the most invisible infrastructure costs. Pods get deleted. Deployments get removed. But by default, the underlying cloud disks remain - and billing continues.
Audit your storage:
# List all PVCs across namespaces
kubectl get pvc --all-namespaces
# Find PVs in Released state (no active PVC)
kubectl get pv | grep Released
# Check volume sizes and storage classes
kubectl get pv -o custom-columns=\
NAME:.metadata.name,\
SIZE:.spec.capacity.storage,\
STATUS:.status.phase,\
CLAIM:.spec.claimRef.name
For each Released or orphaned volume, verify there is no data still needed, then delete:
kubectl delete pv <volume-name>
Also audit volumes that are correctly bound but oversized. A 500 GiB gp3 volume for a database using 40 GiB is a real cost - and resizing down is non-destructive on most cloud providers.
| Storage Waste Type | How to Identify | Action |
|---|---|---|
| Released PVs | kubectl get pv | grep Released |
Verify data not needed, then delete |
| Orphaned PVCs | PVC exists, no pod references it | Confirm ownership, delete |
| Oversized volumes | Allocated vs used via df -h in pod |
Resize down if provider supports it |
| Wrong storage class | gp2 where gp3 would be cheaper |
Migrate to cost-optimized class |
12. OpenCost for Visibility
You cannot optimize what you cannot measure. OpenCost is the CNCF standard for Kubernetes cost allocation - open-source, free, and integrates with AWS, Azure, GCP, and on-premises.
Install with Helm:
helm repo add opencost https://opencost.github.io/opencost-helm-chart
helm repo update
helm install opencost opencost/opencost \
--namespace opencost \
--create-namespace \
--set opencost.exporter.cloudProviderApiKey="YOUR_KEY"
Access the dashboard:
kubectl port-forward -n opencost service/opencost 9090:9090
# Open http://localhost:9090
| OpenCost Metric | What It Reveals | Typical Action |
|---|---|---|
| Cost per namespace | Which teams spend the most | Set quotas, review with team leads |
| Idle costs | Reserved but unused capacity | Right-size requests (Strategy 1) |
| Network cost | Egress patterns | Optimize inter-AZ traffic |
| Storage cost | PVC spend by namespace | Audit and clean up (Strategy 11) |
| Efficiency score | Request vs actual usage ratio | Target >60% efficiency |
13. ResourceQuotas and LimitRanges
Cost optimization isn’t only about fixing existing workloads. It’s about ensuring tomorrow’s workloads start correctly. Without governance, every new namespace defaults to “set it high to be safe” - and multiply that across dozens of teams and the numbers add up fast.
A ResourceQuota that sets team-level spending boundaries:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-platform-quota
namespace: team-platform
spec:
hard:
requests.cpu: "20" # Total CPU requests in namespace
requests.memory: "40Gi" # Total memory requests
limits.cpu: "40"
limits.memory: "80Gi"
persistentvolumeclaims: "10" # Max PVCs
requests.storage: "500Gi" # Total storage
A LimitRange that ensures every new pod starts with sensible defaults:
apiVersion: v1
kind: LimitRange
metadata:
name: default-container-limits
namespace: team-platform
spec:
limits:
- type: Container
defaultRequest:
cpu: "250m" # Applied if pod doesn't specify requests
memory: "256Mi"
default:
cpu: "500m" # Applied if pod doesn't specify limits
memory: "512Mi"
max:
cpu: "4" # Hard ceiling per container
memory: "8Gi"
| Governance Tool | Controls | Enforced At |
|---|---|---|
| ResourceQuota | Total namespace spend ceiling | Admission (create/update) |
| LimitRange | Per-pod defaults and ceilings | Admission (create/update) |
| OPA/Gatekeeper policy | Custom rules (e.g. must set requests) | Admission webhook |
| Namespace labels | Cost center/team attribution | Any time |
14. Non-Production Shutdown
This is the fastest win on this list. Non-production environments - development namespaces, QA clusters, staging environments, demo applications - typically run continuously but are only used during business hours.
Scale workloads down automatically at the end of the workday using a Kubernetes CronJob:
# Scale down every weekday at 8 PM
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-down-dev
spec:
schedule: "0 20 * * 1-5" # 8 PM Mon-Fri
jobTemplate:
spec:
template:
spec:
serviceAccountName: scaler
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
kubectl scale deployment --all \
-n development --replicas=0
restartPolicy: OnFailure
# Restore every weekday at 7:45 AM (15-min head start)
apiVersion: batch/v1
kind: CronJob
metadata:
name: scale-up-dev
spec:
schedule: "45 7 * * 1-5" # 7:45 AM Mon-Fri
jobTemplate:
spec:
template:
spec:
serviceAccountName: scaler
containers:
- name: kubectl
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
kubectl scale deployment --all \
-n development --replicas=1
restartPolicy: OnFailure
| Environment Type | Suggested Off Hours | Estimated Monthly Savings |
|---|---|---|
| Development namespaces | Evenings + weekends | 60–70% of environment cost |
| QA / staging | Evenings + weekends | 60–70% of environment cost |
| Preview environments | After PR merge + 24h | Up to 100% - delete entirely |
| Demo environments | Outside business hours | 60–70% of environment cost |
15. FinOps Culture
The biggest Kubernetes optimization isn’t technical - it’s organizational. Every strategy in this guide requires someone to care about cost. FinOps culture is what creates that accountability at scale.
The best engineering teams don’t run “cost optimization projects.” They build cost awareness into everyday engineering, reviewing these questions every sprint:
- Can this workload be smaller?
- Is autoscaling working correctly this week?
- Do we still need this namespace?
- Are Spot instances appropriate for this workload?
- Is this PVC still in use?
Track these KPIs and review them in sprint retrospectives:
| KPI | Target | How to Measure |
|---|---|---|
| CPU utilization per node | > 60% | Prometheus / OpenCost |
| Memory utilization per node | > 65% | Prometheus / OpenCost |
| Cost per namespace (weekly trend) | Flat or decreasing | OpenCost dashboard |
| Idle cost as % of total | < 10% | OpenCost efficiency score |
| Non-prod environments on schedule | 100% | CronJob success rate |
| VPA recommendations reviewed | Monthly | Manual review process |
Quick Reference
Commands you’ll use most during cost optimization work:
# Resource utilization
kubectl top pods --all-namespaces
kubectl top nodes
# Node details (capacity, allocations, pods)
kubectl describe node <node-name>
# Storage audit
kubectl get pvc --all-namespaces
kubectl get pv | grep -v Bound
# Workload inventory
kubectl get namespaces
kubectl get deployments --all-namespaces
kubectl get statefulsets --all-namespaces
kubectl get cronjobs --all-namespaces
# Autoscaler health
kubectl logs deployment/cluster-autoscaler -n kube-system --tail=100
# VPA recommendations
kubectl get vpa --all-namespaces
kubectl describe vpa <vpa-name>
# Governance audit
kubectl get resourcequota --all-namespaces
kubectl get limitrange --all-namespaces
# Manual scale operations
kubectl scale deployment <name> -n <namespace> --replicas=0
kubectl scale deployment <name> -n <namespace> --replicas=3
Optimization Checklist
Use this during monthly platform reviews. More than five unchecked items means significant savings are likely waiting.
| Item | Status |
|---|---|
| Resource requests reviewed against actual P95 usage | ☐ |
| Limits reviewed - separated from requests | ☐ |
| Cluster Autoscaler scale-down logs audited for blockers | ☐ |
| Karpenter evaluated (EKS) or equivalent autoscaler tuned | ☐ |
| Spot Instances enabled for eligible workloads | ☐ |
| HPA configured with correct metrics and stabilization | ☐ |
| VPA recommendations reviewed this month | ☐ |
| Namespace ResourceQuotas configured | ☐ |
| LimitRanges set as defaults | ☐ |
| Idle namespaces and deployments removed | ☐ |
| PersistentVolumes audited for orphaned disks | ☐ |
| Container images using multi-stage builds | ☐ |
| OpenCost installed and namespace dashboards configured | ☐ |
| Non-production environments on automated shutdown schedule | ☐ |
| Monthly FinOps review completed with team | ☐ |
Monthly questions to answer as a team:
- Which namespace costs the most this month? Is that expected?
- Which workloads haven’t scaled down at all? Is autoscaling blocked?
- Did any namespace cost spike unexpectedly? What changed?
- Are non-production shutdown schedules working correctly?
- What do VPA recommendations say compared to last month?
Key Takeaways
- Measure before you optimize. Guessing costs money. Visibility is free with OpenCost.
- Right-size resource requests first. This single change often reduces node counts by 30–40%.
- Separate requests from limits. Equal values prevent efficient scheduling.
- Use Spot Instances for fault-tolerant workloads. The 60–90% discount is real.
- Adopt Karpenter on EKS. Continuous consolidation keeps costs low automatically.
- Track cost per namespace. Team-level visibility drives team-level accountability.
- Automate non-production shutdown. 60–70% of idle environment cost eliminated overnight.
- Make FinOps part of every sprint. Cost culture is the only optimization that compounds.
Need Help Optimizing Your Kubernetes Platform?
Cloud costs shouldn’t increase faster than your business.
At DevOpsProxy, we help engineering teams design, optimize and operate production-grade Kubernetes platforms across AWS, Azure and Google Cloud. Whether you’re modernizing an existing cluster, implementing FinOps practices, or trying to understand why your cloud bill keeps growing, our engineers work alongside your team to deliver practical, production-ready results.
If you found this useful, these guides cover adjacent topics:
- Terraform Best Practices for Production Infrastructure
- GitOps with ArgoCD: A Production Guide
- Platform Engineering vs DevOps: What’s the Difference?
- AWS EKS vs Azure AKS vs GKE: Choosing the Right Managed Kubernetes
Book a free architecture consultation - let’s identify the biggest optimization opportunities in your environment.
Frequently Asked Questions
How much can Kubernetes cost optimization realistically save?
Most organizations reduce Kubernetes infrastructure costs by 20–50% after implementing proper resource requests, autoscaling, Spot instances, and storage optimization. Teams that also implement FinOps culture tend to sustain those savings permanently rather than reverting after 6 months.
Is Karpenter better than Cluster Autoscaler?
Karpenter provides more flexible instance provisioning and often achieves better bin-packing, particularly on Amazon EKS. The continuous consolidation feature is especially valuable. However, Cluster Autoscaler remains a solid option for multi-cloud environments (Azure, GCP) where Karpenter support is more limited.
Is OpenCost free?
Yes. OpenCost is a CNCF open-source project with no license cost. It integrates with AWS, Azure, GCP, and on-premises environments. Kubecost builds on OpenCost and adds enterprise features - the open-source version is sufficient for most teams.
Should every workload use Spot Instances?
No. Spot Instances are ideal for fault-tolerant workloads: stateless services with multiple replicas, CI/CD runners, batch jobs, and queue workers. Critical stateful applications, leader-elected components, and single-replica databases should stay on On-Demand or Reserved capacity.
What is the biggest Kubernetes cost mistake?
Over-provisioned resource requests. Most production clusters reserve 3–5× more CPU and memory than applications actually consume. Right-sizing requests (Strategy 1) is consistently the highest-impact single optimization available - it reduces node count, enables autoscaler scale-down, and improves the efficiency of every other optimization on this list.