Skip to main content

OpenCost Turns a Fixed Hetzner Bill Into Per-Namespace Chargeback — Without Shipping Cost Data to a SaaS Dashboard

9 min readDora NodaDora Noda
Share
On this page

Every Kubernetes cost dashboard on the market solves the same problem the same way: connect your cloud billing account, let the vendor's backend ingest your usage data, get a pretty chart back. That works fine if "cost" means a metered AWS or GCP invoice a SaaS tool can reconcile against its own copy of your billing API. It doesn't work at all if "cost" means a fixed monthly bill for Hetzner boxes you already own, where there's no billing API to connect to and no vendor account to hand your workload metrics over to in the first place.

OpenCost — the CNCF-incubating allocation engine that computes cost entirely from your own cluster's Prometheus metrics — was built for the first case and happens to solve the second one too. It just requires doing the arithmetic yourself instead of trusting a dashboard to do it. Below is that arithmetic: a real post-hike Hetzner node price, run through OpenCost's actual normalization and allocation rules, landing on real per-namespace euro figures.

What OpenCost Actually Is, and Why It Doesn't Phone Home

OpenCost started as the allocation engine inside Kubecost, and Kubecost donated it to the CNCF in June 2022 as a neutral, vendor-independent core — contributed alongside Adobe, Armory, AWS, D2iQ, Google, Mindcurv, New Relic, and SUSE. It moved from Sandbox to Incubating status on October 25, 2024. In September 2024, IBM acquired Kubecost outright and folded it into the IBM FinOps Suite alongside Cloudability and Turbonomic — but OpenCost itself stayed a CNCF project with its own governance, and every Kubecost tier, including IBM's, still runs OpenCost underneath as its allocation core.

That governance history answers "who controls the project." It doesn't answer "where does my usage data go" — and that's the part that actually matters for a platform running on owned hardware. The answer is: nowhere, unless you tell it to.

OpenCost computes every cost figure from two local inputs — the CPU, RAM, GPU, and PV requests/usage it scrapes from your cluster's own Prometheus, and a pricing config you supply — and serves the result from its own /allocation HTTP API running inside your cluster. For AWS, Azure, and GCP, it can optionally call each provider's public billing API to fetch list prices; for on-prem and bare-metal clusters, that call never happens at all, because there's no cloud billing API to call. You give it a CSV or JSON price sheet, and it never has to leave the cluster to price anything.

Compare that to a SaaS cost-optimization platform like CAST AI or ScaleOps: the entire product depends on you granting it API access to your cloud account so its backend can pull usage and billing data out to compute recommendations centrally. That's a reasonable trade for a team that wants a hosted dashboard and is fine with a vendor holding a copy of its infrastructure spend. It's a non-starter for a platform whose whole pitch to tenants is that nothing about their deployment — including what it costs to run — leaves machines the operator controls.

The Allocation Rule: max(request, usage), Plus What Nobody Claimed

OpenCost prices Kubernetes resources with one consistent rule: a workload's cost for CPU, GPU, and RAM is max(request, usage), priced at the node's rate for that resource. If a pod requests 1 CPU core and only uses 0.4, it's still billed for the full core it reserved — that capacity was unavailable to anything else, whether it used it or not. If a pod requests 1 core but bursts to 1.4 under load, it's billed for the 1.4 it actually consumed. Either way, the workload pays for whichever number was larger, because that's the number that actually constrained the node.

Whatever's left over — capacity the node was paid for but that no workload's request or usage claimed — becomes idle cost. A node running at 15% average CPU utilization has 85% of its hourly cost sitting in the idle bucket, and OpenCost tracks that as its own line rather than hiding it inside someone else's namespace total. That distinction — a workload's direct cost versus the cluster's unclaimed capacity — is the entire mechanism the worked example below runs through with real numbers.

Worked Example: One Hetzner Node, Four Cost Lines

Take a single CCX23 node — 4 dedicated vCPUs, 16 GB RAM — at Hetzner's post-June-15-2026 price of €85.99/month. That's €0.1178/hour (€85.99 ÷ 730 hours). There's no metered bill to reconcile here; that number is fixed whether the node sits idle or runs flat out, which is exactly the "cost is hardware spend, not a cloud invoice" problem a SaaS billing-API integration can't help with.

OpenCost's on-prem pricing config takes whatever base CPU/RAM/GPU prices you supply and normalizes them so the components sum to the node's real cost, holding your chosen ratio constant — its own documented example takes a $30 GPU / $30 CPU / $10-per-GB-RAM input and normalizes it to $15 / $15 / $5, preserving CPU and GPU each pricing at 3x a gigabyte of RAM. Apply that same 3x convention to a CPU/RAM-only bare-metal node — no GPU — and the node's €0.1178/hour splits as:

  • RAM: €0.004207 per GB-hour
  • CPU: €0.012621 per core-hour (3x the RAM rate)

(Check: 4 cores × €0.012621 + 16 GB × €0.004207 = €0.1178/hour — the full node price, exactly.)

Now put three tenant namespaces on that node, each with a request and a measured usage — the two numbers OpenCost's max() rule actually compares:

NamespaceCPU request / usageBilled CPURAM request / usageBilled RAMHourly cost
web1.5 / 1.2 cores1.5 (request wins)4 / 3 GB4 GB (request wins)€0.0358
worker1.0 / 1.4 cores1.4 (usage wins — burst)2 / 2.5 GB2.5 GB (usage wins)€0.0282
postgres1.0 / 0.9 cores1.0 (request wins)4 / 3.8 GB4 GB (request wins)€0.0294
idleunclaimed: 0.1 core, 5.5 GB€0.0244

Multiply every hourly figure by 730 hours and the fixed €85.99 monthly node bill splits into real per-namespace numbers: web costs €26.10, worker €20.58, postgres €21.50, and €17.81 sits in idle — capacity Hetzner is billing for that no namespace's request or usage actually claimed.

That idle line is where showback and chargeback diverge. A showback report can display it as its own "platform overhead" row, honest about the fact that a quarter of this node's cost is currently unclaimed. A chargeback report has to land it on somebody, typically re-spread across the three tenants in proportion to their direct cost share — which pushes web to €32.92, worker to €25.96, and postgres to €27.11, the three numbers still summing to the exact €85.99 the node actually costs. Either way, every euro on the invoice is now accounted for by name, which is the entire point of running this exercise at all.

Showback First, Chargeback Later — Even on Owned Hardware

That idle-cost decision is exactly why most organizations don't jump straight to chargeback. Only about 14% of companies currently run an active chargeback program, and the usual reason isn't organizational resistance — it's that showback has to run first long enough to build trust in the numbers before anyone's willing to move real budget against them. Showback means a namespace owner sees "your worker deployment cost €20.58 this month" with no financial consequence attached. Chargeback means that €20.58 (or the idle-loaded €25.96) actually debits a team's budget or shows up as a line item on an internal invoice.

On owned hardware, that maturity path matters even more than it does on a metered cloud bill, because there's a second trust problem stacked on top of the first: the idle-cost reallocation choice above is a policy decision, not a fact OpenCost hands you. A platform team has to decide — and document — whether idle capacity is a shared cost every tenant absorbs a slice of, or an overhead line the platform itself eats. Get that wrong, or change it without warning, and a chargeback number a team budgeted against moves for reasons that have nothing to do with anything they did. Showback is where you work out that policy in front of the numbers, before it has budget consequences attached.

What OpenCost Doesn't Give You — and What a Platform Team Still Has to Build

None of the arithmetic above came out of a dashboard. OpenCost ships a basic web UI and the /allocation API — query it with a window (today, 30d, an RFC3339 range) and an aggregate dimension (namespace, controller, label:<key>, or a comma-separated combination), and it returns exactly the per-namespace cost and resource breakdown the table above was built from. What it doesn't ship: budget thresholds, anomaly alerts, Slack notifications, cost-saving recommendations, or a federated view across more than one cluster. Kubecost's own paid tiers exist specifically to close that gap — which is the tell that OpenCost was never meant to be the finished product, just the allocation core underneath one.

For a platform team running its own fleet, that means the real deliverable isn't "install OpenCost" — it's "point Grafana at OpenCost's /allocation API, build the per-namespace and per-tenant panels a finance conversation actually needs, and wire alerting on top for the numbers that should page someone." That's more work than clicking through a vendor's hosted dashboard. It's also the only version of this where the CPU-request-vs-usage numbers behind every chargeback figure stay inside the same boundary as the workloads they're pricing, instead of becoming another vendor's copy of your infrastructure spend.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Cost data for those machines is yours to allocate however you decide, not a SaaS vendor's to hold. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex