Cost Optimization

Kubernetes Cost Optimization: 15 Strategies That Actually Reduce Cloud Spend

Learn 15 production-tested Kubernetes cost optimization strategies that reduce cloud spend without sacrificing reliability. Covers requests vs limits, Cluster Autoscaler, Karpenter, Spot Instances, OpenCost, storage optimization, idle workload detection, and real production examples.

Kubernetes Cost Optimization: 15 Strategies That Actually Reduce Cloud Spend
Learn 15 production-tested Kubernetes cost optimization strategies that reduce cloud spend without sacrificing reliability. Covers requests vs limits, Cluster Autoscaler, Karpenter, Spot Instances, OpenCost, storage optimization, idle workload detection, and real production examples.
Cost Optimization kubernetes finops cloud-cost aws eks opencost
Pankaj Dahiya kubernetes-cost-optimization

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:

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.

Get production-grade Platform Engineering tutorials

Actionable guides on Kubernetes, Terraform, AWS, Azure, FinOps, GitOps, Platform Engineering and Cloud Architecture.

  • Real production examples
  • No AI fluff
  • Twice a month
  • Unsubscribe anytime

We never spam. Only practical engineering content.