Cost allocation divides what a Kubernetes cluster costs among the teams and services that use it. The arithmetic is simple. The decisions behind it are not: what counts as using a node, who pays for capacity nobody asked for, and who pays for the parts of the cluster everyone depends on. Make those decisions explicitly and write them down, and teams will argue about their workloads instead of your model.
Three buckets of cost
Every cluster's monthly cost falls into three buckets.
- Workload cost. What each container takes on the node it runs on. This part has an obvious owner.
- Idle cost. Node capacity that no container requested or used. Nodes come in fixed sizes, autoscalers keep headroom, and bin-packing is never perfect, so every cluster has some.
- Shared cost. Things the whole cluster needs that no single workload owns:
- system pods in
kube-system, - DaemonSets that run on every node (logging, monitoring, the CNI),
- the managed control plane fee,
- load balancers,
- storage for platform services.
- system pods in
Allocation is mostly about buckets two and three.
Measuring workload cost
Kubernetes schedules by requests, so a request reserves capacity whether it's used or not. A container can also use more than it requests, up to its limit, when the node has room. The two common rules:
- Requests only. Easy to explain, stable from day to day, and it matches how the scheduler thinks. It hides waste, and it lets workloads without requests (BestEffort pods) run for free.
- Greater of request and usage. Each container pays for what it reserved, or what it used if that was more, measured minute by minute. This is the rule in the OpenCost specification and the one we use. It's harder to game in either direction.
A node's price also has to be split between CPU and memory. Tools do this with a ratio. OpenCost's default, which we also use, prices a vCPU like several GiB of memory. The ratio matters less than using the same one everywhere and saying what it is.
What to do with idle cost
There are two honest options:
- Keep idle as its own line, owned by the platform team. This is the right view for the people who size nodes and tune the autoscaler, because idle is their lever, not the application teams'.
- Spread idle across workloads in proportion to their cost. Every team's number then adds up to the full bill, which is what finance usually wants.
Most organisations need both: one view for the platform team and one for the budget. The mistake is mixing them silently, so that a team's number changes because another team's workload moved off the node.
What to do with shared cost
Spread shared costs proportionally, like idle, or assign them to a platform owner. A few practical rules:
- Per-node DaemonSets scale with node count. Spreading them by compute share is reasonable.
- The control plane fee is per cluster. Spread it, or assign it to whoever owns the cluster.
- Load balancers and storage usually have an owner (the Service or PersistentVolumeClaim that created them). Attribute them directly where you can, and only spread what's genuinely shared.
Ownership: labels and namespaces
Allocation is only as good as ownership data. The usual setup:
- One namespace per team or service gives you a coarse owner for free.
- Labels for finer ownership, set by your deployment tooling: a
teamlabel on every workload, plusenvironmentandcost-centerif finance needs them. Kubernetes' recommended labels (app.kubernetes.io/name,app.kubernetes.io/part-of) help group workloads into services. - Enforce labels at admission (with a policy engine) so new workloads can't arrive unowned.
- Have a way to fix gaps without redeploying. Assign a namespace or workload to a team by hand when its labels are wrong or missing, and let that assignment win.
- Report unowned cost as unowned. A large "no team" bucket is information. Hiding it in a shared line makes it permanent.
Some costs are genuinely shared between teams, like a shared database proxy. Split them by an agreed percentage and write the split down.
Showback before chargeback
Showback tells teams what they cost. Chargeback moves the money through internal billing. Start with showback. Publish each team's number monthly, with the method and the top movers, and give it a few cycles: the first reports always surface ownership mistakes. Chargeback works once teams agree the numbers are fair, and that agreement comes from the decisions above being visible.
Two habits help:
- Freeze closed periods. Once a month is reported, ownership changes apply going forward, not to history. Otherwise last quarter's number changes after it was presented.
- Show the method next to the number. "Greater of request and usage, idle spread, shared costs included" in one line saves a meeting.
How Kubeadapt handles allocation
Kubeadapt charges each container for the greater of its request and usage per minute, splits node prices with the OpenCost ratios, caps a workload at its node's share, and then handles idle and shared costs:
- Two cost modes. Fully Loaded spreads idle capacity and shared costs (control plane, system pods, DaemonSets, load balancers, persistent volumes) in proportion to compute. Workload Only shows workloads alone. The method is documented in how costs are computed.
- Teams and departments. Cost Attribution assigns them from labels you choose (
team,department,ownerand so on), or by hand in an assignment workbench, where manual assignments win over labels. A namespace, workload or volume can be split across up to ten teams. - Closed reports stay closed. Ownership changes apply going forward; finalized reports keep the attribution they had.
- Showback, not a billing system. Kubeadapt doesn't generate internal invoices. Team numbers export to CSV, through the API, or as a FOCUS file.
See trace cost to a team for a walkthrough, or get started.


