Kubernetes Cost Allocation with OpenCost: Showback, Idle Cost and Shared Cost Attribution
The invoice says one number and the finance team wants five. A cloud bill lists virtual machines, disks and load balancers; it knows nothing about namespaces, teams or products. The moment you put forty services on a shared node pool, the bill and the organisation chart stop lining up, and someone has to decide who pays for the capacity nobody is using. That decision is policy, not arithmetic, and most teams make it by accident.
This is why kubernetes cost allocation opencost keeps appearing in FinOps roadmaps in 2026. OpenCost, the Cloud Native Computing Foundation (CNCF) incubating project, gives you a vendor-neutral cost model and an API. What it cannot give you is agreement about idle capacity, shared platform cost and unlabelled workloads. Those are the three places where showback numbers get disputed.
You will leave with the exact allocation model OpenCost implements, a worked numeric example of idle and shared cost attribution you can reproduce, a runnable script against the Allocation API, a label-hygiene design, and a list of failure modes that quietly corrupt the numbers.
What this covers: the allocation model and its data sources, idle versus shared versus overhead, API mechanics, label governance, cloud-bill reconciliation, GPU caveats, a worked example with code, and what goes wrong in production.
Context and Background
Kubernetes hides cost in two ways. First, the scheduler packs many tenants onto one node, so no single bill line belongs to one team. Second, resources are reserved by requests rather than consumed, so what a team asks for and what it uses diverge, sometimes by a factor of three or more. Any allocation tool has to choose which of the two to charge for, and that choice shapes behaviour.
OpenCost began as the cost-allocation engine inside Kubecost and was contributed to the CNCF, where it is an incubating project. According to the OpenCost FAQ, Kubecost continues to use the engine in every version and layers commercial features on top, such as reconciling published list prices with your negotiated discounts. The CNCF’s January 2026 retrospective says IBM Kubecost, Randoli, the wider community and several cloud providers maintain it, and names 2026 priorities including a new data model (KubeModel), AI usage cost tracking and supply chain security. It also describes a beta “promless” mode that avoids a Prometheus dependency; the classic deployment still requires Prometheus for scraping and storage, as the installation docs state.
The project is deliberately specified. The OpenCost specification defines how cluster cost decomposes, how each resource is priced and how workload cost is derived, so two implementations should agree on the arithmetic even if they differ on data sources. That specification is the reason this post can reason about a model rather than a product UI.
Two neighbouring topics are covered elsewhere on this site and are not repeated here. For installation, VPA-driven rightsizing and a chargeback walkthrough, see OpenCost, VPA rightsizing, showback and chargeback. For billing-side normalisation across clouds, see the FOCUS specification pipeline. This post sits between them and concentrates on the attribution decisions: what counts as idle, who owns shared cost, and how label quality bounds the accuracy of every number.
Showback means reporting what each team’s workloads cost without moving money. Chargeback means actually journalling that cost to the team’s budget. Showback tolerates approximation; chargeback needs a dispute process, an audited method and stable rules, which is why most organisations spend a year in showback first. Everything below assumes showback and flags where chargeback would demand more.
The OpenCost Allocation Model: From Node Price to Workload Cost
OpenCost prices every node per resource per hour, measures what each container holds on that node using the larger of its request and its usage, and charges the container for that share. Whatever node cost no container claims is idle cost. Cluster-wide costs that belong to no tenant, such as management fees, are overhead. Everything else is policy you layer on top.

Figure 1: Data flow of kubernetes cost allocation with OpenCost, from pricing sources and cluster metrics through the cost model to API consumers.
Figure 1 shows the pipeline. Pricing arrives from a cloud billing integration or a custom pricing sheet. Cluster facts arrive through Prometheus, fed by node-exporter and kube-state-metrics. The OpenCost cost model combines them, exports cost metrics back to Prometheus, and serves the Allocation API on port 9003 for the UI, scripts and dashboards. Consumers sit at the end, which matters: every dispute downstream traces back to one of the three inputs on the left.
Pricing: per-resource hourly rates
The specification prices allocation costs as amount multiplied by duration multiplied by an hourly rate. For CPU that is cores times hours times dollars per core-hour; for memory it is gigabytes times hours times dollars per gigabyte-hour. A node is a bundle, so the model decomposes the node’s hourly price into a CPU rate and a RAM rate, and a GPU rate where GPUs exist. OpenCost exports these as node_cpu_hourly_cost, node_ram_hourly_cost and node_gpu_hourly_cost, alongside node_total_hourly_cost, each labelled by node.
The decomposition is a modelling choice, not a billing fact. Your provider bills one price for an 8 vCPU, 32 GiB instance; no invoice line says what a core costs. Whatever ratio the model uses, the sum for the node is preserved, so cluster totals stay correct while the CPU versus memory split changes who is charged more. A memory-heavy service and a CPU-heavy service can swap rank depending on the ratio, which is worth knowing before anyone compares teams to the cent.
Storage follows the same pattern. Persistent volumes carry an hourly price per gibibyte exposed as pv_hourly_cost, and workload storage cost is based on the persistent volume claim request attached at pod level, exposed through pod_pvc_allocation. A claim for 500 GiB that holds 20 GiB of data costs 500 GiB, because the disk was provisioned at 500 GiB.
Usage versus request: the max rule
The specification’s central rule is that for resources with allocation costs, workload cost is the maximum of request and usage, computed per container and then aggregated. A container that requests 2 cores and uses 0.5 is charged for 2, because the scheduler reserved those two cores and nobody else could use them. A container that requests 0.5 and bursts to 2 is charged for 2, because it took capacity it had not reserved.
This rule is a deliberate behavioural nudge. Charging for requests alone would reward teams that request nothing and burst everywhere, leaving the scheduler blind. Charging for usage alone would let teams over-reserve for free, which is exactly what drives cluster bloat. Taking the maximum penalises both habits and keeps the cost aligned with what the scheduler and the node actually experienced.
The consequence for engineers is practical. Rightsizing requests towards real usage directly lowers a workload’s allocated cost, because the larger term shrinks. That is why this model pairs naturally with in-place pod resize and vertical scaling: once requests can move without restarts, the cost signal can drive a feedback loop rather than a quarterly review.
Idle, overhead and the cluster identity
The specification states total cluster cost as asset costs plus overhead costs, with asset costs being the sum of resource allocation costs and resource usage costs. Cluster idle cost is the cluster’s asset cost minus the cost allocated to workloads. Idle percentage is idle cost divided by resource allocation cost, and the specification says usage-only resources, such as egress bytes, should be excluded from that percentage because they cannot be idle in the same sense.
Read that as an accounting identity: every dollar of node cost lands in exactly one bucket, either a workload, idle, or overhead. The identity is the best correctness test you have. If workload plus idle plus overhead does not equal what the bill says for those nodes, something is mis-joined, and no amount of dashboard polish repairs it.
What the model does not cover
Two gaps deserve honesty. The specification gives no separate GPU allocation formula; GPU pricing is treated like CPU pricing in its normalisation appendix. It also gives no network asset formula, only that workload network cost is measured as bytes crossing zones, regions or the internet, varying by provider. OpenCost exposes zone, region and internet egress cost metrics labelled by namespace and service, and load balancer cost as kubecost_load_balancer_cost, but how faithfully those reflect your actual bill depends on provider and configuration. Treat network and load balancer lines as estimates until reconciled against billing.
Finally, labels. The specification lists labels and annotations as aggregation dimensions but defines no formula for allocating cost by label. Labels are a grouping key, nothing more, which means their quality is entirely your responsibility. The labelling section later in this post returns to that.
Idle and Shared Cost Attribution: A Worked Example
Idle cost allocation is where showback politics lives. There are three honest options: leave idle visible as its own line owned by the platform team, spread it across tenants in proportion to what they use, or split it into intentional headroom and waste and charge only the waste to its cause. Each answers a different question, and mixing them in one report is how numbers lose trust.

Figure 2: Where each dollar of node cost goes in kubernetes showback, and the three places policy enters: idle sharing, platform redistribution and unallocated handling.
Figure 2 traces the flow. Node cost divides into workload, idle and overhead. Idle either stays separate or is folded into workloads. Shared platform workloads, such as monitoring and the kube-system namespace, then either stay with the platform team or are redistributed to tenants. The two policy switches are independent, and that independence is the point.
The example cluster
All numbers in this example are illustrative, chosen so the arithmetic is easy to follow; they are not benchmarks and not real prices. Take four identical nodes, each with 8 vCPU and 32 GiB, priced at $0.40 per hour. Suppose the model decomposes this as $0.028 per core-hour and $0.0055 per GiB-hour, since 8 x 0.028 + 32 x 0.0055 = 0.224 + 0.176 = 0.40. The cluster costs $1.60 per hour: $0.896 for 32 cores and $0.704 for 128 GiB.
Four namespaces run, with average max(request, usage) per hour:
| Namespace | CPU cores | RAM GiB | CPU cost | RAM cost | Workload cost |
|---|---|---|---|---|---|
| payments | 6 | 20 | $0.168 | $0.110 | $0.278 |
| search | 8 | 40 | $0.224 | $0.220 | $0.444 |
| data | 4 | 24 | $0.112 | $0.132 | $0.244 |
| platform (kube-system, monitoring) | 3 | 8 | $0.084 | $0.044 | $0.128 |
| Total workloads | 21 | 92 | $0.588 | $0.506 | $1.094 |
Idle cost is $1.60 minus $1.094, which is $0.506 per hour: 11 idle cores ($0.308) and 36 idle GiB ($0.198). Idle is about 31.6% of cluster cost. If you only watched the workload column you would believe the cluster costs $1.094 an hour.
Policy one: idle stays separate
With shareIdle=false, the OpenCost default, the report shows payments at $0.278, search at $0.444, data at $0.244, platform at $0.128 and a separate __idle__ line of $0.506 when includeIdle=true. This is the cleanest view for engineers, because a team’s number moves only when that team’s requests or usage move. It is also the right view for the platform owner, who can see the capacity buffer as a line item and own it.
Policy two: idle shared proportionally
With shareIdle=true, OpenCost spreads idle across non-idle allocations in proportion to each allocation’s non-idle cost, calculated separately for each resource. The per-resource detail matters. CPU idle is spread by CPU share: the CPU multiplier is 0.896 / 0.588, about 1.524. Memory idle is spread by memory share: the RAM multiplier is 0.704 / 0.506, about 1.391.
Payments then costs 0.168 x 1.524 + 0.110 x 1.391, about $0.256 + $0.153 = $0.409. Search costs about $0.341 + $0.306 = $0.647. Data costs about $0.171 + $0.184 = $0.354. Platform costs about $0.128 + $0.061 = $0.189. The four sum to $1.60, the full bill. Every team’s number is now about 47% higher than its direct cost, and the increase is exactly the share of idle its resource mix attracted.
Policy three: platform redistributed to tenants
The specification lists three ways to distribute shared cost: uniformly across tenants, proportionally to each tenant’s asset cost consumption, or by a custom metric such as network egress. Suppose we spread the platform’s $0.189 proportionally to tenant cost. Tenant cost after idle sharing is $0.409 + $0.647 + $0.354 = $1.410. Payments takes 29.0%, about $0.055, ending at roughly $0.464. Search takes 45.9%, about $0.087, ending at roughly $0.734. Data takes 25.1%, about $0.047, ending at roughly $0.402. Totals sum to $1.60 within rounding.
Notice what happened to the story. Search’s direct cost was $0.444; its fully loaded cost is $0.734, a 65% uplift. Neither number is wrong. They answer “what does this workload consume” and “what share of the cluster does this team cause us to run”. Showback reports should name which one they present.
Which policy to choose
Use the separate-idle view for engineering dashboards, since it keeps cost responsive to engineering decisions. Use the fully loaded view for product margin and budgeting, since totals must tie to the bill. Cost-weighted proportional sharing has one flaw: it penalises large tenants for idle they may not have caused, because idle often comes from fragmentation, topology constraints or autoscaler settings rather than from any tenant. Where idle has a clear owner, such as a pool kept warm for a latency promise, charge it there.
Querying the Allocation API and Reproducing the Math
The fastest way to trust OpenCost is to reproduce its arithmetic yourself on a small window. This section shows the API calls, the Prometheus queries that back them, and a script that applies the three policies above to live data.

Figure 3: Request flow for an Allocation API call: the client asks for a window and aggregation, the cost model queries Prometheus at the configured resolution, applies pricing and returns allocation sets.
Figure 3 shows what a request triggers. The cost model does not store a cost database of its own for this call; it queries Prometheus across the window at the configured resolution, joins the result with pricing, applies the max rule per container, aggregates, and returns. That is why long windows at fine resolution are slow and why Prometheus retention bounds how far back you can look.
Installing and reaching the API
The Helm chart assumes an existing Prometheus. These commands come from the OpenCost Helm documentation; adjust the Prometheus service details in values.yaml to your cluster, and enable persistence for production as the docs recommend.
helm repo add opencost-charts https://opencost.github.io/opencost-helm-chart
helm repo update
helm install opencost opencost-charts/opencost \
--namespace opencost --create-namespace -f values.yaml
kubectl port-forward --namespace opencost service/opencost 9003 9090
curl -G http://localhost:9003/allocation \
-d window=7d -d aggregate=namespace -d includeIdle=true
Port 9090 serves the UI and 9003 the API. The documented parameters for /allocation are window (keywords such as today, durations such as 7d, or timestamp pairs), aggregate (cluster, node, namespace, controller kind, controller, service, pod, container, label:NAME or annotation:NAME, comma-separated for multi-level grouping), step, resolution (default 1m), includeIdle, shareIdle and idleByNode. Setting idleByNode=true computes idle per node instead of per cluster, which is more honest when pools differ in size or price. I could not find shareNamespaces, shareLabels or shareCost in the current OpenCost docs for this endpoint, so do shared-cost redistribution in your own reporting layer, as the script below does, rather than depending on parameters that may only exist in commercial tooling.
Prometheus queries for cross-checks
OpenCost exports its cost metrics, so you can cross-check the API with plain PromQL. These are adapted from the documented examples; 730 is the conventional hours-per-month factor.
# Monthly run-rate of all nodes
sum(node_total_hourly_cost) * 730
# Monthly run-rate of each namespace's CPU allocation
sum(avg(container_cpu_allocation) by (namespace)) * 730
# Hourly load balancer cost per namespace
sum(kubecost_load_balancer_cost) by (namespace)
If the first query disagrees with your node bill extract by more than a few percent, fix pricing before looking at allocation. Allocation errors are second-order; a wrong node price is first-order.
A reconciliation script
The script below fetches namespace allocations twice, once with idle kept separate and once with shareIdle=true, then redistributes a named platform namespace proportionally. It asserts that every view sums to the same total, which is the accounting identity from earlier. The attribution function is pure, so it can be tested without a cluster.
#!/usr/bin/env python3
"""Showback with separate idle, shared idle and redistributed platform cost."""
import json, sys, urllib.parse, urllib.request
BASE = "http://localhost:9003"
def fetch(window, aggregate, **extra):
q = {"window": window, "aggregate": aggregate, **extra}
url = f"{BASE}/allocation?{urllib.parse.urlencode(q)}"
with urllib.request.urlopen(url, timeout=60) as r:
body = json.load(r)
merged = {}
for alloc_set in body["data"]: # one set per step
for name, a in alloc_set.items():
merged[name] = merged.get(name, 0.0) + a["totalCost"]
return merged
def redistribute(costs, platform_keys, idle_key="__idle__"):
"""Spread platform cost over tenants in proportion to tenant cost."""
skip = set(platform_keys) | {idle_key, "__unallocated__"}
tenants = {k: v for k, v in costs.items() if k not in skip}
pool = sum(costs.get(k, 0.0) for k in platform_keys)
base = sum(tenants.values())
out = {k: v + pool * v / base for k, v in tenants.items()}
for k in (idle_key, "__unallocated__"):
if k in costs:
out[k] = costs[k]
return out
def check(label, view, expected):
total = sum(view.values())
assert abs(total - expected) < 1e-6 * max(1, expected), (label, total, expected)
def selftest():
shared = {"payments": 0.409, "search": 0.6474, "data": 0.3544, "platform": 0.1892}
out = redistribute(shared, ["platform"])
check("selftest", out, 1.6)
for k, v in sorted(out.items()):
print(f"{k:10s} {v:.3f}")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "selftest":
selftest(); sys.exit(0)
window = sys.argv[1] if len(sys.argv) > 1 else "7d"
separate = fetch(window, "namespace", includeIdle="true")
loaded = fetch(window, "namespace", includeIdle="true", shareIdle="true")
total = sum(separate.values())
check("shareIdle view", loaded, total)
final = redistribute(loaded, ["kube-system", "monitoring"])
check("redistributed view", final, total)
for k in sorted(final, key=final.get, reverse=True):
print(f"{k:24s} direct={separate.get(k, 0):10.2f} loaded={final[k]:10.2f}")
The selftest mode reproduces the worked example offline: feeding it the idle-shared numbers and redistributing the platform line prints the redistributed per-team totals, and asserts they still sum to $1.60. Against a live cluster, the asserts are your canary. If the shared-idle view does not sum to the separate view, you have either a window mismatch between the two calls or an allocation set that was dropped.
A caution on the response shape. I read the documented request parameters but the docs pages I fetched did not enumerate the allocation response fields, so the script relies on data being a list of allocation sets keyed by name, each with a totalCost field. Confirm that against the OpenAPI definition in the OpenCost repository for your version before depending on it.
Label Governance, Billing Reconciliation and GPU Cost
Allocation math is the easy part. The hard part is that cost can only be attributed to the keys your workloads actually carry, and that the model’s prices have to meet the bill somewhere.
Kubernetes FinOps labels as a data-quality problem
Aggregating by label:team groups pods by the value of that label. A pod without it lands in an unallocated bucket, which I recall appearing as __unallocated__ in OpenCost output, though you should confirm that name against your version. The size of that bucket is your single best data-quality metric: a showback report with 30% unallocated cost is a report where one dollar in three has no owner.

Figure 4: A label governance loop for kubernetes finops labels: enforce at admission, inherit from the namespace, aggregate in OpenCost, and review the unallocated share.
Figure 4 shows a loop rather than a one-off cleanup. Enforcement at admission prevents new drift, namespace-level defaults cover the long tail, aggregation produces the report, and a review of unallocated cost feeds back into policy. A few design rules make the loop work.
First, label the namespace, not only the pod. Cost ownership usually follows the namespace boundary, and a small set of namespace labels (team, cost-centre, environment) is far easier to enforce than per-pod labels across thousands of Deployments. Aggregating by namespace first and joining to an ownership table you control is more robust than relying on every chart to propagate labels.
Second, enforce at admission. A validating policy that rejects Deployments in tenant namespaces lacking the required keys turns hygiene from a chase into a gate. ValidatingAdmissionPolicy with CEL can express this without a webhook, which keeps the cost-control path from becoming a cluster availability risk.
Third, restrict values, not just keys. team=payments, team=Payments and team=payment are three teams to an aggregator. Validate values against an allowlist, and check how your OpenCost version normalises label names containing dots or slashes before you build dashboards on them; I could not verify the sanitisation rules from the documentation, so test with one real label first.
Fourth, label the shared things honestly. Ingress controllers, service meshes, observability stacks and CI runners serve everyone. Give them a platform label and let the redistribution policy handle them, rather than hiding them in one tenant’s bill.
Reconciling with the cloud bill
OpenCost’s prices come from a billing integration or from a custom pricing sheet. The FAQ lists integrations for AWS, Azure, GCP and OCI and custom pricing sheets for on-premises clusters. The caveat from that same FAQ is that the commercial Kubecost tier is what reconciles published bills with negotiated discounts. If you run the open-source project alone, assume list-price or configured-price allocation until you verify otherwise.
That matters because commitments distort cost. Savings Plans, reserved instances, committed use discounts and enterprise agreements lower the effective price of a node, but they apply to an account or billing family, not to a pod. Two honest approaches exist. You can allocate at list price and report the discount as a separate organisation-level line, or you can compute an effective blended rate per node pool and feed it as custom pricing. The second approach produces totals closer to the bill; the first keeps team numbers stable when the commitment portfolio changes.
Either way, run a monthly reconciliation. Sum node-level allocation cost for the month, compare with the compute lines in the billing export for the same accounts, and record the delta. A stable delta of a few percent is a pricing-model difference you can document. A growing delta points to nodes the model cannot see, such as a pool created outside the monitored cluster, or spot price drift. For billing-side normalisation, the FOCUS-based pipeline gives you a common schema to compare against, and anomaly detection on billing data tells you when the delta moves for a reason you did not plan.
Spot, autoscaling and short-lived nodes
OpenCost marks spot nodes with kubecost_node_is_spot, and the custom pricing block distinguishes spot CPU and RAM rates. Spot makes node price vary with time, so a long aggregation window averages over price changes while a short one captures them. Autoscalers add a second wrinkle: when a node-provisioning tool consolidates workloads, idle falls and the same workloads become cheaper, even though no team changed anything. The Karpenter versus Cluster Autoscaler comparison covers how provisioning behaviour changes packing, which is exactly the variable that moves idle cost.
GPU cost and the fractional-sharing gap
GPUs are where allocation models are most likely to mislead. OpenCost exports node_gpu_hourly_cost, node_gpu_count and container_gpu_allocation, and the specification treats GPU pricing like CPU pricing. In practice that means a pod holding a whole GPU is charged for the whole GPU, which is right. When a GPU is shared through MIG partitions, MPS or time-slicing, whether a pod’s charge reflects its slice or the device depends on how the extended resource is advertised and what the allocation metric counts; verify with a test workload before trusting per-team GPU numbers. My expectation, not something the specification states, is that request-based counting cannot see actual GPU utilisation inside a shared device, so utilisation-weighted attribution needs separate telemetry. The GPU sharing comparison explains how each mode exposes capacity.
Idle GPU is also the most expensive idle on the cluster. Run the idle report with idleByNode=true and filter to GPU pools; a single reserved-but-unused accelerator node can outweigh all CPU idle in the same cluster.
Trade-offs, Gotchas, and What Goes Wrong
Every allocation method is a compromise, and the failures are rarely in the arithmetic. They are in the inputs and in the social contract around the output.
Prometheus is the single point of truth. In the classic deployment, OpenCost reads everything through Prometheus. Gaps from scrape failures, short retention, relabelling that drops kube-state-metrics series, or duplicate metrics from two kube-state-metrics versions all become cost errors. The Helm docs call out disabling the v1-style metrics emission to avoid duplicates; a double-counted request inflates a team’s bill and nobody sees an alert. Watch for missing series before you watch for expensive teams.
Short-lived pods fall between samples. The resolution parameter defaults to 1m, and the docs note larger values are faster but less accurate for short-lived workloads. A burst of five-second batch pods can be undercounted at coarse resolution. If your workload mix is dominated by short jobs, test fine resolution on a known batch and compare with job-level accounting.
Request-heavy cost punishes safe defaults. Because the max rule charges for requests, a team that sets requests high to stay safe pays for that safety. That is intended, but it means the first showback report triggers an argument about whether requests are a budget or a reliability tool. Settle that before publishing, and give teams a rightsizing path rather than just a number.
Proportional sharing hides causes. Spreading idle by consumption looks fair and is easy to compute. It also taxes efficient large tenants for fragmentation they did not create, and gives the platform team no incentive to fix it. If you must share, publish the unshared view beside it.
Unlabelled cost becomes everyone’s cost. If you redistribute __unallocated__ like shared platform cost, bad hygiene is subsidised by good hygiene. Keep it visible, name an owner, and treat its percentage as a team-level KPI for the platform group.
Zeroing out a negative residual. If workload plus overhead exceeds node cost in some interval, the temptation is to clamp idle to zero. Resist it. A negative idle value indicates double counting, a node missing from pricing, or misaligned windows, and clamping hides the evidence.
Network and load balancer numbers are soft. The specification offers no network asset formula, so egress attribution depends on provider-specific data and your configuration. Do not charge back network to the cent without comparing it to billing line items.
Chargeback changes behaviour, including gaming. Once money moves, teams move workloads to namespaces with lower rates, strip labels, or batch work into cheaper windows. Showback is forgiving; chargeback needs audit rules, a change-control process for pricing and an appeals path.
Practical Recommendations
Start with the narrowest model that answers a real question, and widen only when someone asks for the next level of detail. A defensible first release is namespace-level showback, idle shown separately, platform shown separately, and a tracked unallocated percentage. That is a report engineers can verify by hand against the identity in this post, and it avoids defending a redistribution formula on day one.
Then layer the policy in the order of disputes. Share idle only when finance needs totals to tie to the bill, and label that view “fully loaded”. Redistribute platform cost proportionally once the platform namespace set is stable and agreed. Treat anything more elaborate, such as custom-metric splits by egress bytes or request counts, as a later refinement that needs a named owner.
Close the loop with engineering. Pair the report with rightsizing so that a high request-versus-usage gap produces a ticket or an automated resize rather than a shaming chart. Feed the same data to anomaly detection so a sudden namespace jump pages someone within a day instead of surfacing at month-end.
A launch checklist:
- Verify node pricing:
sum(node_total_hourly_cost) * 730within a few percent of the billing extract. - Confirm Prometheus retention covers your longest reporting window and that kube-state-metrics series are not duplicated.
- Define required namespace labels (team, cost-centre, environment) and enforce them at admission with an allowlist of values.
- Report idle separately first; add
shareIdleas a distinct, labelled “fully loaded” view. - Name platform namespaces explicitly and agree the redistribution rule in writing.
- Track unallocated cost as a percentage and set a target.
- Reconcile monthly against the billing export and record the delta.
- Test GPU attribution with a known workload before publishing per-team GPU cost.
- Re-run the self-test or an equivalent assertion that every view sums to the same total.
Frequently Asked Questions
What is the difference between OpenCost and Kubecost?
OpenCost is the open-source, vendor-neutral cost allocation engine and specification, and it is a CNCF incubating project. Kubecost originally created it and, according to the OpenCost FAQ, uses it in all its versions while adding commercial features, including reconciliation of published bills with negotiated discounts. If you need list-price allocation and an API, OpenCost is enough. If you need discount-aware reconciliation, multi-cluster aggregation and support, evaluate the commercial tier or build that layer yourself.
How does OpenCost calculate idle cost?
Cluster idle cost is the cluster’s asset cost minus the cost allocated to workloads, where workload cost uses the larger of request and usage per container. Idle percentage is idle cost divided by resource allocation cost, excluding usage-only resources such as egress. In the API, idle is omitted by default, appears as an __idle__ allocation with includeIdle=true, and is spread across workloads with shareIdle=true. Adding idleByNode=true computes it per node.
Should I charge for requests or for usage?
OpenCost charges for the larger of the two per container, which discourages both over-requesting and under-requesting. Charging only requests rewards under-reserving and bursting; charging only usage lets teams over-reserve for free and bloats the cluster. If your culture treats requests as a reliability margin, expect pushback and pair the report with rightsizing tooling so teams can lower the number safely rather than argue with it.
Do I need Prometheus to run OpenCost?
In the standard deployment, yes: the installation docs say OpenCost requires Prometheus for scraping metrics and data storage, and the Helm chart assumes an existing Prometheus. The CNCF’s January 2026 retrospective describes a beta “promless” mode using a collector datasource, but I would treat it as beta until your version’s documentation says otherwise. Either way, metric gaps are the main source of silent error, so monitor ingestion health.
How should I allocate shared platform costs such as monitoring and ingress?
The OpenCost specification lists three methods: spread uniformly across tenants, allocate in proportion to each tenant’s cost, or use a custom metric such as network egress bytes. Proportional allocation is the most common starting point. Whatever you choose, keep the platform line visible alongside the redistributed view, get agreement from the platform and tenant owners in advance, and revisit it when the platform’s usage pattern changes.
Can OpenCost handle GPU cost?
It exports GPU hourly cost, GPU count and GPU allocation metrics, and the specification prices GPUs similarly to CPUs. Whole-GPU allocation is straightforward. Fractional sharing through MIG, MPS or time-slicing is less clear, because attribution depends on what the allocation metric counts and whether utilisation inside the device is visible. Validate with a controlled workload and consider supplementing with device-level telemetry before using GPU numbers for chargeback.
Further Reading
- OpenCost, VPA rightsizing, showback and chargeback: installation and rightsizing companion to this post.
- Cloud cost anomaly detection architecture for FinOps billing data: alerting on the numbers this post produces.
- Karpenter versus Cluster Autoscaler for GPU workloads: how provisioning changes packing and idle cost.
- Kubernetes in-place pod resize and vertical scaling: turning request cuts into lower allocated cost.
- FOCUS specification billing normalisation pipeline: the billing side of reconciliation.
- GPU sharing on Kubernetes: MIG, MPS, time-slicing and vGPU: background for the GPU attribution caveat.
- OpenCost specification: the authoritative allocation model.
- OpenCost Allocation API documentation: query parameters and semantics.
- OpenCost metrics reference: exported cost metrics and their labels.
By Riju — about
