Idle Kubernetes Resources: How to Find and Eliminate Them at Scale Cast AI's 2026 State of Kubernetes Optimization Report, covering tens of thousands of clusters measured before optimization, found average CPU utilization of 8% and memory utilization of 20%, with CPU overprovisioning at 69% and memory overprovisioning at 79%. The report attributes most idle Kubernetes spend to configuration — workloads requesting far more than they consume — and recommends measuring requested CPU and memory against P95 actual usage per workload, then rightsizing requests, consolidating onto fewer nodes, and deleting orphaned PVCs, load balancers and forgotten namespaces in that order. Organizations applying automated rightsizing cut their provisioned CPU footprint by about 50%, per the same report. Idle Kubernetes resources are capacity you paid for and never used. That means the difference between what workloads request and what they actually consume. Last updated: November 11, 2026 The gap stays hidden because the scheduler reserves against requests, not real usage. A cluster can look full and still run at a fraction of its capacity. At the same time, a node dashboard can read as healthy while most of the reserved CPU sits untouched. To clear it, measure first. Compare requested CPU and memory to P95 actual usage per workload. Then scan for orphaned PersistentVolumeClaims PVCs , load balancers, and forgotten namespaces. Eliminate what you find in a fixed order: rightsize requests, consolidate onto fewer nodes, delete what is abandoned. The Cast AI 2026 State of Kubernetes Optimization Report covers tens of thousands of clusters. Average CPU utilization across that fleet is 8%. CPU overprovisioning has reached 69%. Most of your idle spend is a configuration problem, and configuration can be fixed this sprint. Key Takeaways - Idle equals requested minus used. The scheduler reserves against requests, so idle capacity is invisible to it. A cluster can sit at 8% utilization and still refuse new pods. - Report baseline: average CPU utilization is 8% and memory 20%. CPU overprovisioning has climbed to 69% and memory overprovisioning to 79% Cast AI 2026 State of Kubernetes Optimization Report, tens of thousands of clusters, measured before optimization . - Five types recur: over-requested workloads, idle pods and replicas, underpacked nodes, orphaned PVCs and load balancers, and forgotten namespaces. - Apply the P95 method: compare requested CPU and memory to P95 actual usage per workload, then rank by the ratio of used to requested. - Sequence matters: rightsize, then consolidate, then delete. Skip that order and you destroy the evidence you need. - Rightsizing is also a reliability win. On one cluster it cut OOM kills from 40 to 50 per interval down to near zero. Organizations applying automated rightsizing cut their provisioned CPU footprint by about 50%, per the same report. What counts as an idle Kubernetes resource Start with arithmetic. A pod requesting 2 CPUs and consuming 200 millicores holds 1.8 CPUs of unused capacity. To the scheduler it looks ten times its real size. Most node dashboards and cloud bills report provisioned capacity, not consumption, so neither shows the gap by default. Some FinOps tools surface a request-versus-usage delta once you configure labels and allocation rules, but they stop at the cluster total and rarely name the container to change. Three terms get used as if interchangeable, and the confusion costs engineering time. Overprovisioned means a running workload requests far more than it uses, which is padding on a live service. Unused or orphaned means capacity with no workload behind it, such as a PVC whose pod was deleted. Every paid-for capacity doing no useful work is idle, including nodes nobody packed. Kubernetes rightsizing https://cast.ai/blog/automated-workload-rightsizing-precisionpack/ handles the overprovisioned case. The orphaned and underpacked cases need a scan. The five types of idle Kubernetes resources Nearly every wasted dollar in a cluster falls into one of five buckets. Each has a distinct detection signal, which matters because the fix for one does not touch the others. 1. Over-requested workloads. Detection: requested CPU or memory far above P95 actual usage for the same container. 2. Idle pods and replicas. Detection: replicas serving near-zero traffic, often a Deployment pinned at a non-zero count or an HPA with a floor above zero. 3. Underpacked nodes. Detection: node allocatable far above the sum of pod usage on that node. 4. Orphaned PVCs and load balancers. Detection: a bound volume or cloud load balancer with no owning pod or Service. 5. Forgotten namespaces. Detection: no recent deployments and near-zero usage across a representative window. The table is the short reference for all five. Pay attention to the right column. Detection is rarely where cleanup goes wrong. Order of operations is. | Idle type | Detect | Eliminate | Safety note | |---|---|---|---| | Over-requested workloads | Requested far above P95 actual | Rightsize to P95 plus headroom | Keep spike headroom | | Idle pods / replicas | Replicas serving near-zero traffic | Scale down; set HPA min | Do not scale stateful blindly | | Underpacked nodes | Allocatable far above pod usage | Bin-pack onto fewer nodes | Respect PDBs and anti-affinity | | Orphaned PVCs and LBs | Bound, no owner pod | Delete after verifying owner | Snapshot PVCs first | | Forgotten namespaces | No recent activity | Archive after sign-off | Check for cron jobs first | How to find idle Kubernetes resources Measure before you remove anything. One cluster average tells you little, because Kubernetes waste concentrates. In a shared cluster, one namespace running a fat legacy service can hold most of the padding while every other team sits near the median. You want a ranked list by workload, not a headline number. The largest Kubernetes cost drivers https://cast.ai/blog/kubernetes-cost-drivers/ on most bills, over-requested pods and idle nodes, both surface there. Ranking is where cleanup starts. The P95 method: requested vs actual usage Compare requested CPU and memory to P95 actual consumption per workload, over seven to fourteen days. A one-week window misses monthly batch jobs, and a single day misses anything on a cron. The gap between request and P95 is your padding estimate, and it usually runs larger than engineers expect. The ratio that ranks workloads is used divided by requested, expressed as a percentage. Take a container at 200 millicores of P95 usage against a 1,000 millicore request. That sits at 20%. That is one signal among many, and it needs a second reference point. Compare each workload against its own history before you change a request. A batch job that peaks once a day still needs its peak covered. The following recording rules compare per-container P95 usage with positive current requests. They collapse duplicate scrape labels within one cluster and exclude the infrastructure container. Containers without positive requests need a separate audit. Keep these series to compare before and after changes. Single-cluster example; retain a cluster label for multi-cluster queries. groups: - name: container-utilization interval: 5m rules: - record: container:cpu p95 ratio expr: | quantile over time 0.95, max by namespace, pod, container rate container cpu usage seconds total{container ="",container ="POD",namespace ="kube-system"} 5m 7d:5m / on namespace, pod, container max by namespace, pod, container kube pod container resource requests{resource="cpu",namespace ="kube-system"} 0 - record: container:memory p95 ratio expr: | quantile over time 0.95, max by namespace, pod, container container memory working set bytes{container ="",container ="POD",namespace ="kube-system"} 7d:5m / on namespace, pod, container max by namespace, pod, container kube pod container resource requests{resource="memory",namespace ="kube-system"} 0 Two caveats apply at fleet scale. A seven-day subquery stepped at five minutes evaluates about 2,016 samples per series, and rate inside it compounds the cost. Expect slow queries across a few thousand workloads and resource limits on smaller Prometheus deployments, so split by namespace or raise the step on long-lived workloads. Results also inherit your scrape interval, so compare workloads scraped at the same cadence. Use container memory working set bytes as one memory-sizing signal, alongside limits, RSS, cache behavior, and OOM history. It is not a direct OOM threshold. The rules above retain pod and container labels; rollouts create new series. For stable workload trends, map pods to their Deployment or StatefulSet owners before aggregating, and use consistent cluster labels when querying multiple clusters. Prometheus queries and kubectl checks Without recording rules, a few commands give you a first pass in a couple of minutes. Start with the pods themselves. Actual CPU and memory per container, next to the request kubectl top pods -n payments --containers kubectl get pods -n payments -o jsonpath='{range .items }{.metadata.name}{"\t"}{range .spec.containers }{.name}{"="}{.resources.requests.cpu}{"/"}{.resources.requests.memory}{" "}{end}{"\n"}{end}' Node-level checks confirm underpacking. The gap between requested and used capacity on each node is where consolidation gets its headroom. kubectl describe node