GCP / Guide 3
Labels and governance
A label strategy finance can use, labels versus tags, organisation policy constraints, and labels in cost reports.
Labels answer the questions that come up every month: who owns this, what does it belong to, and who pays for it. In Google Cloud, labels flow straight into the billing export, so good labels mean cost reports by team and application with no extra work. Organisation policies and Cloud Asset Inventory make sure the rules are followed.
Labels and tags are different
Google Cloud has two similar-sounding features. Use both, for different jobs:
| Labels | Tags (Resource Manager tags) | |
|---|---|---|
| What they are | Key/value pairs on a resource | Key/value resources defined centrally, then bound to resources |
| Used for | Cost reports, filtering, automation | Access and policy: IAM Conditions, organisation policy conditions, firewall policies |
| Who can set them | Anyone who can edit the resource | Only people granted tagUser on the tag value |
| Inheritance | None | Inherited down the hierarchy |
| In billing export | Yes | Yes |
Because anyone who can edit a resource can change its labels, never base security decisions on labels. That's what tags are for. And don't confuse either with network tags on VMs, which are plain strings used to target firewall rules (Guide 4).
A label strategy that finance can use
The same five as the Azure and AWS notes, adapted to Google's rules:
| Label | Example | Why |
|---|---|---|
owner | jane_ravindran_in | Who to ask before changing or deleting it |
costcenter | cc-1042 | Who pays; used for showback and chargeback |
environment | prod, test, dev | Different rules, schedules and priorities |
application | payroll | Groups resources across projects and services |
dataclassification | confidential | Drives security controls |
- Lowercase only. Keys and values can contain lowercase letters, digits, underscores and dashes; keys must start with a letter. No
@or., which is why the owner is writtenjane_ravindran_in. - Limits: up to 64 labels per resource; keys and values up to 63 characters.
- Label the project too. Project labels appear in the billing export alongside resource labels, so a one-workload-per-project layout gets most costs allocated even when a resource label is missing.
- Not everything takes labels. VMs, disks, snapshots, buckets, Cloud SQL, BigQuery datasets, GKE clusters and Cloud Run services do; firewall rules and IAM don't.
- Don't put secrets or personal data in labels. Many people and tools can read them.
# Label a VM and a bucket
gcloud compute instances add-labels vm-notes-test-01 --zone=us-east1-b `
--labels=owner=jane_ravindran_in,costcenter=cc-1042,environment=test
gcloud storage buckets update gs://prj-notes-test-logs --update-labels=owner=jane_ravindran_in,costcenter=cc-1042
# Label the project
gcloud projects update prj-notes-test --update-labels=costcenter=cc-1042,environment=test
Find what is missing labels
Google Cloud has no built-in "require this label" policy for every service. Check instead, with Cloud Asset Inventory:
# Resources in the organisation with no costcenter label
gcloud asset search-all-resources --scope=organizations/123456789012 `
--asset-types=compute.googleapis.com/Instance,compute.googleapis.com/Disk,storage.googleapis.com/Bucket `
--query="NOT labels:costcenter" --format=json | ConvertFrom-Json |
Select-Object assetType, displayName, project, location
Get-GcpUnlabeledResources.ps1 does this for a list of required labels across the common billable resource types, and reports near misses such as cost_center or cost-center. To enforce labels at creation, use custom organisation policy constraints for the services that support them (for example Compute Engine instances and GKE clusters), or require labels in your Terraform modules.
Organisation policy constraints worth setting
Beyond the secure-by-default set (Guide 2):
| Constraint | Why |
|---|---|
gcp.resourceLocations | Keep resources in approved regions (data residency and support) |
compute.skipDefaultNetworkCreation | New projects don't get the default network with its permissive firewall rules |
compute.vmExternalIpAccess | VMs can't have public IPs, except those you list |
compute.requireOsLogin | SSH access controlled by IAM, not by keys in metadata |
compute.requireShieldedVm | Secure boot and integrity monitoring on every VM |
storage.publicAccessPrevention | No bucket can be made public |
sql.restrictPublicIp | Cloud SQL instances on private IPs only |
iam.allowedPolicyMemberDomains | Stop grants to personal Gmail accounts and other outside domains |
Test before you enforce. Organisation policies support dry run: the policy is evaluated and violations are logged, but nothing is blocked. Run it for a couple of weeks, check the logs, fix or exempt what breaks, then enforce. Set exceptions on specific projects or with tag conditions rather than turning a constraint off higher up.
# Enforce a boolean constraint on a folder
gcloud resource-manager org-policies enable-enforce compute.skipDefaultNetworkCreation --folder=111122223333
# Or set a list constraint from a YAML file (supports dryRunSpec for testing)
gcloud org-policies set-policy .\resource-locations.yaml
Labels in cost reports
- Billing reports in the console group and filter by label and project label straight away; nothing to activate, unlike AWS.
- Export billing data to BigQuery (standard and detailed usage cost) from day one. The export isn't retroactive, and it's where showback reports, dashboards and anomaly checks come from (Guide 10).
- Labels apply to cost from the moment they're added. Costs from before a label was set stay unlabelled, so label at creation.
-- Last month's cost by costcenter label (BigQuery billing export)
SELECT
(SELECT value FROM UNNEST(labels) WHERE key = 'costcenter') AS costcenter,
ROUND(SUM(cost) + SUM(IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c), 0)), 2) AS net_cost
FROM `prj-billing-export.billing.gcp_billing_export_v1_012345_6789AB_CDEF01`
WHERE invoice.month = FORMAT_DATE('%Y%m', DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH))
GROUP BY costcenter
ORDER BY net_cost DESC