FinOps Governance in Practice: From Showback to Kubernetes Namespace Chargeback
FinOps & Coûts

FinOps Governance in Practice: From Showback to Kubernetes Namespace Chargeback

September 20, 20267 min readFinOpsKubernetesOpenCost

How to build a FinOps team with a real mandate: actionable KPIs, the showback-to-chargeback progression, and allocating Kubernetes costs per namespace with OpenCost.

Most organisations discover FinOps the same way: a budget alert fires, someone exports a CSV from the billing console, and a spreadsheet gets circulated for two weeks. Then nothing changes. The cost curve keeps climbing, because the problem was never a lack of data — it was the absence of an owner, a decision cadence, and an allocation model that engineers actually trust.

This article covers the operating model that makes cloud cost management stick: a FinOps team with a real mandate, a short list of KPIs that drive decisions, the showback-to-chargeback progression, and the hardest technical piece of the puzzle — allocating Kubernetes costs down to the namespace.

The FinOps team is an operating model, not a cost-cutting squad

A FinOps team (or guild, or CCoE cost workstream) exists to make cost a first-class engineering variable, on par with latency and availability. Its job is not to say no to spending. Its job is to make the cost of every architectural choice visible, at the moment the choice is being made, to the people making it.

The failure mode is centralisation. A team that produces reports and sends them to engineering teams becomes a cost police force that is ignored within a quarter. The team that works is small, cross-functional, and operates as a platform: it builds the allocation pipeline, the dashboards, the guardrails, and then pushes accountability outward to the teams that own the workloads.

RoleContributionTypical time commitment
FinOps lead / platform engineerOwns the allocation pipeline, tooling, data qualityFull-time or majority
Finance controllerBudgets, amortisation, chart of accounts mapping, forecast validationA few days per month
Cloud / platform architectCommitment strategy, architectural arbitration, right-sizing standardsA few days per month
Product / engineering managersOwn their scope's cost, act on recommendations1–2 hours per month each
Procurement / sponsorContracts, EDP/committed-use negotiation, escalationQuarterly

Three decisions define the mandate: who commits to reserved capacity and savings plans (never individual teams — always centralised), who arbitrates between cost and performance (the architect, with engineering), and who is accountable for a budget overrun (the team owning the scope, not the FinOps team).

KPIs that steer, not KPIs that decorate

Total monthly spend is a terrible KPI. It goes up when the business grows, which is the desired outcome. What you need is a small set of indicators that separate healthy growth from waste, and that someone can actually act on within a sprint.

KPIDefinitionWhat it tells you
Unit costCloud spend / business unit (per order, per active user, per ingested GB)Whether the platform is getting more efficient as it scales
Allocation rate% of spend attributable to an identified ownerData quality. Below 90% nothing else is credible
Commitment coverage & utilisation% of eligible usage covered by RI/SP/CUD, and % of commitments consumedWhether you are leaving discounts on the table or over-committed
Idle / waste ratioProvisioned but unused capacity (requests vs. actual usage, unattached volumes, orphan snapshots, idle load balancers)The immediate, no-architecture-change savings pool
Forecast accuracyAbsolute deviation between forecast and actuals, month over monthYour credibility with finance
Remediation lead timeMedian time between a recommendation being raised and applied or explicitly refusedWhether the loop is actually closed

Two rules make these KPIs survive. First, every indicator has a named owner and a threshold that triggers an action, not just a colour change on a dashboard. Second, "explicitly refused" is a valid outcome — a recommendation rejected for a documented reason (compliance, latency budget, upcoming migration) is a closed loop. What kills FinOps is the backlog of recommendations nobody ever looks at.

Showback before chargeback, always

Showback shows each team what they consume and what it costs, with no accounting impact. Chargeback actually re-invoices that cost against the team or business unit budget. They are not alternatives — they are sequential stages, and skipping the first one guarantees the second will fail.

ShowbackChargeback
Accounting impactNoneReal budget transfer
Data quality requiredGood enough (~90% allocated)Near-perfect and auditable
Typical behavioural effectAwareness, opportunistic optimisationStructural arbitration, genuine trade-offs
Main riskReports nobody readsEndless disputes over shared costs; teams gaming the model
PrerequisiteA tagging taxonomy and an allocation pipelineThree to six months of stable, contested-and-corrected showback

The practical test for readiness: run showback for a full quarter and count the disputes. When teams stop challenging their numbers, the model is trustworthy enough to carry money. Move to chargeback before that, and every finance conversation turns into a data argument.

The foundation: a tagging taxonomy you enforce at provisioning time

Allocation quality is decided at resource creation, not in the reporting layer. A tag added retroactively does not backfill historical billing data. The rule is simple: no mandatory tag, no resource.

Keep the mandatory set short — five keys maximum. Anything longer will be filled with garbage.

// Terraform: mandatory tags applied by default at provider level
provider "aws" {
  region = "eu-west-3"
  default_tags {
    tags = {
      cost-center = var.cost_center   // finance mapping key
      owner       = var.team          // accountable team
      application = var.app_name      // product / service
      environment = var.environment   // prod | staging | dev
      managed-by  = "terraform"
    }
  }
}

Enforce it with a policy engine rather than a code review: AWS Service Control Policies or Tag Policies, Azure Policy with a deny effect, GCP organisation policies, plus Conftest/OPA in the CI pipeline so the failure happens on the pull request, not in production.

Kubernetes: where allocation actually gets hard

A Kubernetes cluster is a single billing line item — a set of EC2 instances, a managed control plane, some volumes and load balancers — shared by dozens of workloads belonging to different teams. The cloud provider's cost console cannot see inside it. You need an allocation layer.

The reference approach, implemented by OpenCost (the CNCF project that Kubecost is built on), works like this: derive an hourly unit price for CPU, memory and GPU from the node's actual billing rate (on-demand, spot, or reserved), then attribute that price to each pod in proportion to what it consumes. The critical detail is the metric you use.

The standard formula charges each pod on the maximum of its request and its actual usage, over the time window:

pod_cpu_cost = Σ over time (
    max(cpu_request, cpu_usage) × node_cpu_hourly_rate × hours
)
pod_ram_cost = Σ over time (
    max(ram_request_bytes, ram_usage_bytes) × node_ram_hourly_rate × hours
)
namespace_cost = Σ pod_cost + PV cost + LB cost + network cost

Charging on requests rather than usage is a deliberate and important choice. Requests are what the scheduler reserves; that capacity is unavailable to anyone else whether you use it or not. Billing on usage alone would let a team request 32 cores, use two, and pay for two — with the platform absorbing the difference. Using max(request, usage) makes over-provisioning visible and directly incentivises right-sizing, which is exactly the behaviour you want.

Querying the allocation is straightforward once OpenCost is deployed:

kubectl port-forward -n opencost svc/opencost 9003:9003

curl -sG "http://localhost:9003/allocation/compute" \
  --data-urlencode "window=30d" \
  --data-urlencode "aggregate=namespace" \
  --data-urlencode "accumulate=true" \
  --data-urlencode "idle=true" \
  --data-urlencode "shareIdle=false" | jq '.data'

You can aggregate on any label, which is how you map namespaces back to the finance taxonomy — aggregate=label:cost-center works as soon as your namespaces carry the label. Enforce that with Kyverno so a namespace cannot exist unlabelled:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cost-labels
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-namespace-labels
      match:
        any:
          - resources:
              kinds: ["Namespace"]
      validate:
        message: "Labels cost-center and owner are mandatory on namespaces."
        pattern:
          metadata:
            labels:
              cost-center: "?*"
              owner: "?*"

Shared costs: the part that generates every argument

Between the sum of your namespace costs and the cluster's actual invoice there is always a gap: unallocated node capacity (idle), the managed control plane, the ingress controller, observability agents running as DaemonSets, cross-AZ traffic, the service mesh. This can easily represent a fifth to a third of a cluster's cost, and how you distribute it determines whether teams trust the model.

MethodMechanicsWhen to use it
Platform absorbs itShared costs stay on the platform team's budgetEarly showback; keeps the conversation simple
Proportional to direct costEach namespace is uplifted by the same percentageThe pragmatic default for chargeback
Even splitDivided equally across namespaces or teamsRarely fair — penalises small workloads
Dedicated node poolsTaints/tolerations isolate a team's nodes; idle is charged to that teamLarge, predictable tenants who want full control

Whatever you pick, publish the rule. An allocation model that teams cannot recompute themselves will be contested forever. And keep idle as a visible line item rather than silently smearing it — it is the platform team's own efficiency KPI, and hiding it removes the incentive to improve bin-packing, autoscaling and node sizing.

The cadence that keeps it alive

Tooling is maybe a third of the work. The rest is rhythm. A weekly 30-minute anomaly review (variance above a defined threshold, with a named owner for each line). A monthly showback with each engineering team, focused on unit cost and idle ratio rather than absolute spend. A quarterly commitment review before rate decisions, and a forecast reconciliation with finance.

One last piece of advice: start with one cluster and three teams, not the whole estate. Get the allocation right, get the numbers contested and corrected, publish the rules, and only then generalise. A FinOps model that half the organisation distrusts is worse than no model at all — because it burns the credibility you will need when the real architectural arbitrations arrive.

← Back to blog