Cost attribution
Attribution answers "which team is spending this?" It takes the node cost from the cost model and divides it among the pods that reserved capacity, then rolls that up by workload and namespace.
Requests are the denominator
A pod is charged for what it requested, not what it used and not the node's full allocatable capacity.
This is the right choice because a request is what actually consumes the cluster: once a pod reserves 2 vCPU, the scheduler cannot give that 2 vCPU to anything else, whether the pod uses it or not. Charging on usage would make an idle over-provisioned service look free and hide the waste that rightsizing exists to surface.
The per-pod formula
For each hour a pod existed on a node:
vcpu = cpuRequestMilli / 1000
gib = memoryRequestBytes / 2^30
cpuCost = vcpu × costPerVCPUHour × podHours
memCost = gib × costPerGiBHour × podHoursThe unit costs come from the actual node that pod was on, in that hour — so a pod scheduled onto a c5a.large spot node is priced at that node's rate, not at a cluster average. A pod that moves between differently-priced nodes during the window is charged correctly for each hour.
Where several samples exist for one pod-hour, the largest request is used, which is immune to a partially-populated sample at the start of a scrape. Pod-hours are counted from distinct observations rather than assumed from pod count.
dailyUSD divides the total by the number of days the workload was actually observed, not by the requested window, so a workload that existed for two days of a 30-day query does not report a misleadingly small daily average.
How workloads are named
The owner reference is used when present. Otherwise the pod name has its generated suffix stripped with a regex:
-[a-z0-9]{5,10}(-[a-z0-9]{5})?$That collapses checkout-7d4b9f8c5-x2plq to checkout — Deployment ReplicaSet hash plus pod suffix. Namespace cost is the sum of its workloads.
Idle capacity is reported, not spread
attributedUSD = Σ all workload costs
clusterUSD = what the cluster actually cost
unallocatedUSD = max(0, clusterUSD − attributedUSD)unallocatedUSD is node capacity nobody requested: kubelet and system reserves, plus genuine idle headroom.
It is reported as its own figure rather than distributed across workloads. Spreading it would inflate every workload beyond what its own requests justify, and it would bury the single most actionable number on the page — the money you are paying for capacity nothing asked for.
The value is clamped at zero because requests can exceed billable capacity on an overcommitted node, and a negative "unallocated" is a nonsense figure to show anyone.
Reconciliation is exact by construction: attributed + unallocated = cluster. The cluster total is computed by the same code path as the main cost breakdown, not re-derived, so the two views cannot disagree.
Attribution reaches back 90 days: a pod's requests can only be priced against the instance it ran on, and that pairing is kept for 90 days. For a longer window the workloads and the cluster total — compute, storage and network — are all taken over the most recent 90 days, and coverage is marked partial. Unallocated therefore never includes spend from days no workload could be attributed for.
When a node cannot be priced
The workloads on it contribute no cost, and the instance type appears in coverage.unpriced. The gap is reported rather than filled with a guess. If no workload rows exist at all, the entire cluster cost shows as unallocated.
Reading the numbers
A healthy cluster has a modest unallocated share. A large one usually means one of:
- requests set far above real usage → see Rightsizing
- nodes larger than the workloads need → see Node optimization
- fragmentation: enough total slack to drop a node, spread too thin to schedule onto