Skip to content
NewFinOps, Kubernetes and AI, on one platform
Book a demo
All articles

Kubernetes

Kubernetes cost allocation that reconciles to the bill

Allocations built from list prices drift away from the invoice, and finance stops trusting them. How to split node cost from billed, net amortized cost, charge pods fairly, and show what belongs to nobody.

UnitVane Engineering

October 6, 20267 min read

A common first attempt at Kubernetes cost allocation multiplies each pod's CPU and memory requests by a public on-demand rate. The numbers look precise, down to the cent per pod. Then finance adds them up and they disagree with the invoice, because the invoice includes Savings Plans, reservations, negotiated discounts and credits, and the price sheet does not.

Once a total disagrees with the bill, every number underneath it is disputed, and the allocation program stalls. It matters: after waste reduction, allocation of cloud spend is the next priority for FinOps practitioners in the FinOps Foundation's 2025 survey (FinOps Foundation, State of FinOps 2025). The first rule of allocation is simple: start from what you were billed.

Why list-price allocation drifts

What the price sheet leaves outEffect on a list-price allocation
Savings Plans and reservationsCovered hours look full price; the commitment fee appears elsewhere on the bill
Negotiated and enterprise discountsEvery pod looks more expensive than it was
Credits and refundsAllocated total exceeds what was paid
Spot and preemptible pricingNodes billed at a fraction of on-demand look full price
Volumes, load balancers, data transferMissing entirely unless priced separately
Capacity no pod requestedDisappears, or is silently spread across teams
Each gap moves the allocated total away from the invoice in a different direction, so they do not cancel out.

Start from the billed cost of each node

Every major provider exports billing at resource level: AWS Cost and Usage Report (CUR 2.0), Azure Cost Management exports, and the Google Cloud detailed billing export in BigQuery. The FinOps Foundation's open FinOps Foundation, FOCUS specification format describes the same data in one schema. Join each node to its line items by provider ID and use the net amortized cost: commitments spread over the hours they cover, discounts and credits applied.

That number is what the node really cost you that day. Everything that follows only divides it up, so the parts always add back to the bill.

Split on max(request, usage)

Charging only for usage lets teams reserve large requests for free: the scheduler still holds that capacity for them. Charging only for requests makes a pod that bursts above its request look cheaper than it is. Charging the higher of the two, per resource and per hour, matches what each pod took from the node.

AWS's own split cost allocation data for EKS works from the instance's amortized cost and the share of CPU and memory each pod consumed (AWS, Understanding split cost allocation data). With usage metrics enabled, it uses the greater of each pod's requested and actual usage (AWS Cloud Financial Management, Improve cost visibility of Amazon EKS with AWS split cost allocation data), so the two approaches can be cross-checked.

Here is one node for one day. It is billed $12.00 net, with 8 vCPU and 32 GiB of memory. Split 50/50 between CPU and memory, that is $0.75 per vCPU-day and $0.1875 per GiB-day.

PodCPU request / usedMemory request / usedCharged (the higher of each)Cost for the day
checkout2 / 1 vCPU4 / 3 GiB2 vCPU, 4 GiB$2.25
search1 / 2.5 vCPU8 / 6 GiB2.5 vCPU, 8 GiB$3.375
batch1 / 0.5 vCPU4 / 2 GiB1 vCPU, 4 GiB$1.50
Infrastructure idle2.5 vCPU unclaimed16 GiB unclaimed$4.875
Billed$12.00
Illustrative example. Pods total $7.125, idle $4.875; together they equal the node's billed cost exactly.

Note what the rule does to each team. search uses more CPU than it requested and pays for what it used. checkout and batch requested more than they used and pay for their requests. And 40% of the node's cost is visible as idle instead of being spread invisibly across all three.

  • Split node cost between CPU and memory first; 50/50 is a reasonable default you can tune.
  • Charge each pod its share of max(request, usage) for each resource, for each hour it ran.
  • Keep GPU, persistent volume, network and load balancer cost in their own columns.

Keep idle and shared cost visible

Whatever no pod claimed is idle. Platform namespaces such as kube-system, monitoring and ingress-nginx are shared. Both are real costs, and hiding them inside team numbers is how allocation loses trust. Report them in their own columns, then decide who pays with an explicit policy.

PolicyHow it worksGood fit when
Platform absorbsIdle and shared stay with the platform teamThe platform team owns node sizing and wants the incentive
ProportionalRedistributed in proportion to each team's direct costYou want team totals to add up to the whole bill
HybridA share stays with the platform, the rest is proportionalSome idle is a deliberate safety margin, some is waste

Date your policies

Whichever policy you choose, record the date it took effect. Changing it next quarter should change next quarter's numbers, not rewrite this one's. See chargeback that survives a reorganization.

Show what you could not attribute

Some billed resources will not join to anything the cluster reports: a volume whose PersistentVolume was deleted, a node that ran between snapshots, a load balancer with no matching Service. Do not spread that cost into idle. List it as unallocated, with the resource IDs, so it becomes a list of fixes rather than a gap in the total.

Reconcile every day, with two numbers

A single percentage cannot say both "the total is right" and "everything has an owner". Report them separately, for the same clusters, period and cost basis:

Two daily figures
Reconciled to invoice = (direct + shared + idle + unallocated) / billed cost in scopeOwnership coverage    = cost that resolves to a team or cost center / billed cost in scope

A cluster can be 100% reconciled and 89% owned at the same time: every dollar is accounted for, and the unowned part is a named list of resources waiting for a label.

Who does what

How a cloud charge becomes a team cost

  1. 01

    Read the bill

    Import net amortized charges or use your data center rate card.

    Billing source

  2. 02

    Match resources

    Join billed nodes, volumes and load balancers with cluster inventory.

    KubeOn hub

  3. 03

    Allocate + assign

    Split node cost using max(request, usage), then apply ownership and shared-cost policies.

    Allocation engine

  4. 04

    Reconcile

    Review billed versus allocated totals and show unallocated spend separately.

    FinOps review

Read-only by default. KubeOn changes a cluster only where you grant write access, feature by feature.

Read the workflow
How a cloud charge becomes a team cost in KubeOn, and who is responsible at each step.

A checklist

  1. Billing export with resource IDs, cataloged and queryable.
  2. Node cost from net amortized line items.
  3. Pod cost on max(request, usage), per resource, per hour.
  4. Idle and shared in their own columns, with an effective-dated policy.
  5. Unallocated listed with resource IDs.
  6. A daily reconciliation per cluster, and ownership coverage beside it.
Why not allocate on usage only?

Because the scheduler reserves requests. A team that requests 4 vCPU and uses 1 still blocks 4 vCPU from everyone else, and the node still costs the same.

What if billing data arrives late?

Providers restate recent line items. Re-read the last few days of billing every day, and re-allocate a day once both its usage and its billing are in. Old days should be re-run deliberately, and logged.

Does this work for data center clusters?

Yes, with a rate card or amortized hardware cost in place of the invoice. See pricing Kubernetes in your own data center.

How does KubeOn do it?

Exactly as described: net amortized node cost, a 50/50 CPU and memory split by default, max(request, usage) per hour, idle and shared in their own columns, unallocated with up to 200 resource IDs, and both daily figures. Where AWS split cost allocation data exists, KubeOn uses AWS's per-pod cost directly. See the cost allocation methodology.

Let's get started

A 30-minute walkthrough on your own billing data, for your clouds, your clusters and your model providers.