GCP Setup
Install the Cost Operator (trezr) on GKE with Workload Identity, and connect the BigQuery billing export that gives each node its billed cost.
GCP Setup
On GKE, trezr runs as a Google service account through Workload Identity. No keys are stored in the cluster. It gets read-only roles and nothing else.
It reads two places:
- The cluster project. Compute Engine commitments (resource-based CUDs).
- The billing project. The Cloud Billing "Detailed usage cost" export in BigQuery. This gives each node its billed cost, and the utilization of spend-based CUDs.
GCP has no API for spend-based CUD utilization. The BigQuery billing export is the only source. Without it, DevZero knows which spend-based CUDs you bought but not how well they are used.
Prerequisites
- A GKE cluster on Kubernetes 1.31 or later
- The Read Operator installed
gcloud,bq,kubectl, andhelm, all pointed at this cluster- IAM admin rights on the cluster project and the billing project
- Billing Account Administrator, to turn on the export and grant the billing account role
Run every step in one shell. Later steps use the variables earlier steps set.
Setup
Set the cluster values
GKE names kube contexts gke_<project>_<location>_<cluster>. Set these by hand if you renamed the context.
export PROJECT_ID=$(kubectl config current-context | cut -d_ -f2)
export LOCATION=$(kubectl config current-context | cut -d_ -f3)
export CLUSTER=$(kubectl config current-context | cut -d_ -f4)Create the billing project and dataset
If your billing account already exports Detailed usage cost to BigQuery, set BILLING_PROJECT and DATASET to that project and dataset. Run only the first three lines, then skip the next step. You can see existing exports at console.cloud.google.com/billing/export.
export BILLING_ACCOUNT_ID=$(gcloud billing projects describe $PROJECT_ID \
--format='value(billingAccountName)' | sed 's|billingAccounts/||')
export BILLING_PROJECT="trezr-billing-project"
export DATASET="trezr_billing_dataset"
gcloud projects create $BILLING_PROJECT
gcloud billing projects link $BILLING_PROJECT --billing-account=$BILLING_ACCOUNT_ID
gcloud services enable bigquery.googleapis.com \
bigquerydatatransfer.googleapis.com --project=$BILLING_PROJECT
bq --location=US mk --dataset "${BILLING_PROJECT}:${DATASET}"Project IDs are unique across all of Google Cloud, so change the name if it is taken. Dataset names allow only letters, numbers, and underscores.
Turn on the Detailed usage cost export
Google has no CLI for this step.
- Open
https://console.cloud.google.com/billing/<BILLING_ACCOUNT_ID>/exportand go to the BigQuery export tab. - Under Detailed usage cost, click Edit settings.
- Choose the project and dataset from the last step, then Save.
The first rows arrive within a few hours. A dataset in the US multi-region is backfilled from the start of last month. Check that the table exists:
bq ls "${BILLING_PROJECT}:${DATASET}" | grep gcp_billing_export_resource_v1_Turn on Workload Identity
Workload Identity has two parts. The cluster needs a workload pool, and each node pool trezr can run on needs the GKE metadata server. Autopilot clusters always have both, so skip this step on Autopilot.
Check the cluster. If this prints <project>.svc.id.goog, the pool is set:
gcloud container clusters describe $CLUSTER --location=$LOCATION \
--project=$PROJECT_ID --format='value(workloadIdentityConfig.workloadPool)'If it printed nothing, set it:
gcloud container clusters update $CLUSTER --location=$LOCATION \
--project=$PROJECT_ID --workload-pool=$PROJECT_ID.svc.id.googThen check each node pool, even if the cluster already had a workload pool. A node pool created before Workload Identity was turned on keeps the old metadata server:
gcloud container node-pools list --cluster=$CLUSTER --location=$LOCATION \
--project=$PROJECT_ID --format='table(name,config.workloadMetadataConfig.mode)'Update every pool that does not show GKE_METADATA. This replaces the pool's nodes one at a time, so pick a quiet time.
gcloud container node-pools update <pool-name> --cluster=$CLUSTER \
--location=$LOCATION --project=$PROJECT_ID --workload-metadata=GKE_METADATACreate trezr's service account and grant read access
export GSA=devzero-trezr@$PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts create devzero-trezr --project=$PROJECT_ID \
--display-name="DevZero trezr cost collector"
# Resource-based CUDs in the cluster project
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$GSA" --role=roles/compute.viewer
# Commerce API (spend-based CUDs) and Cloud Billing API (CUD list prices)
gcloud services enable cloudcommerceconsumerprocurement.googleapis.com \
cloudbilling.googleapis.com --project=$PROJECT_ID
# Spend-based CUDs on the billing account
gcloud billing accounts add-iam-policy-binding $BILLING_ACCOUNT_ID \
--member="serviceAccount:$GSA" --role=roles/consumerprocurement.orderViewer
# Read the billing export and run queries on it
gcloud projects add-iam-policy-binding $BILLING_PROJECT \
--member="serviceAccount:$GSA" --role=roles/bigquery.dataViewer
gcloud projects add-iam-policy-binding $BILLING_PROJECT \
--member="serviceAccount:$GSA" --role=roles/bigquery.jobUser
# Let trezr's Kubernetes ServiceAccount act as this service account
gcloud iam service-accounts add-iam-policy-binding $GSA --project=$PROJECT_ID \
--role=roles/iam.workloadIdentityUser \
--member="serviceAccount:$PROJECT_ID.svc.id.goog[devzero-system/trezr]"| Role | Where | Used for |
|---|---|---|
roles/compute.viewer | Cluster project | Resource-based CUDs |
roles/consumerprocurement.orderViewer | Billing account | Spend-based CUDs, through the Commerce API |
roles/bigquery.dataViewer | Billing project | Reading the billing export |
roles/bigquery.jobUser | Billing project | Running queries on the export. It cannot change any data. |
roles/iam.workloadIdentityUser | trezr's service account | Lets the Kubernetes ServiceAccount use it |
The Cloud Billing API needs no role. It prices new resource-based CUDs from Google's price list before the billing export has billed them. If the billing project holds other data, grant bigquery.dataViewer on the export dataset instead of the whole project.
Install trezr
helm upgrade --install trezr oci://registry-1.docker.io/devzeroinc/trezr \
--version 0.0.13 \
--namespace devzero-system --create-namespace \
--set trezr.clusterName=$CLUSTER \
--set trezr.region=<your-region> \
--set trezr.cloudProvider=gcp \
--set trezr.gcp.project=$PROJECT_ID \
--set trezr.gcp.billingAccountId=$BILLING_ACCOUNT_ID \
--set trezr.gcp.bigqueryProject=$BILLING_PROJECT \
--set trezr.gcp.bigqueryDataset=$DATASET \
--set serviceAccount.annotations."iam\.gke\.io/gcp-service-account"=$GSAYou can also run dz install cost with the same values as --set flags. On GCP the CLI does not create cloud resources.
Check it
The first command should print the service account email. The logs should name the billing export table and count your commitments.
kubectl get sa trezr -n devzero-system \
-o jsonpath='{.metadata.annotations.iam\.gke\.io/gcp-service-account}'
kubectl logs -n devzero-system deploy/trezr \
| grep -E "using billing export table|collected commitments"Pick the export in DevZero
Open the cluster in DevZero, go to Cost & Resources → Commitments, and choose the export under Billing data (GCP BigQuery export). See Billing Exports.
The billing project, the export, and the billing account role are set up once per billing account. Each new cluster needs only the service account steps and the install.
Next Steps
- Billing Exports -- what trezr reads from BigQuery, and what it costs
- Configuration -- lookback months, org-wide CUDs, and other Helm values
- Troubleshooting -- no export table, Minimal access tier