Skip to main content
A2DevOps

Kubernetes · Cost Optimization · Cloud

Kubernetes Cost Optimization: 7 Strategies to Reduce Your Cloud Bill

Running Kubernetes in production can get expensive fast. Learn practical strategies to optimize your K8s costs without sacrificing performance or reliability.

Author
A2DEVOPS Team
Published
Last updated
Updated
Reading time
7 min read

Kubernetes is powerful, but it can also be expensive if not managed properly. We've seen companies overspend by 40-60% on their K8s infrastructure simply because they haven't optimized their clusters.

In this guide, we'll share seven proven strategies to reduce your Kubernetes costs while maintaining performance and reliability.

1. Right-Size Your Resource Requests and Limits#

The most common cause of Kubernetes overspending? Over-provisioned resources.

Many teams set resource requests based on guesswork or worst-case scenarios:

YAML
# ❌ Over-provisioned (common mistake)
resources:
  requests:
    memory: "2Gi"
    cpu: "1000m"
  limits:
    memory: "4Gi"
    cpu: "2000m"

Instead, use monitoring data to right-size your containers:

YAML
# ✅ Right-sized based on actual usage
resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"
    cpu: "200m"

2. Implement Cluster Autoscaling#

Don't pay for nodes you don't need. Cluster Autoscaler automatically adjusts the number of nodes based on demand.

There's no ClusterAutoscaler resource to apply: the upstream Cluster Autoscaler is configured through command-line flags on its own Deployment. Here's an excerpt of the container args:

YAML
# Excerpt from the cluster-autoscaler Deployment (kube-system)
containers:
  - name: cluster-autoscaler
    command:
      - ./cluster-autoscaler
      - --cloud-provider=aws
      - --nodes=2:10:my-node-group   # min:max:node-group-name
      - --expander=least-waste
      - --balance-similar-node-groups
      - --scale-down-unneeded-time=10m
      - --scale-down-delay-after-add=10m
      - --scale-down-utilization-threshold=0.5
      - --max-node-provision-time=15m

Key settings to configure:

FlagRecommendation
--scale-down-unneeded-time5-10 minutes before an underutilized node is removed
--scale-down-delay-after-add10 minutes
--scale-down-utilization-threshold0.5 (node becomes a scale-down candidate below 50% requested CPU/memory)
--expanderleast-waste to pick the node group that leaves the least idle capacity
--max-node-provision-time15 minutes

On AWS, Karpenter is a popular alternative. Instead of scaling fixed node groups, it provisions right-sized nodes on demand and actively consolidates underutilized ones:

YAML
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m

3. Use Spot/Preemptible Instances for Non-Critical Workloads#

Spot instances can save you 60-90% compared to on-demand pricing. They're perfect for:

  • Development and staging environments
  • Batch processing jobs
  • Stateless workloads with proper retry logic
  • CI/CD runners
YAML
apiVersion: v1
kind: Pod
metadata:
  name: spot-workload
spec:
  # Custom label/taint on your spot node group. Managed offerings also set their own
  # labels, e.g. eks.amazonaws.com/capacityType: SPOT or cloud.google.com/gke-spot: "true"
  nodeSelector:
    node.kubernetes.io/lifecycle: spot
  tolerations:
    - key: "spot"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"
  containers:
    - name: app
      image: myapp:latest

4. Implement Pod Disruption Budgets and Priority Classes#

Pod Disruption Budgets (PDBs) are what make aggressive scale-down safe. Cluster Autoscaler and Karpenter respect PDBs when draining nodes, so they can pack your pods onto fewer nodes without taking an application below the number of replicas it needs. Without PDBs, teams tend to keep scale-down conservative and keep paying for half-empty nodes.

YAML
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web

Avoid PDBs that allow zero disruptions (e.g. minAvailable equal to the replica count): they block node drains entirely and stop the autoscaler from removing those nodes.

Use Priority Classes to ensure critical workloads get resources first:

YAML
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "Critical production workloads"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 1000
globalDefault: true
description: "Non-critical workloads"

This allows the scheduler to preempt low-priority pods when resources are scarce, reducing the need for additional nodes.

5. Schedule Non-Production Workloads Off-Hours#

Why pay for dev/staging environments 24/7 when they're only used during business hours?

Use KEDA's cron scaler to run workloads only during business hours and scale them to zero outside that window:

YAML
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: dev-app-business-hours
spec:
  scaleTargetRef:
    name: dev-app
  minReplicaCount: 0
  triggers:
    - type: cron
      metadata:
        timezone: America/New_York
        start: "0 8 * * 1-5"
        end: "0 18 * * 1-5"
        desiredReplicas: "3"

If you prefer annotation-based schedules, py-kube-downscaler, the maintained community fork of the original kube-downscaler, can scale down whole namespaces or individual workloads:

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dev-app
  annotations:
    downscaler/uptime: "Mon-Fri 08:00-18:00 America/New_York"
spec:
  replicas: 3
  # ... rest of spec

Potential savings: Running dev environments only during business hours saves ~65% on those workloads.

6. Optimize Persistent Volume Usage#

Storage costs add up quickly. Audit your PVCs regularly:

Bash
# Find PVCs not referenced by any pod (including Bound-but-unmounted ones).
# Review before deleting: e.g. StatefulSets scaled to 0 will show up here too.
comm -13 \
  <(kubectl get pods -A -o json \
      | jq -r '.items[] | .metadata.namespace as $ns | .spec.volumes[]? | select(.persistentVolumeClaim) | "\($ns)/\(.persistentVolumeClaim.claimName)"' \
      | sort -u) \
  <(kubectl get pvc -A -o json \
      | jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)"' \
      | sort -u)

# Find Released PVs (claim deleted, but the disk still exists and is billed)
kubectl get pv | grep Released

# Check PVC sizes vs actual usage
kubectl exec -it <pod> -- df -h

Best practices for storage optimization:

  • Delete unused PVCs immediately
  • Use appropriate storage classes (don't use premium SSD for logs)
  • Implement data lifecycle policies
  • Consider object storage (S3/GCS) for large, infrequently accessed data

7. Use Namespace Resource Quotas#

Prevent runaway costs by setting quotas per namespace:

YAML
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "20"
    requests.memory: "40Gi"
    limits.cpu: "40"
    limits.memory: "80Gi"
    persistentvolumeclaims: "10"
    pods: "50"

This ensures no single team can accidentally (or intentionally) consume all cluster resources.


Quick Wins Checklist#

Here's a summary of actions you can take today:

  • Analyze current resource usage with metrics
  • Right-size top 10 resource-consuming deployments
  • Enable cluster autoscaler (or Karpenter) if not already
  • Identify workloads suitable for spot instances
  • Set up off-hours scaling for non-production
  • Audit and clean up unused PVCs
  • Implement namespace quotas

How Much Can You Save?#

Based on our experience with clients, here's what typical savings look like:

StrategyTypical Savings
Right-sizing20-40%
Cluster autoscaling15-25%
Spot instances60-90% (on applicable workloads)
Off-hours scaling50-65% (dev/staging)
Storage optimization10-20%

Combined, these strategies typically reduce Kubernetes costs by 30-50%.


Need Help Optimizing Your Kubernetes Costs?#

At A2DevOps, we've helped companies reduce their cloud bills by hundreds of thousands of dollars. Our Kubernetes cost optimization assessment includes:

  • Full resource utilization analysis
  • Custom recommendations for your workloads
  • Implementation support
  • Ongoing monitoring setup

Book a free consultation to learn how much you could save, or see how we run production Kubernetes.

Want this done for your stack?

Start with the free assessment, or talk it through with an engineer.