Kubernetes Cost Control Checklist: Stop Burning Money on Idle Clusters

14 min read
kubernetescost-optimizationfinopshpakubecostspot-instancesresource-management

Audit resource requests, configure HPA stabilization windows, deploy Karpenter spot node pools, enforce ResourceQuotas and LimitRanges, install Kubecost, and run a monthly cost review cycle. This checklist takes a cluster from unchecked spending to lean, observable infrastructure in one sprint.

Running Kubernetes in production without cost guardrails is like leaving every light on in a skyscraper overnight. According to the CNCF 2024 FinOps Survey, 65% of organizations overspend on Kubernetes by 30-50% because nobody audits resource requests after initial deployment. A Datadog 2025 container report found that the median cluster runs at only 34% CPU utilization, meaning two-thirds of purchased compute is wasted. This checklist gives you concrete, copy-paste commands and YAML manifests to fix that.

---

1. Audit Resource Requests and Limits

The single biggest source of Kubernetes waste is over-requested CPU and memory. Developers set requests high during the initial deployment panic and never revisit them. According to Sysdig's 2024 Container Security Report, over 60% of containers request 2x or more resources than they actually consume.

Find overprovisioned pods

<h1>Show pods where actual CPU usage is less than 20% of requested</h1>

kubectl top pods -A --no-headers | awk '{

split($3, cpu, "m");

if (cpu[1] < 50) print $1, $2, $3, "(low CPU)"

}'

Compare requests vs actual usage

<h1>Install metrics-server if not present</h1>

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

<h1>Per-namespace resource consumption summary</h1>

kubectl resource-capacity --sort cpu.request --util

<h1>Find pods with no resource requests at all (unbounded risk)</h1>

kubectl get pods -A -o json | jq -r '

.items[] |

select(.spec.containers[].resources.requests == null) |

.metadata.namespace + "/" + .metadata.name

'

<h1>Export a CSV of all requests vs limits for spreadsheet analysis</h1>

kubectl get pods -A -o json | jq -r '

["Namespace","Pod","Container","CPU Req","CPU Lim","Mem Req","Mem Lim"],

(.items[]

.metadata as $m
.spec.containers[] |

[$m.namespace, $m.name, .name,

(.resources.requests.cpu // "none"),

(.resources.limits.cpu // "none"),

(.resources.requests.memory // "none"),

(.resources.limits.memory // "none")])

| @csv

' > resource-audit.csv

Set a monthly calendar reminder to run this audit. Better yet, automate it as a CronJob that posts results to Slack. On Arxentra's managed platform, resource analytics are built into the dashboard so you can spot waste without SSH access.

Resource audit results interpretation

FindingActionPriority

CPU request > 3x actual usageReduce request to 1.5x p95 usageHigh
Memory request > 2x actual usageReduce request to 1.3x p99 usageHigh
No requests or limits setAdd immediately, cluster stability riskCritical
Limits equal requestsConsider adding headroom to limits onlyMedium
CPU throttling detectedRaise limits, keep requests stableMedium

According to Google Cloud's 2025 GKE Best Practices report, right-sizing resource requests alone reduces cluster costs by 25-40% without any architecture changes. The key metric is the ratio between your p95 actual usage and your request value, which should stay between 1.2x and 1.8x.

---

2. Tune HPA Thresholds Correctly

The Horizontal Pod Autoscaler is powerful but badly configured in most clusters. The default CPU target of 80% is often wrong for your workload profile. CNCF survey data from 2024 shows that 41% of teams using HPA never modified the default thresholds, leading to either constant scaling thrash or failure to scale under load.

Properly configured HPA with stabilization

apiVersion: autoscaling/v2

kind: HorizontalPodAutoscaler

metadata:

name: api-server

spec:

scaleTargetRef:

apiVersion: apps/v1

kind: Deployment

name: api-server

minReplicas: 2

maxReplicas: 20

behavior:

scaleDown:

stabilizationWindowSeconds: 300

policies:

- type: Percent

value: 25

periodSeconds: 60

scaleUp:

stabilizationWindowSeconds: 30

policies:

- type: Percent

value: 100

periodSeconds: 30

metrics:

- type: Resource

resource:

name: cpu

target:

type: Utilization

averageUtilization: 65

Key tuning decisions explained:

  • Scale-down stabilization at 300s prevents thrashing during traffic dips. Without this, a brief drop in traffic causes pod termination, then a small spike causes recreation, wasting both compute and time.
  • Scale-up at 30s responds quickly to load spikes. You want asymmetric scaling: fast up, slow down.
  • CPU target at 65% leaves headroom for burst traffic without over-provisioning. A 2024 Robusta.dev analysis of 500+ production clusters found that 65% is the optimal CPU target for API workloads, balancing cost and latency.
  • Scale-down limited to 25% per minute avoids dropping capacity too fast during load fluctuations.

HPA target selection guide

Workload TypeRecommended CPU TargetScale-Down WindowReasoning

Stateless API60-70%300sNeeds burst headroom
Background worker75-85%600sTolerates latency, slow scale-down saves money
WebSocket server50-60%120sConnection migration is expensive
Batch processor85-95%900sMaximize utilization, long stabilization

For teams running multiple services on Arxentra's cloud computing platform, HPA configurations are managed through the dashboard with automatic threshold recommendations based on historical usage patterns.

---

3. Adopt Spot/Preemptible Nodes for Stateless Workloads

Spot instances cost 60-90% less than on-demand across all major clouds. AWS reports that the average spot discount is 70% off on-demand pricing as of 2025. The trade-off is interruption, but stateless workloads handle this gracefully with proper pod disruption budgets. According to Spot.io's 2024 report, organizations using spot instances save an average of $147,000 per year per 100-node cluster.

Karpenter NodePool with spot instances and diversification

apiVersion: karpenter.sh/v1beta1

kind: NodePool

metadata:

name: spot-pool

spec:

template:

spec:

requirements:

- key: karpenter.sh/capacity-type

operator: In

values: ["spot"]

- key: node.kubernetes.io/instance-type

operator: In

values:

- m5.xlarge

- m5a.xlarge

- m6i.xlarge

- m6a.xlarge

- c5.xlarge

- c6i.xlarge

nodeClassRef:

name: default

limits:

cpu: "100"

memory: 400Gi

disruption:

consolidationPolicy: WhenUnderutilized

expireAfter: 720h

Key considerations for spot adoption:

  • Diversify instance types across at least 6 types. This reduces interruption frequency by 80% because AWS draws from multiple capacity pools.
  • Mix instance families (m5, m6i, c5, c6i) rather than sticking to one generation. This maximizes the chance of finding available capacity.
  • Set a PodDisruptionBudget for every stateless workload so Kubernetes can gracefully drain spot nodes before termination.
  • Never run databases or stateful workloads on spot. Use on-demand or reserved instances for those.

PodDisruptionBudget for spot-ready workloads

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: api-server-pdb

spec:

minAvailable: "50%"

selector:

matchLabels:

app: api-server

---

4. Enforce Namespace Quotas and LimitRanges

Without quotas, a single team can consume your entire cluster. ResourceQuotas cap total namespace consumption, while LimitRanges set defaults and bounds for individual pods. These are your financial guardrails at the namespace level.

ResourceQuota for a team namespace

apiVersion: v1

kind: ResourceQuota

metadata:

name: team-backend-quota

namespace: team-backend

spec:

hard:

requests.cpu: "20"

requests.memory: 40Gi

limits.cpu: "40"

limits.memory: 80Gi

persistentvolumeclaims: "10"

services.loadbalancers: "2"

pods: "100"

LimitRange to set defaults and prevent outliers

apiVersion: v1

kind: LimitRange

metadata:

name: default-limits

namespace: team-backend

spec:

limits:

- type: Container

default:

cpu: 500m

memory: 512Mi

defaultRequest:

cpu: 100m

memory: 128Mi

max:

cpu: "4"

memory: 8Gi

min:

cpu: 50m

memory: 64Mi

The LimitRange is particularly important because it catches pods deployed without any resource specifications. Instead of running unbounded, they get sensible defaults. This single manifest prevents the most common cause of noisy-neighbor problems in multi-tenant clusters.

---

5. Install Kubecost for Real-Time Cost Visibility

You cannot optimize what you cannot measure. Kubecost provides per-namespace, per-deployment, and per-pod cost attribution. The open-source version covers most needs. According to Kubecost's own customer data, teams that deploy Kubecost reduce their cloud spend by an average of 30-50% within the first 90 days simply through visibility.

Install Kubecost via Helm

<h1>Add the Kubecost Helm repository</h1>

helm repo add kubecost https://kubecost.github.io/cost-analyzer/

helm repo update

<h1>Install Kubecost into its own namespace</h1>

helm install kubecost kubecost/cost-analyzer \

--namespace kubecost \

--create-namespace \

--set kubecostToken="your-token-here" \

--set prometheus.server.retention=30d \

--set networkCosts.enabled=true

<h1>Verify installation</h1>

kubectl get pods -n kubecost

<h1>Port-forward to access the dashboard</h1>

kubectl port-forward -n kubecost svc/kubecost-cost-analyzer 9090:9090

Key Kubecost metrics to monitor weekly

MetricWhat It Tells YouAction Threshold

Cluster efficiencyOverall utilization vs costBelow 50% needs attention
Namespace costTeam-level spendingAny namespace above budget
Idle costMoney spent on unused resourcesAbove 20% of total
Network egress costCross-AZ and internet trafficSpikes above baseline
Storage costPV and snapshot expensesUnused PVCs

Teams using Arxentra's infrastructure management get cost dashboards built in, but Kubecost remains the gold standard for self-managed clusters.

---

6. Run a Monthly Cost Review Cycle

Technology alone does not control costs. You need a recurring process. The most effective teams run a monthly 30-minute cost review with three fixed agenda items.

Monthly cost review agenda

  • Top 5 most expensive namespaces - Review Kubecost dashboard, identify any namespace that grew more than 15% month-over-month without a corresponding traffic increase.
  • Resource right-sizing opportunities - Run the kubectl audit commands from Step 1, generate the CSV, and identify the top 10 pods with the largest request-to-usage gap.
  • Spot adoption progress - Check what percentage of non-stateful workloads run on spot. Target is 80% or higher for stateless services.
  • Automation script for monthly report

    #!/bin/bash
    

    <h1>Monthly K8s cost audit script</h1>

    echo "=== K8s Monthly Cost Report ==="

    echo "Date: $(date -u +%Y-%m-%d)"

    echo ""

    echo "--- Node Utilization ---"

    kubectl top nodes

    echo ""

    echo "--- Namespace Resource Usage ---"

    kubectl resource-capacity --sort cpu.request --util --namespace-count

    echo ""

    echo "--- Pods Without Resource Requests ---"

    kubectl get pods -A -o json | jq -r '

    [.items[]

    select(.spec.containers[].resources.requests == null)]
    length

    ' | xargs -I {} echo "{} pods without requests"

    echo ""

    echo "--- Spot vs On-Demand Node Distribution ---"

    kubectl get nodes -L karpenter.sh/capacity-type -L node.kubernetes.io/instance-type --no-headers | \

    awk '{print $6}'

    sortuniq -c
    sort -rn

    Save this script and run it on the first Monday of every month. Pipe the output to a Slack channel for team visibility.

    ---

    Frequently Asked Questions

    How much can I realistically save with this checklist?

    Most teams see 30-50% reduction in their Kubernetes bill within the first month. The biggest wins come from right-sizing resource requests (Step 1) and adopting spot instances (Step 3). A 2025 FinOps Foundation study found that organizations following a structured cost optimization process save 2.4x more than those doing ad-hoc optimization.

    Should I set CPU limits or just requests?

    This is debated. Google's official recommendation since 2023 is to set CPU requests but not CPU limits for most workloads. CPU limits cause throttling even when the node has spare capacity. Memory limits should always be set to prevent OOM kills from affecting other pods. Tim Hockin (Kubernetes co-founder) has publicly stated that CPU limits cause more problems than they solve for the majority of workloads.

    How do I convince management to invest time in cost optimization?

    Run the audit commands from Step 1 and calculate the annual waste. Present it as: "We are paying $X per year for compute we are not using. This checklist will recover Y% of that in one sprint." The CNCF FinOps survey found that the average ROI on Kubernetes cost optimization is 8:1 in the first year.

    Is Kubecost worth it for small clusters (under 20 nodes)?

    Yes. The open-source version is free and runs on minimal resources (about 0.5 CPU and 1GB RAM). Even on a 5-node cluster, the visibility it provides typically identifies savings that exceed its resource cost by 10x within the first week.

    How often should I right-size resource requests?

    Monthly for active services, quarterly for stable ones. The Arxentra blog regularly publishes updated benchmarks and tooling recommendations. Automate the audit with the script from Step 6 so you never forget. VPA (Vertical Pod Autoscaler) in recommendation mode can also continuously suggest right-sized values without applying them automatically.

    What is the relationship between requests, limits, and QoS classes?

    Kubernetes assigns QoS classes based on how you set requests and limits. Guaranteed (requests equal limits) pods are never evicted first. Burstable (requests set, limits higher or unset) pods can use spare capacity but get evicted before Guaranteed pods. BestEffort (no requests or limits) pods are evicted first during node pressure. For cost optimization, most stateless workloads should be Burstable with well-tuned requests.