How it works Integrations Pricing Blog
Sign In Start free trial
Blog Cost Control

Kubernetes Cost Attribution: Why Naming the Idle ReplicaSet Beats the Dashboard

Kubernetes cost attribution showing idle workload identified by name and namespace with spend breakdown

The Kubernetes cost dashboard shows your cluster spend as a stacked bar or a pie chart. Compute: $2,840 this month. Memory: $1,190. Storage: $440. The total is $4,470, and it is $380 higher than last month. Nobody knows why, and nobody acts on it, because the number has no owner and no workload attached to it.

This is the fundamental problem with cluster-wide cost dashboards: they answer the question "how much?" but not the question "which workload, which team, and what do we do about it?" The second question is the actionable one. The first is just a number that goes into a spreadsheet and gets reviewed once a quarter by someone who cannot change it.

Where the idle spend actually lives

Kubernetes idle spend falls into several categories, and they have different root causes and different remediation paths. The most common in practice:

Over-provisioned resource requests. A deployment's resources.requests.cpu is set to 2 cores but the workload consistently uses under 200 millicores. The scheduler reserves 2 cores of node capacity for this pod, those cores are unavailable to other workloads, and you are paying for them even though your actual compute consumption is a fraction of the reservation. This is the most widespread form of idle spend and the hardest to see without workload-level granularity.

Forgotten ReplicaSets with non-zero replica counts. A Deployment update creates a new ReplicaSet and scales down the old one to zero replicas. That part works correctly. What is less clean: sometimes teams manually scale up an older ReplicaSet for a hotfix, then forget it. The kubectl get replicasets -n production output shows a ReplicaSet with 2 replicas that is not managed by any active Deployment revision and is receiving no traffic. Those 2 replicas are pure idle spend.

Staging workloads in production namespaces. Not every environment has clean namespace separation. It is common to find staging or QA workloads co-located in namespaces alongside production traffic, sized identically to production equivalents, and running continuously even when no active testing is happening. Namespace-level cost attribution shows you the namespace total, but if your staging and production workloads are in the same namespace, the spend blends together invisibly.

Jobs with excessive parallelism and poor completion tracking. Batch Jobs with parallelism set high for initial runs but not reduced for follow-on smaller batches. The Job completes, but if the team used completions: N and a misconfiguration kept pods running past completion, the pods are not terminated and are sitting idle on nodes.

Why namespace-level attribution is insufficient

The tooling most teams use first, including the namespace-based cost views in tools like Kubecost, gives you spend broken down by namespace. That is a real improvement over cluster-wide totals. But it is not fine-grained enough to be actionable for the most common categories of idle spend.

Take over-provisioned resource requests. The payments namespace might have 15 deployments. The namespace total is $920 this month. One deployment, payment-audit-worker, accounts for $340 of that because its resource requests were set when it was under much higher load and never adjusted. The namespace view shows you $920. It does not tell you that payment-audit-worker is the reason the namespace is expensive relative to its actual utilization.

To find that, you need utilization-versus-request comparison at the individual workload level: what is the deployment requesting, what is it actually using, and what is the cost of the gap? That calculation can be done manually by running resource utilization queries against your metrics backend and matching them to your resource request configs. Doing it across all workloads in a cluster with any regularity is not practical without automation.

A cost dashboard that shows you a namespace total is like a bank statement that shows you an account balance without itemizing transactions. You know you spent money. You do not know where.

The name is the unit of accountability

When you surface idle spend at the workload level with the workload's name, namespace, and owning team, something changes. The information goes from being a cost team's problem to being that specific team's problem. The payment-audit-worker team can look at the utilization data, recognize that their request specs were set during a peak load period that has since passed, and update the resource requests. The cost reduction is a direct consequence of that team's action, and the attribution makes that chain of causation visible.

Without the name, you have a number and a conversation with engineering management that goes: "Our cluster spend is up, we need to look at optimizing." With the name, you have a specific item: "payment-audit-worker in the payments namespace has resource requests that are 10x its average utilization, representing approximately $340 in monthly overallocation." The second conversation leads to action. The first leads to a working group.

This is why we built NudgeBee's cost attribution to name workloads rather than aggregate them. The goal is not to produce a comprehensive cost report. The goal is to produce a short list of specific workloads that are materially over-allocated or clearly idle, with enough information that the team responsible for each one can understand the issue and act on it without additional investigation.

The idle ReplicaSet problem in detail

The forgotten ReplicaSet is worth examining specifically because it is invisible in most cost tooling. Here is what it looks like in practice.

A Deployment undergoes a rolling update. The old ReplicaSet (call it payments-api-7d9f5b6c4) is scaled down to 0 replicas. Normal. Three weeks later, a developer running a hotfix directly against the old ReplicaSet using kubectl scale scales it up to 2 replicas to test something. The test finishes, the developer forgets to scale it back down, and moves on.

kubectl get replicasets \
  -n production \
  -o wide
NAME                        DESIRED   CURRENT   READY
payments-api-9c2b4d1e8      3         3         3
payments-api-7d9f5b6c4      2         2         2
payments-api-5a8e3f7d2      0         0         0

The active Deployment revision points to payments-api-9c2b4d1e8. The payments-api-7d9f5b6c4 ReplicaSet is old, not referenced by the current Deployment revision, receiving no Service traffic (because the Service selector points to pods owned by the current revision via the pod-template-hash label), but running 2 replicas consuming CPU and memory continuously.

Your namespace-level cost dashboard sees all pods in the namespace and includes those 2 replicas in the total. It does not know they are orphaned from the active Deployment revision. It shows you a namespace total that is inflated by these idle pods, but nothing flags them as different from the active pods.

Finding orphaned ReplicaSets manually requires cross-referencing the Deployment's spec.selector against the ReplicaSet's metadata.labels for the pod-template-hash that matches the current revision, then identifying all ReplicaSets in the namespace whose hash does not match and whose replica count is non-zero. It is a three-step lookup that is easy to automate and extremely tedious to do by hand across a cluster with dozens of namespaces.

What good attribution output looks like

The output that actually gets acted on is short and specific. Not a full cluster cost report sorted by spend descending. Something closer to: three to five workloads identified as materially over-allocated or idle, with the deployment name, namespace, the nature of the waste (over-provisioned requests vs. idle replicas vs. resource vs. orphaned Job pods), the estimated monthly cost of the waste, and the suggested remediation action.

The suggested action should be specific to the category. For over-provisioned requests, the suggestion is the new request value based on observed P95 utilization over the past 30 days. For orphaned ReplicaSets, the suggestion is a scale-down command. For idle Jobs, the suggestion is a cleanup command.

That level of specificity is the difference between a dashboard that gets checked occasionally and a tool that generates a specific action item for a specific engineer. The cost dashboard is not the end product. The action item is the end product. Everything before that is just data collection.

Get started

Stop triaging alerts manually.

NudgeBee traces alert floods to the responsible workload and surfaces a fix for your approval. Solo tier is free, no card needed.

Start free trial

More from the blog