GCP Cloud Billing Best Practices: Labels, Budgets, and Cost Allocation
Cloud billing surprises are almost always a labeling and visibility problem. This guide walks through Google Cloud's full billing stack — labels, budgets, billing export to BigQuery, and chargeback models — so you can see exactly where every dollar goes and alert before a runaway job drains the budget.
When a $12,000 Cloud Run invoice lands in your inbox at the end of the month, the most common reaction is shock followed by a frantic search through the console to figure out which team, project, or rogue script caused it. The real problem isn't the bill — it's that you had no visibility until it was already too late to do anything about it.
Google Cloud's billing system is actually quite powerful, but most organizations use maybe 20% of it. They set up a billing account, attach projects, and check the console once a month when the invoice arrives. This guide covers the full stack: labels for attribution, budgets for real-time alerting, BigQuery export for deep analysis, and chargeback models for holding teams accountable.
Whether you're a startup trying to keep cloud costs under control or an enterprise building a FinOps practice across dozens of GCP projects, these patterns apply at every scale.
Understanding GCP's Billing Hierarchy
Before you can allocate costs properly, you need to understand how Google Cloud organizes billing.
The Billing Account → Organization → Project Chain
Every GCP resource lives in a project. Projects are grouped under a folder hierarchy within an organization. Every project is linked to exactly one billing account that pays for it.
Billing Account (pays the invoice)
└── Organization (your company domain)
├── Folder: Engineering
│ ├── Project: prod-frontend
│ ├── Project: prod-backend
│ └── Project: staging
├── Folder: Data
│ ├── Project: data-pipeline
│ └── Project: analytics
└── Folder: Infrastructure
└── Project: shared-vpc
This hierarchy matters for cost allocation. You can view costs by project natively, but for sub-project attribution (which team within engineering? which feature?) you need labels.
Sub-Accounts for Enterprise Billing Separation
If you're a large enterprise or managed service provider, you can create sub-billing accounts that let you separate invoicing while keeping centralized visibility. This is common when different business units have separate cost centers or PO numbers.
# Create a sub-billing account (requires Billing Account Admin role)
gcloud billing accounts create --display-name="Engineering Sub-Account" --master-billing-account=012345-ABCDEF-789012
Labels: The Foundation of Cost Attribution
Labels are key-value pairs attached to GCP resources. They flow through to the billing export and let you slice costs by any dimension that matters to your organization.
Designing a Label Taxonomy
Before you start labeling anything, design a consistent taxonomy. Ad-hoc labeling creates a mess where one team uses env: prod and another uses environment: production. You need a standard enforced across all projects.
A solid baseline taxonomy for most organizations:
| Label Key | Example Values | Purpose |
|---|---|---|
env |
prod, staging, dev | Environment separation |
team |
platform, data, frontend, security | Cost attribution by team |
cost-center |
cc-1001, cc-1002 | Finance chargeback |
application |
checkout, analytics, auth | Per-app attribution |
managed-by |
terraform, gke, manual | Operations context |
Applying Labels with Terraform
The most reliable way to enforce labels is through IaC. If every resource is defined in Terraform, you can add labels at the module level and inherit them automatically.
# Define labels in a local variable at the module root
locals {
common_labels = {
env = var.environment
team = var.team
cost-center = var.cost_center
application = var.application_name
managed-by = "terraform"
}
}
# Apply to Compute Engine instance
resource "google_compute_instance" "api_server" {
name = "api-server-${var.environment}"
machine_type = "n2-standard-4"
zone = "europe-west4-a"
labels = local.common_labels
# ... boot disk, network interface, etc.
}
# Apply to Cloud SQL instance
resource "google_sql_database_instance" "postgres" {
name = "postgres-${var.environment}"
database_version = "POSTGRES_15"
region = "europe-west4"
settings {
tier = "db-n1-standard-4"
user_labels = local.common_labels
}
}
# Apply to GKE cluster
resource "google_container_cluster" "main" {
name = "main-${var.environment}"
location = "europe-west4"
resource_labels = local.common_labels
}
What Labels Don't Cover
Labels have important limitations you should understand upfront:
- Network egress: Inter-region and internet egress costs are not labeled — they appear as unlabeled line items
- Sustained use discounts: Applied automatically, not attributable to labels
- Free tier credits: Not labeled
- Labels on GKE node VMs: Labels on the cluster don't automatically flow to node pool VMs — you must label node pools separately
For GKE cost attribution at the pod/namespace level (not just cluster level), use GKE cost allocation with the built-in namespace and label attribution:
# Enable GKE usage metering and cost attribution
gcloud container clusters update my-cluster --region=europe-west4 --enable-resource-consumption-metering --resource-usage-bigquery-dataset=my_billing_dataset
Budget Alerts: Real-Time Cost Visibility
Labels give you retrospective attribution. Budgets give you real-time alerting before you overspend. You should have budgets at multiple levels: per-project, per-service, and at the billing account level.
Creating Budgets via gcloud
# Create a project-level budget with email alerts
gcloud billing budgets create --billing-account=012345-ABCDEF-789012 --display-name="prod-backend monthly budget" --budget-amount=5000USD --filter-projects=projects/prod-backend-project --threshold-rule=percent=0.5,basis=current-spend --threshold-rule=percent=0.9,basis=current-spend --threshold-rule=percent=1.0,basis=current-spend --all-updates-rule-monitoring-notification-channels=projects/prod-backend-project/notificationChannels/1234567890
Programmatic Budget Notifications with Pub/Sub
Email alerts are useful, but for automated responses (e.g., disabling a service when a budget is exceeded), use Pub/Sub notifications:
# Create a Pub/Sub topic for budget alerts
gcloud pubsub topics create billing-alerts
# Create a budget with Pub/Sub notification
gcloud billing budgets create --billing-account=012345-ABCDEF-789012 --display-name="Data pipeline budget" --budget-amount=2000USD --filter-projects=projects/data-pipeline-project --threshold-rule=percent=0.8,basis=current-spend --threshold-rule=percent=1.0,basis=current-spend --all-updates-rule-pubsub-topic=projects/my-project/topics/billing-alerts
Then subscribe a Cloud Function to that topic to take automated action:
import base64
import json
import functions_framework
@functions_framework.cloud_event
def handle_budget_alert(cloud_event):
data = json.loads(base64.b64decode(cloud_event.data["message"]["data"]).decode("utf-8"))
budget_amount = data["budgetAmount"]["specifiedAmount"]["units"]
cost_amount = data["costAmount"]["units"]
cost_interval_start = data["costIntervalStart"]
alert_threshold = data["alertThresholdExceeded"]
print(f"Budget alert: {cost_amount}/{budget_amount} USD ({alert_threshold*100:.0f}% threshold)")
# If over 100%, take automated action
if float(cost_amount) >= float(budget_amount):
# Example: disable Cloud Run service
# subprocess.run(["gcloud", "run", "services", "update", "expensive-service", "--no-traffic"])
# Or send Slack alert
send_slack_alert(f"BUDGET EXCEEDED: Data pipeline project hit ${cost_amount} of ${budget_amount} budget")
Budget Filters for Service-Level Visibility
You can create budgets filtered to specific services or labels:
# Budget for BigQuery only
gcloud billing budgets create --billing-account=012345-ABCDEF-789012 --display-name="BigQuery analysis budget" --budget-amount=1000USD --filter-services=services/95FF-2EF5-5EA1 --threshold-rule=percent=0.7,basis=current-spend
To find service IDs:
gcloud services list --available --filter="name:bigquery" --format="value(name)"
BigQuery Billing Export: Deep Cost Analysis
Budget alerts tell you when you've hit a threshold, but they don't tell you WHY. For root cause analysis and historical trending, you need the Cloud Billing export to BigQuery.
Setting Up Billing Export
# Create a BigQuery dataset for billing data
bq mk --location=EU --dataset --description="Cloud billing export" billing_export
# Enable export in console: Billing → Billing Export → BigQuery Export
# Project ID: your-project
# Dataset: billing_export
# Table prefix: gcp_billing_export_v1
The export creates two tables:
gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX— Standard usage data (updated daily)gcp_billing_export_v1_resource_XXXXXX— Resource-level data (more granular, larger)
Essential Billing Queries
Cost by team label this month:
SELECT
labels.value AS team,
ROUND(SUM(cost), 2) AS total_cost_usd,
ROUND(SUM(IFNULL((SELECT SUM(amount) FROM UNNEST(credits) WHERE type != 'SUSTAINED_USAGE_DISCOUNT'), 0)), 2) AS credits_usd
FROM
`my-project.billing_export.gcp_billing_export_v1_*`
CROSS JOIN UNNEST(labels) AS labels
WHERE
labels.key = 'team'
AND DATE(_PARTITIONTIME) >= DATE_TRUNC(CURRENT_DATE(), MONTH)
GROUP BY team
ORDER BY total_cost_usd DESC;
Daily spend trend by service:
SELECT
DATE(usage_start_time) AS usage_date,
service.description AS service,
ROUND(SUM(cost), 2) AS cost_usd
FROM
`my-project.billing_export.gcp_billing_export_v1_*`
WHERE
DATE(_PARTITIONTIME) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
GROUP BY usage_date, service
ORDER BY usage_date DESC, cost_usd DESC;
Top 20 most expensive SKUs this month:
SELECT
service.description,
sku.description,
ROUND(SUM(cost), 2) AS total_cost
FROM
`my-project.billing_export.gcp_billing_export_v1_*`
WHERE
DATE(_PARTITIONTIME) >= DATE_TRUNC(CURRENT_DATE(), MONTH)
GROUP BY 1, 2
ORDER BY total_cost DESC
LIMIT 20;
Anomaly detection — days where spend exceeds 2x the 7-day average:
WITH daily_costs AS (
SELECT
DATE(usage_start_time) AS usage_date,
SUM(cost) AS daily_cost
FROM `my-project.billing_export.gcp_billing_export_v1_*`
WHERE DATE(_PARTITIONTIME) >= DATE_SUB(CURRENT_DATE(), INTERVAL 60 DAY)
GROUP BY usage_date
),
rolling_avg AS (
SELECT
usage_date,
daily_cost,
AVG(daily_cost) OVER (
ORDER BY usage_date
ROWS BETWEEN 7 PRECEDING AND 1 PRECEDING
) AS seven_day_avg
FROM daily_costs
)
SELECT
usage_date,
ROUND(daily_cost, 2) AS cost,
ROUND(seven_day_avg, 2) AS avg_7d,
ROUND(daily_cost / seven_day_avg, 2) AS ratio
FROM rolling_avg
WHERE daily_cost > 2 * seven_day_avg
ORDER BY ratio DESC;
Chargeback and Showback Models
Once you have attribution data, you need to decide what to do with it. Two models dominate enterprise cloud billing:
Showback: Teams can see their costs, but no money actually transfers between departments. This is easier to implement and great for driving cost awareness without financial friction.
Chargeback: Teams actually pay for their cloud spend, either as a budget deduction or an internal invoice. This creates strong incentives but requires organizational buy-in and a clear pricing model for shared services.
Handling Shared Costs
Shared costs (shared VPC, network egress, Security Command Center, Cloud Armor) are the hard part. Common approaches:
- Even split: Divide shared costs equally across all consuming teams. Simple, but unfair if teams have very different usage.
- Proportional: Allocate based on each team's percentage of total attributable spend. Fair, but complex.
- Designated payer: Assign shared infrastructure costs to the platform/infra team. Clean separation, but the platform team carries an inflated bill.
For most organizations, proportional allocation works well:
-- Calculate each team's share of total labeled spend
-- Then apply that percentage to unlabeled/shared costs
WITH labeled_by_team AS (
SELECT
labels.value AS team,
SUM(cost) AS team_cost
FROM `my-project.billing_export.gcp_billing_export_v1_*`
CROSS JOIN UNNEST(labels) AS labels
WHERE labels.key = 'team'
AND DATE(_PARTITIONTIME) >= DATE_TRUNC(CURRENT_DATE(), MONTH)
GROUP BY team
),
total_labeled AS (
SELECT SUM(team_cost) AS total FROM labeled_by_team
),
unlabeled_cost AS (
SELECT SUM(cost) AS unlabeled
FROM `my-project.billing_export.gcp_billing_export_v1_*`
WHERE NOT EXISTS (
SELECT 1 FROM UNNEST(labels) AS l WHERE l.key = 'team'
)
AND DATE(_PARTITIONTIME) >= DATE_TRUNC(CURRENT_DATE(), MONTH)
)
SELECT
team,
ROUND(team_cost, 2) AS direct_cost,
ROUND((team_cost / total) * (SELECT unlabeled FROM unlabeled_cost), 2) AS allocated_shared_cost,
ROUND(team_cost + (team_cost / total) * (SELECT unlabeled FROM unlabeled_cost), 2) AS total_allocated
FROM labeled_by_team, total_labeled
ORDER BY total_allocated DESC;
Looker Studio Dashboard for Cost Visibility
Export your billing queries to a Looker Studio dashboard that refreshes daily. Connect BigQuery as the data source and create:
- A scorecard showing current month spend vs. last month
- A time series chart of daily spend by service
- A table of cost by team with month-over-month change
- A bar chart of top 10 most expensive SKUs
The billing BigQuery dataset is the source for all charts. Set the date range control to filter the dashboard dynamically. Share the dashboard with team leads so they have self-service cost visibility without needing BigQuery access.
For committed use discount strategy to reduce your baseline bill, see our Google Cloud FinOps guide. For rightsizing specific resource types, see our GCP rightsizing guide.
Recommendations Hub and Active Assist
Google Cloud's Recommendations Hub surfaces automated cost recommendations across all your projects. Check it weekly — it catches things like idle VMs, oversized Cloud SQL instances, and idle Persistent Disks that are easy to miss manually.
# List cost recommendations for a project
gcloud recommender recommendations list --project=my-project --location=global --recommender=google.billing.ProjectBillingRecommender
# List VM rightsizing recommendations
gcloud recommender recommendations list --project=my-project --location=us-central1 --recommender=google.compute.instance.MachineTypeRecommender
Putting It All Together: The Monthly FinOps Ritual
A working billing practice isn't a one-time setup — it's a monthly ritual:
- Week 1: Review the previous month's Looker Studio dashboard. Identify any teams or services that exceeded budget. Send showback reports to team leads.
- Week 2: Review Recommendations Hub. Triage rightsizing suggestions. Assign action items.
- Week 3: Review committed use discount utilization. Identify opportunities to expand or reduce commitments.
- Week 4: Update budgets for the coming month based on planned work. Verify all new resources have proper labels.
The most important metric to track isn't total cloud spend — it's unit cost (cost per customer, cost per transaction, cost per GB processed). Total spend going up is fine if your business is growing proportionally. Unit cost going up is a problem.
For the complete picture on GCP cost optimization including rightsizing strategies, see our GCP rightsizing guide. For real-time visibility into your infrastructure health, see our GCP observability guide.