
FinOps
Kubernetes Cost Allocation: OpenCost Namespace Showback to Chargeback
Namespace-level Kubernetes cost allocation with OpenCost: run showback and graduate to chargeback on bare-metal clusters where no cloud billing API exists.
Cloud FinOps has a comfortable premise: the provider hands you an itemised bill, and the whole discipline is splitting that bill fairly across the teams that generated it. Tag an instance, map it to a cost centre, chase the untagged remainder. The number at the top of the invoice is ground truth, supplied by someone else. Move that estate onto owned Kubernetes — bare metal in a colocation cage, an on-prem cluster you provisioned yourself — and the premise evaporates. There is no invoice, no billing API. The dollar figure cloud FinOps treats as an input is now something you have to construct.
This is not a reason to skip cost governance; it is the reason to do it deliberately. A cluster you own still costs real money — capex, power, cooling, data-centre space, network, the salaries of the people who run it — consumed unevenly by the namespaces sharing it. Without allocation none of it is visible, and the cluster drifts toward the tragedy of the commons that made the public cloud expensive in the first place. This article is a practitioner's path from zero to namespace-level showback with OpenCost, then the harder graduation to chargeback.
Why cloud FinOps tools have nothing to attribute on bare metal
Every hosted Kubernetes cost tool — and most SaaS FinOps platforms — begins by ingesting a cloud provider's billing export: the AWS Cost and Usage Report, the GCP billing dataset, the Azure cost management API. That export is where the actual prices live, and the tool's job is reconciliation: match an instance line item to the node it backs, then split that node's cost across its pods by consumption. Remove the export and the tool has node capacity and pod usage but no prices to multiply them by. It can tell you a namespace used 1,200 core-hours last week; it cannot tell you what a core-hour cost, because on your metal nobody billed you one.
OpenCost accommodates this directly by separating the cost model from the pricing source: for cloud nodes it reads the provider's rates, for owned nodes a pricing file you supply. That single seam — pricing as configuration, not a hardwired API call — is what makes credible on-prem cost allocation possible at all. The work the cloud hides now lands on you: model what an hour of your own hardware costs. The economics of running your own metal only hold up if you can measure them, and OpenCost is the instrument that does.
OpenCost, Kubecost, and why the split matters in 2026
OpenCost is a CNCF project — accepted to the Sandbox on 17 June 2022 and promoted to Incubating on 25 October 2024. It was built by Kubecost and donated to the foundation as the vendor-neutral specification and reference implementation of Kubernetes cost monitoring. Kubecost, the commercial product, is built on top of it, adding a richer UI, longer retention, optimisation recommendations, and enterprise governance.
The reason this lineage matters now is ownership. On 17 September 2024, IBM acquired Kubecost, folding it into the IBM FinOps suite alongside Apptio. Whenever the company stewarding an open-source project is absorbed by a larger vendor, teams that depend on it are right to ask what happens to the core's roadmap, licence, and pricing. OpenCost's answer is structural, not promissory: it lives under the CNCF, governed by the foundation rather than any one company's product strategy — which is the whole point of an incubating project. The governance is designed to survive the acquisition of any single contributor.
For an owned-infrastructure practice the calculus is usually simple: start with OpenCost — a single deployment that speaks Prometheus, publishes an allocation API, and asks nothing of the public internet. The commercial upgrade is incremental if you ever need it, but most teams find namespace showback and a Grafana dashboard cover the need without leaving the open-source core.
The allocation formula: max(request, usage), idle, and shared cost
OpenCost's cost model is worth understanding rather than treating as a black box, because every dispute chargeback provokes traces to one of its three moving parts. For each container over a window, a resource's cost is the amount allocated times its hourly price. The subtlety is the amount allocated: OpenCost bills the greater of what a container requested and what it actually used.
container_cost =
max(cpu_request, cpu_usage) * cpu_core_hours * cpu_$/core-hour
+ max(ram_request, ram_usage) * ram_gib_hours * ram_$/GiB-hour
+ gpu_count * gpu_hours * gpu_$/GPU-hour
+ pv_gib * pv_hours * storage_$/GiB-hour
namespace_cost = Σ container_cost (over all pods in the namespace)Charging max(request, usage) rather than usage alone is the single most important design choice for governance. A team that requests 32 cores and uses four is holding 28 no one else can schedule onto; that reservation has a real opportunity cost, and billing only the four used would reward the hoarding. OpenCost surfaces the gap as an efficiency score — often the first number that changes behaviour, because an engineer who sees their namespace at 12% efficiency does not need a chargeback invoice to go fix their requests.
The second moving part is idle. A node's full hourly cost is fixed the moment you rack it — you pay for all 64 cores whether pods request them or not. The sum of what pods allocated is almost always less than the node's cost, and the difference is idle: capacity you pay for and no namespace uses. OpenCost computes it as node cost minus allocations, and you must decide who owns it. Left unallocated it is honest but nobody's problem; spread proportionally it inflates every team's bill for a headroom decision no single team made. There is no correct answer, only a policy you choose and defend.
The third is shared cost: the namespaces that exist for everyone — kube-system, ingress, the monitoring stack, the mesh, cert-manager. Nobody's product feature, everybody's dependency. OpenCost lets you designate these as shared and distribute them evenly, by manual weights, or proportionally to each team's direct cost. Proportional is the usual default and the fairest starting point, but the choice turns political the moment it becomes a real charge — which is why it belongs in a published policy before the first invoice, not an argument after it.
Pricing the metal: the custom pricing ConfigMap
On owned infrastructure the pricing you feed OpenCost is a default.json of per-unit hourly rates — dollars per CPU-core-hour, per GiB-hour of memory, per GPU-hour, per GiB-hour of storage — delivered through the Helm chart's custom-pricing values or a mounted ConfigMap. The numbers are yours to derive, and deriving them honestly is the real work.
{
"provider": "custom",
"description": "On-prem amortized pricing — colo rack, 3-year node life",
"CPU": "0.0079",
"RAM": "0.0005",
"GPU": "0.80",
"storage": "0.00004",
"zoneNetworkEgress": "0.0",
"regionNetworkEgress": "0.0",
"internetNetworkEgress": "0.0",
"spotLabel": "",
"spotLabelValue": ""
}The arithmetic is a total-cost-of-ownership model you build once and revisit quarterly. A node that cost 12,000 in capital over a three-year life is 12,000 over 26,280 hours — roughly 0.46 per node-hour of pure hardware. Apply an overhead multiplier for what capex does not cover (power, cooling, the colocation fee, network, the amortised ops team), and a defensible 1.6× to 1.8× turns 0.46 into about 0.78 per node-hour. Split that across resources by a ratio you choose — commonly weighting CPU over RAM — and divide by the core and memory counts; a 64-core, 512 GiB node lands near the figures above.
GPU nodes deserve their own line, because they dominate the bill wherever they appear and a whole-GPU price is rarely how the hardware is used. If you slice accelerators with MIG or time-slicing to share a GPU across workloads, your model must price a slice, not a card — otherwise the first team to request a fraction absorbs the cost of the whole device and the report is wrong in its most expensive dimension.
Showback first: the report nobody can dispute
Showback is chargeback without the invoice: you attribute cost to each team and publish it, but no money moves. It is the correct first phase for a reason that has nothing to do with tooling and everything to do with trust — the first report a team sees will contain a number they dispute, and the value of showback is that the disagreement is free. They contest it, you find the mislabelled namespace or the idle-cost policy they did not know about, and the next report is more credible. Do that for a quarter and you have numbers an organisation will accept; skip it and the first wrong number poisons the whole programme.
Mechanically, showback on owned infrastructure is a query against the OpenCost allocation API and a Grafana dashboard reading its Prometheus metrics. If you already run self-hosted observability with Prometheus and Grafana, OpenCost slots in as one more metrics source — not a parallel cost platform.
# Per-namespace cost for the last 7 days, one accumulated figure each.
curl -sG http://opencost.opencost.svc:9003/allocation/compute \
--data-urlencode 'window=7d' \
--data-urlencode 'aggregate=namespace' \
--data-urlencode 'accumulate=true' | jq '.data[] |
{ namespace: .name,
cpu: .cpuCost, ram: .ramCost, gpu: .gpuCost,
pv: .pvCost, network: .networkCost,
shared: .sharedCost, total: .totalCost,
efficiency: .totalEfficiency }'
# Aggregate by a chargeback label instead of raw namespace —
# team, cost-centre, or product — for reporting that maps to the org chart.
curl -sG http://opencost.opencost.svc:9003/allocation/compute \
--data-urlencode 'window=30d' \
--data-urlencode 'aggregate=label:team' \
--data-urlencode 'accumulate=true' | jq '.data'That second query points at the discipline that makes or breaks the programme: aggregating by a label such as team or cost-centre rather than raw namespace. Namespaces are an implementation detail — teams own products that span several — so a durable strategy attaches a small set of mandatory labels to every workload and reports against those. Enforcing those labels is not OpenCost's job; it is a policy-as-code and golden-path concern, and the cost programme is a strong argument for making a team label non-negotiable at admission time.
The practical bar for moving beyond showback is attributability. A widely-used practitioner rule of thumb holds that at least 80% of spend should be cleanly attributable before you consider hard chargeback — below that, too much cost lands in an untagged or idle bucket no team accepts, and disputes swamp the signal. Showback is where you grind that bucket down: every report exposes another unlabelled workload, another orphaned volume, a namespace nobody will claim.
Graduating to chargeback: an organizational gate, not a technical one
Here is the counter-intuitive part: OpenCost can emit a per-team chargeback figure on the day you install it. What is hard is entirely organizational. A real budget line now moves on a number your platform produced, so every soft assumption in your model becomes a line item in an argument. The FinOps Foundation's State of FinOps research bears this out year after year: showback, not chargeback, remains the dominant reporting model, precisely because the organizational preconditions are harder to satisfy than the technical ones.
So the graduation is gated, each gate a precondition rather than a nice-to-have. Is at least 80% of spend attributable, with idle and shared costs modelled and disclosed? Have teams seen their showback numbers long enough — a quarter is a reasonable floor — to trust them and fix the errors those reports surfaced? Does every namespace have a named budget owner? A chargeback with no owner is a bill addressed to no one.
When you graduate, do it in the least punitive way that still changes behaviour: charge on a trailing average rather than a spiky single month, and keep the idle and shared-cost policies visible in the same report as the charge. Chargeback that feels arbitrary gets litigated; one that feels like an honest, predictable reflection of consumption gets acted on.
The failure modes that make chargeback political
Every one of these is survivable if you name it before it bites; left implicit, each becomes an argument you lose in a meeting.
- Idle cost nobody will own. The gap between node cost and allocated cost is real money. Whether you leave it unallocated, spread it proportionally, or charge it to a central platform budget is a policy decision — but it must be a decision, published, not an accident discovered when a team audits their bill.
- Shared overhead that dwarfs direct cost. In a small cluster the monitoring stack, ingress, and mesh can rival the workloads they serve. Distribute that proportionally and a lightly-loaded team pays a surprising premium; the distribution method has to be defended, not defaulted.
- GPU allocation on shared accelerators. Price a whole card and charge it to whichever workload first requested a fraction of it, and your most expensive line item is also your most wrong. Model the slice you actually schedule.
- The untagged bucket. Every workload without the mandatory
teamlabel is cost that lands nowhere and erodes trust in the whole report. This is why label enforcement belongs at admission control, upstream of the cost tool. - Stale pricing. Your
default.jsonwas right the quarter you wrote it. Power rates changed, you added nodes at a different price, the amortization schedule advanced. Pricing that is never revisited slowly diverges from reality until the whole model is quietly wrong. - The divide-by-730 scale bug. On custom-provider nodes, verify emitted hourly-cost metrics against your model; an off-by-730 error makes the cluster look almost free and no external invoice will contradict it.
The through-line is that owned-infrastructure cost allocation has no external referee: in the cloud a suspiciously low number is contradicted by the invoice at month's end; on your metal the only check is the rigour you build in. That is why this is an audit-grade concern, not a glanced-at dashboard — the pricing model, idle policy, shared-cost method, and label taxonomy are all evidence, and should be version-controlled and reviewed like any other part of the platform.
The exit ramp: the OpenCost spec as anti-lock-in
The optionality here is unusually strong because OpenCost is a specification before it is an implementation. The cost model is a published, vendor-neutral spec under the CNCF, and its output is standard: a JSON allocation API and Prometheus metrics with documented names. Nothing about your investment in labelling, pricing, and dashboards is trapped inside one tool. Point a different cost engine at the same metrics, adopt Kubecost's commercial layer over the same core, or export the data to your own warehouse — the substrate does not change.
That is the difference between owning a discipline and renting a dashboard. A SaaS FinOps platform is where your cost data goes to live inside someone else's product; when you leave, the reports, trends, and often the allocation logic leave with it. An OpenCost programme keeps the raw data in your own Prometheus and the pricing model in your own Git — the analysis is portable because the inputs never left. The same reasoning scales: with GitOps-managed fleets at scale, OpenCost per cluster feeding a central metrics store gives fleet-wide allocation with no per-node SaaS fee and no cost data leaving your control.
The long game: cost as an owned discipline
Cost governance is not a project with an end date — it is a permanent property of running shared infrastructure, and it compounds. The label taxonomy you enforce this year, the pricing model you refine each quarter, the showback culture that teaches engineers to read their own efficiency scores: these accrue into an organization that treats its owned cluster with the discipline the cloud's monthly invoice once imposed from outside — now internal, self-sustaining, and free of a vendor's meter.
We repatriated to save money, and for a year we had no idea whether it was working, because we'd left the one tool that measured cost back in the cloud we'd left. OpenCost was the instrument we should have racked on day one.
That is the sovereign case for FinOps without the SaaS, in one line: on owned infrastructure the bill is not handed to you, so the ability to construct it — accurately, per namespace, on your own terms — is not a convenience but a core competency. OpenCost is open, CNCF-governed, and already speaks the telemetry you run. The decision it leaves you is the one this engineering practice keeps returning to: whether the ledger that judges your infrastructure lives in a vendor's product, or in systems you own.
§FAQ/Common questions
Frequently asked
Can OpenCost do cost allocation on bare-metal or on-prem clusters with no cloud billing API?
Yes — that is one of its defining strengths over cloud-only FinOps tools. OpenCost separates the cost model from the pricing source. On cloud nodes it reads the provider's published rates; on owned nodes it reads a custom pricing file (default.json) you supply, containing per-unit hourly rates for CPU, RAM, GPU, and storage that you derive from your own hardware amortization and operating overheads. The cost model — allocating each namespace the greater of its resource requests or usage, computing idle as node cost minus allocated, and distributing shared overhead — runs identically whether the prices came from a billing API or from your own ConfigMap. The only extra work on owned metal is building the pricing model honestly, and verifying the emitted node_total_hourly_cost metric matches it, because a known on-prem bug can misinterpret hourly rates as monthly.
What is the difference between showback and chargeback in Kubernetes cost allocation?
Both attribute cost to the teams that consumed it; the difference is whether money moves. Showback publishes each namespace or team's cost as a report with no financial consequence — it builds visibility and, crucially, trust, because the first few reports will contain numbers teams dispute, and correcting those disputes for free is the whole point. Chargeback takes the same numbers and applies them to real team budgets. Chargeback is technically trivial for OpenCost to produce on day one, but it is an organizational gate: it should only follow once at least 80% of spend is cleanly attributable, teams have trusted their showback numbers for a quarter or more, and every namespace has a named budget owner. Sending a chargeback invoice before those preconditions are met turns cost governance into a political fight.
OpenCost vs Kubecost — which should a self-hosted team choose in 2026?
Start with OpenCost. It is the CNCF-governed open-source core — accepted to the CNCF Sandbox in June 2022 and promoted to Incubating in October 2024 — and it covers namespace-level showback, an allocation API, Prometheus metrics, and custom on-prem pricing with a single self-hosted deployment and no per-node fee. Kubecost is the commercial product IBM acquired in September 2024, built on the same OpenCost data model, adding a richer UI, longer retention, and optimisation recommendations. Because Kubecost consumes the same core, adopting it later is incremental rather than a re-instrumentation, so there is no lock-in cost to beginning with OpenCost. Most owned-infrastructure teams find that OpenCost plus a Grafana dashboard covers the governance need without ever moving to the commercial layer.
How does OpenCost handle idle and shared costs, and why does it matter for chargeback?
Idle cost is the gap between a node's full hourly cost — which you pay the moment you rack it — and the sum of what pods actually allocated on it; OpenCost computes it as node cost minus allocated cost. Shared cost covers namespaces that serve everyone, such as kube-system, ingress, and the monitoring stack, which OpenCost can distribute evenly, by manual weights, or proportionally to each team's direct cost. Both matter enormously for chargeback because they are the parts of a team's bill they did not directly cause. Whether idle is left unallocated or spread across teams, and which method distributes shared cost, are policy decisions that must be published and defended before any invoice, because they are the first things a team will contest when real budget is on the line.
Does running OpenCost send any cost or usage data to a third party?
No. OpenCost runs entirely inside your cluster: it reads resource requests and usage from the telemetry you already run (kube-state-metrics, cAdvisor, and Prometheus), multiplies them by prices you supply locally, and exposes the result through an in-cluster allocation API and Prometheus metrics. Nothing about your namespaces, workloads, pricing model, or spend leaves the cluster, and it requires no outbound internet access — which is what makes it appropriate for air-gapped and data-residency-constrained environments where a SaaS FinOps platform would be a non-starter. This is the same self-hosting property that makes cost governance an owned discipline rather than a dependency on a vendor's meter.
Further reading
- Cloud repatriation: the Kubernetes on-premises engineering playbook
- Platform engineering: golden paths and policy enforcement on Kubernetes
- Multi-cluster GitOps: scaling Argo CD and Flux to 100 clusters
- Self-hosted observability with OpenTelemetry, Prometheus, Grafana, and Loki
- NVIDIA GPU Operator: MIG and time-slicing for GPU sharing on Kubernetes
- vcluster: hard multi-tenancy and cluster consolidation on Kubernetes
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.