GCP CUD Purchased but Billing Still Shows On-Demand Rates
Diagnosing a non-applying GCP CUD: verifying machine family, region, commitment type (compute vs memory-optimised), checking billing export, and fixing the issue.
This runbook covers diagnosing and fixing a situation where a purchased GCP CUD is not being applied to running instances. For a detailed discussion of CUD strategies, Spot VMs, and FinOps practices, see our article GCP cost optimization - Committed Use Discounts, Spot VMs, and FinOps strategies. If you need help selecting and optimising commitments - book a consultation.
Symptom
The billing report shows on-demand rates despite an active commitment. CUD credits do not appear in the billing export:
# Lista aktywnych commitmentów w projekcie
gcloud compute commitments list \
--project=my-project \
--format="table(name, region, status, plan, resources[].type, resources[].amount, startTimestamp, endTimestamp)"
# Sprawdź status konkretnego commitmentu
gcloud compute commitments describe my-cud-commitment \
--region=us-central1 \
--project=my-project \
--format="yaml(name, status, plan, resources, type, startTimestamp, endTimestamp, category)"
# Zweryfikuj running instances i ich machine type
gcloud compute instances list \
--project=my-project \
--format="table(name, zone, machineType.basename(), status)" \
--filter="status=RUNNING"
BigQuery billing export query to verify CUD credit presence:
-- Sprawdź czy CUD credits pojawiają się w billing export
SELECT
invoice.month,
sku.description,
service.description,
SUM(cost) AS total_cost,
SUM((SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT')) AS cud_credits,
SUM((SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT_DOLLAR_BASE')) AS cud_dollar_credits
FROM `my-billing-project.billing_export.gcp_billing_export_v1_XXXXXX`
WHERE
service.description = 'Compute Engine'
AND usage_start_time >= TIMESTAMP('2026-07-01')
AND project.id = 'my-project'
GROUP BY 1, 2, 3
HAVING total_cost > 0
ORDER BY total_cost DESC
LIMIT 20;
If the cud_credits and cud_dollar_credits columns return NULL or 0 for SKUs covered by the commitment - the CUD is not being applied.
Root Cause
GCP CUD is automatically applied to matching resources, but all criteria must be met simultaneously. The most common reasons for non-application:
-
Machine family mismatch - a CUD purchased for the N2 family does not cover E2, N1, or N2D instances. A
GENERAL_PURPOSEcommitment type covers only the specific machine family indicated at purchase. A common mistake: purchasing a CUD for N2 while running workloads on E2 (because they are cheaper per-vCPU on-demand). -
Region mismatch - a CUD is bound to a specific GCP region. A commitment purchased in
us-central1does not cover instances running ineurope-west1. There is no cross-region CUD mechanism - each region requires a separate commitment. -
Commitment type mismatch - a
COMPUTE_OPTIMIZEDCUD (C2/C2D family) does not cover general-purpose workloads (N2/E2). Similarly, aMEMORY_OPTIMIZEDCUD (M1/M2) covers only memory-optimised instances. -
CUD not yet active - after purchase, a commitment passes through a
CREATINGstate before becomingACTIVE. This can take up to 24 hours. Billing credits only begin accruing from the moment of activation. -
Sole-tenant nodes - instances running on sole-tenant nodes are not covered by standard CUDs. They require a separate sole-tenant commitment type.
-
Commitment exhausted - if the total vCPU/RAM of running instances exceeds the purchased commitment, the surplus is billed at on-demand rates. The CUD covers resources up to the purchased quantity limit.
Resolution
A) Verify commitment alignment with running instances:
# Szczegóły zakupionego CUD - machine type family i zasoby
gcloud compute commitments describe my-cud-commitment \
--region=us-central1 \
--project=my-project \
--format="json(name, status, resources, type, category)"
# Wynik powinien pokazać np.:
# "resources": [
# { "type": "VCPU", "amount": "32" },
# { "type": "MEMORY", "amount": "131072" } <- MB
# ]
# "type": "GENERAL_PURPOSE_N2"
# "category": "MACHINE"
# Porównaj z running instances - filtruj po machine family
gcloud compute instances list \
--project=my-project \
--filter="status=RUNNING AND machineType~'n2-'" \
--format="table(name, zone, machineType.basename())"
# Policz łączne vCPU instancji N2 w regionie us-central1
gcloud compute instances list \
--project=my-project \
--filter="status=RUNNING AND zone~'us-central1' AND machineType~'n2-'" \
--format="value(machineType.basename())" | \
awk -F'-' '{sum += $NF} END {print "Total vCPU (N2, us-central1):", sum}'
# Porównaj z commitmentem:
# Jeśli commitment = 32 vCPU, a running instances mają 48 vCPU
# -> 32 vCPU pokryte CUD, 16 vCPU rozliczane on-demand
B) Migrate instances to the matching machine family:
If the commitment is for N2 but instances are running on E2 - change the machine type:
# Zatrzymaj instancję przed zmianą machine type
gcloud compute instances stop my-instance \
--zone=us-central1-a \
--project=my-project
# Zmień machine type z E2 na N2 (zachowując podobną konfigurację)
# e2-standard-4 (4 vCPU, 16 GB) -> n2-standard-4 (4 vCPU, 16 GB)
gcloud compute instances set-machine-type my-instance \
--machine-type=n2-standard-4 \
--zone=us-central1-a \
--project=my-project
# Uruchom instancję ponownie
gcloud compute instances start my-instance \
--zone=us-central1-a \
--project=my-project
# Dla MIG (Managed Instance Group) - zaktualizuj instance template
gcloud compute instance-templates create my-template-n2-v2 \
--machine-type=n2-standard-4 \
--image-family=debian-12 \
--image-project=debian-cloud \
--project=my-project
gcloud compute instance-groups managed set-instance-template my-mig \
--template=my-template-n2-v2 \
--zone=us-central1-a \
--project=my-project
# Rolling update MIG do nowego template
gcloud compute instance-groups managed rolling-action start-update my-mig \
--version=template=my-template-n2-v2 \
--zone=us-central1-a \
--max-surge=1 \
--max-unavailable=0 \
--project=my-project
C) Purchase an additional CUD in the correct region/family:
# Sprawdź dostępne typy CUD dla regionu
gcloud compute commitments create-help
# Zakup CUD dla E2 w europe-west1 (jeśli tam działają instancje)
gcloud compute commitments create cud-e2-europe-west1 \
--region=europe-west1 \
--project=my-project \
--plan=twelve-month \
--resources=vcpu=16,memory=65536 \
--type=GENERAL_PURPOSE_E2
# Zakup CUD typu compute-optimised (C2) jeśli workload wymaga C2
gcloud compute commitments create cud-c2-us-central1 \
--region=us-central1 \
--project=my-project \
--plan=thirty-six-month \
--resources=vcpu=64,memory=262144 \
--type=COMPUTE_OPTIMIZED_C2D
Note: Before purchasing an additional CUD, make sure the existing commitment is insufficient. Check utilisation via the recommendations in Billing - Commitments.
D) Flex CUD for multi-region scenarios:
If workloads change regions (e.g. DR failover, seasonal migration) - consider an approach with multiple shorter commitments:
# GCP nie oferuje oficjalnie "Flex CUD" - ale można stosować strategię:
# 1. Mniejsze commitmenty per-region zamiast jednego dużego
# 2. Monitorowanie utilization i dokupywanie w miarę potrzeb
# Sprawdź rekomendacje CUD od Google (Billing -> Commitments -> Recommendations)
gcloud billing budgets list --billing-account=XXXXXX-XXXXXX-XXXXXX
# Alternatywnie - BigQuery query na billing export do analizy coverage
SELECT
location.region,
sku.description,
SUM(usage.amount_in_pricing_units) AS total_usage,
SUM(cost) AS total_cost,
SUM((SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT')) AS cud_applied
FROM `my-billing-project.billing_export.gcp_billing_export_v1_XXXXXX`
WHERE
service.description = 'Compute Engine'
AND usage_start_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND sku.description LIKE '%Instance Core%'
GROUP BY 1, 2
ORDER BY total_cost DESC;
Validation
Once changes are deployed, verify that CUD credits begin appearing:
-- BigQuery: potwierdź naliczanie CUD credits w bieżącym dniu
SELECT
usage_start_time,
sku.description,
cost AS gross_cost,
(SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT') AS cud_credit,
cost + IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT'), 0) AS net_cost
FROM `my-billing-project.billing_export.gcp_billing_export_v1_XXXXXX`
WHERE
service.description = 'Compute Engine'
AND project.id = 'my-project'
AND usage_start_time >= TIMESTAMP_TRUNC(CURRENT_TIMESTAMP(), DAY)
AND sku.description LIKE '%N2%Instance Core%'
ORDER BY usage_start_time DESC
LIMIT 20;
-- Expected: cud_credit < 0 (ujemna wartość = naliczony rabat)
# Potwierdź utilization commitment
gcloud compute commitments list \
--project=my-project \
--region=us-central1 \
--format="table(name, status, resources[].type, resources[].amount)"
# Sprawdź w Billing API commitment coverage
gcloud billing accounts describe XXXXXX-XXXXXX-XXXXXX \
--format="json"
# Zweryfikuj brak instancji poza pokryciem CUD (on-demand spillover)
gcloud compute instances list \
--project=my-project \
--filter="status=RUNNING AND zone~'us-central1'" \
--format="table(name, machineType.basename())" | \
grep -v "n2-" | grep -v "NAME"
# Expected: pusta lista (wszystkie instancje w regionie to N2 pokryte CUD)
Full financial validation - compare the current month’s cost against the previous one:
-- Porównanie kosztów month-over-month z widocznością CUD
SELECT
FORMAT_TIMESTAMP('%Y-%m', usage_start_time) AS month,
SUM(cost) AS gross_cost,
SUM((SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT')) AS cud_savings,
SUM(cost) + SUM(IFNULL((SELECT SUM(c.amount) FROM UNNEST(credits) c WHERE c.type = 'COMMITTED_USE_DISCOUNT'), 0)) AS net_cost
FROM `my-billing-project.billing_export.gcp_billing_export_v1_XXXXXX`
WHERE
service.description = 'Compute Engine'
AND project.id = 'my-project'
AND usage_start_time >= TIMESTAMP('2026-06-01')
GROUP BY 1
ORDER BY 1;
-- Expected: net_cost w bieżącym miesiącu niższy o ~30-57% vs on-demand baseline
CUD not reducing your GCP bill?
Book a free 30-minute call. We will analyse your commitments, identify coverage gaps, and select the optimal CUD strategy for your infrastructure.