Kubernetes Cost Optimization â Reduce K8s Spending
Kubernetes cost optimization reduces K8s infrastructure spend by right-sizing clusters, setting pod resource limits, using node autoscaling, scheduling on spot instances, and monitoring costs with Kubecost â without sacrificing reliability.
What You'll Learn
By the end of this guide, you'll be able to right-size clusters, implement Cluster Autoscaler and Karpenter, set pod resource requests and limits, use Vertical Pod Autoscaler, adopt spot instances, enforce namespace quotas, monitor costs with Kubecost, and garbage-collect unused resources.
Why It Matters
Kubernetes clusters are notoriously over-provisioned. Teams set generous requests "to be safe," leave nodes running 24/7, and run workloads that could be scheduled on spot instances. The result: 40-60% of K8s spend is waste. DodaTech reduced EKS costs for DodaZIP's backend by 45% using Karpenter spot instances and VPA recommendations.
Real-World Use
Spotify runs 7,000+ Microservices on GKE with 90% spot instance adoption. Pinterest reduced K8s costs by 50% using VPA and Cluster Autoscaler. Snapchat saves $2M+/year by right-sizing pod requests across thousands of services.
flowchart LR
A[Cluster Metrics] --> B[Right-Size Nodes]
A --> C[Pod Requests/Limits]
B --> D[Cluster Autoscaler]
B --> E[Karpenter]
C --> F[VPA]
A --> G[Spot Instances]
D --> H[30-50% Savings]
style H fill:#326ce5,color:#fff
Prerequisites: Kubernetes basics, kubectl access. Understanding of AWS or Azure node types helps.
1. Cluster Right-Sizing
The first step is choosing the right instance type and size for your nodes.
kubectl top nodes
# Check which instance types you're using
kubectl get nodes -o json | jq '.items[].metadata.labels["beta.kubernetes.io/instance-type"]' | sort | uniq -c
Expected output:
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
node-m5-2x-1 850m 10% 12Gi 37%
node-m5-2x-2 1200m 15% 18Gi 56%
node-m5-4x-1 900m 5% 20Gi 31%
If all nodes show <50% resource usage, you're over-provisioned. Downsize to smaller instance types.
2. Node Autoscaling
Cluster Autoscaler scales node groups based on pending pods. Karpenter provisions optimal instance types directly.
# Karpenter NodePool (faster, cheaper)
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["on-demand", "spot"]
nodeClassRef:
name: default
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h
# Cluster Autoscaler (AWS EKS)
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
containers:
- image: k8s.gcr.io/autoscaling/cluster-autoscaler:v1.28.0
name: cluster-autoscaler
command:
- ./cluster-autoscaler
- --v=4
- --cloud-provider=aws
- --scale-down-unneeded-time=10m
3. Pod Resource Requests and Limits
Setting accurate requests and limits is the single highest-impact K8s cost optimization.
# BAD: no requests/limits (unbounded)
apiVersion: v1
kind: Pod
metadata:
name: web-1
spec:
containers:
- name: app
image: nginx:latest
# GOOD: set requests based on profiling
apiVersion: v1
kind: Pod
metadata:
name: web-1
spec:
containers:
- name: app
image: nginx:latest
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
Recommendation: Set requests at P99 of observed usage and limits at 2x requests. Use VPA to get these numbers.
4. Vertical Pod Autoscaler (VPA)
VPA analyzes historical pod usage and recommends optimal CPU/memory requests.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-app-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: web-app
updatePolicy:
updateMode: "Off" # Switch to "Auto" after reviewing
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 2
memory: 4Gi
kubectl describe vpa web-app-vpa
# Expected: Lower: 150m CPU, 256Mi RAM
# Target: 300m CPU, 512Mi RAM
5. Spot Instances for K8s
Use spot instances for worker nodes running stateless, fault-tolerant workloads:
# EKS managed node group with spot
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: prod
region: us-east-1
managedNodeGroups:
- name: spot-workers
instanceTypes:
- m5.large
- m5a.large
spot: true
minSize: 2
maxSize: 20
# AKS spot pool
az aks nodepool add \
--resource-group prod-rg \
--cluster-name prod-cluster \
--name spotpool \
--priority Spot \
--eviction-policy Delete \
--node-count 3 \
--enable-cluster-autoscaler \
--min-count 1 --max-count 10
6. Namespace Quotas
Prevent one team from consuming all cluster resources:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-backend-quota
namespace: team-backend
spec:
hard:
requests.cpu: "10"
requests.memory: 40Gi
limits.cpu: "20"
limits.memory: 80Gi
persistentvolumeclaims: 10
pods: "50"
kubectl describe quota team-backend-quota -n team-backend
7. Cost Monitoring with Kubecost
Kubecost provides per-namespace, per-deployment, and per-label cost breakdowns:
helm repo add kubecost https://kubecost.github.io/cost-analyzer/
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--create-namespace
kubectl port-forward --namespace kubecost svc/kubecost-cost-analyzer 9090:9090
8. Garbage Collection
Unused resources accumulate and cost money:
# Clean up completed pods
kubectl delete pods --field-selector=status.phase==Succeeded
# Delete old ReplicaSets with 0 replicas
kubectl delete replicasets --all-namespaces \
--field-selector=status.replicas==0
Common Mistakes
1. No Resource Requests or Limits
Pods can consume unlimited cluster resources, causing noisy neighbors and unpredictable costs.
2. Ignoring VPA Recommendations
Setting requests by guessing leads to massive over-provisioning. VPA provides data-driven recommendations.
3. Fixed-Size Node Groups
Without Cluster Autoscaler or Karpenter, nodes run 24/7 even when idle. Autoscaling is non-negotiable.
4. No Spot Instances
Stateless workloads (CI/CD, batch, web workers) can run on spot at 60-90% discount. Only databases and stateful services need on-demand.
5. No Namespace Quotas
One team's over-provisioned pods inflate the entire cluster's cost. Quotas enforce fairness.
Practice Questions
1. What is the difference between Cluster Autoscaler and Karpenter? Cluster Autoscaler works with node groups; Karpenter provisions individual optimal instance types. Karpenter achieves higher density and faster scaling.
2. How do requests and limits affect cost? Requests determine the minimum resources reserved for a pod (and billed). Limits cap resource usage. Over-provisioned requests waste money; under-provisioned limits cause throttling.
3. What workloads should not run on spot instances? Stateful workloads (databases), long-running batch jobs without checkpointing, and workloads that cannot tolerate abrupt termination.
4. How does Kubecost help reduce costs? It shows exact cost per namespace, deployment, label, and pod. It identifies idle resources, rightsizing opportunities, and savings from spot adoption.
5. Challenge: Given a 50-node EKS cluster with $25k/month spend: install VPA in recommendation mode for all deployments, implement requests/limits recommendations, switch 70% of nodes to spot using Karpenter, set namespace quotas for all teams, install Kubecost to track savings.
Mini Project: K8s Cost Reporter
class K8sCostReporter:
def __init__(self):
self.namespaces = {}
def add_namespace(self, name, cpu_request, mem_gb, node_cost=0.10):
cost = (cpu_request * node_cost * 730) + (mem_gb * node_cost * 0.5 * 730)
self.namespaces[name] = {"cpu": cpu_request, "mem": mem_gb, "cost": round(cost, 2)}
def report(self):
total = sum(n["cost"] for n in self.namespaces.values())
print(f"{'Namespace':<20} {'CPU':<10} {'Memory':<10} {'Monthly Cost':<15}")
print("-" * 55)
for name, ns in sorted(self.namespaces.items(), key=lambda x: x[1]["cost"], reverse=True):
print(f"{name:<20} {ns['cpu']:<10.1f} {ns['mem']:<10.1f} ${ns['cost']:<10.2f}")
print(f"{'TOTAL':<20} {'':<10} {'':<10} ${total:<10.2f}")
reporter = K8sCostReporter()
reporter.add_namespace("production", 48, 192)
reporter.add_namespace("staging", 12, 48)
reporter.add_namespace("development", 8, 32)
reporter.report()
FAQ
Related Concepts
What's Next
You now understand Kubernetes cost optimization! Next, explore Multi-Cloud Cost optimization for managing costs across K8s clusters on AWS, Azure, and GCP.
- Practice daily â Review Kubecost dashboard and identify top spenders
- Build a project â Automate VPA recommendations roll-out across namespaces
- Explore related topics â Check out Karpenter vs Cluster Autoscaler comparison
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro