GCP FinOps CUD cost optimization billing

GCP CUD kupiony ale billing nadal pokazuje stawki on-demand

Diagnoza niedziałającego GCP CUD: weryfikacja machine family, regionu, typu commitment (compute vs memory-optimized), sprawdzenie billing export i naprawa.

·
Kupiłeś 1-roczny lub 3-letni Committed Use Discount spodziewając się nawet 57% oszczędności na instancjach Compute Engine, ale rachunek za kolejny billing cycle nie zmienił się ani o cent. Instancje dalej rozliczane są po stawkach on-demand, a commitment widnieje jako aktywny w konsoli. Przy fleetach liczących dziesiątki lub setki vCPU oznacza to tysiące dolarów miesięcznie przepłacanych za zasoby, które powinny być pokryte rabatem.

Ten runbook opisuje diagnozę i naprawę sytuacji, w której zakupiony GCP CUD nie jest naliczany na uruchomionych instancjach. Szczegółowe omówienie strategii CUD, Spot VMs i praktyk FinOps znajdziesz w artykule Optymalizacja kosztów GCP - Committed Use Discounts, Spot VMs i strategie FinOps. Jeśli potrzebujesz pomocy z doborem i optymalizacją commitmentów - umów konsultacje.

Objaw

Billing report pokazuje stawki on-demand mimo aktywnego commitment. Kredyty CUD nie pojawiają się w eksporcie rozliczeniowym:

# 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"

Zapytanie do BigQuery billing export weryfikujące obecność kredytów CUD:

-- 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;

Jeśli kolumny cud_credits i cud_dollar_credits zwracają NULL lub 0 dla SKU objętych commitmentem - CUD nie jest aplikowany.

Przyczyna

GCP CUD jest automatycznie aplikowany do pasujących zasobów, ale muszą być spełnione wszystkie kryteria jednocześnie. Najczęstsze przyczyny braku naliczenia:

  • Niezgodność machine family - CUD zakupiony dla rodziny N2 nie pokrywa instancji E2, N1 ani N2D. Commitment typu GENERAL_PURPOSE pokrywa tylko konkretną rodzinę maszyn wskazaną przy zakupie. Częsty błąd: zakup CUD na N2 a uruchamianie workloadów na E2 (bo są tańsze per-vCPU on-demand).

  • Niezgodność regionu - CUD jest powiązany z konkretnym regionem GCP. Commitment zakupiony w us-central1 nie pokrywa instancji uruchomionych w europe-west1. Nie ma mechanizmu cross-region CUD - każdy region wymaga oddzielnego commitment.

  • Niezgodność typu commitment - CUD typu COMPUTE_OPTIMIZED (rodzina C2/C2D) nie pokrywa workloadów general-purpose (N2/E2). Analogicznie CUD MEMORY_OPTIMIZED (M1/M2) pokrywa wyłącznie instancje memory-optimized.

  • CUD jeszcze nieaktywny - po zakupie commitment przechodzi przez stan CREATING zanim stanie się ACTIVE. Może to trwać do 24 godzin. Billing credits zaczynają być naliczane dopiero od momentu aktywacji.

  • Sole-tenant nodes - instancje uruchomione na sole-tenant nodes nie są pokrywane standardowymi CUD. Wymagają oddzielnego commitment typu sole-tenant.

  • Commitment wyczerpany - jeśli suma vCPU/RAM uruchomionych instancji przekracza zakupiony commitment, nadwyżka jest rozliczana on-demand. CUD pokrywa zasoby do limitu zakupionej ilości.

Rozwiązanie

A) Weryfikacja zgodności commitment z uruchomionymi instancjami:

# 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) Migracja instancji do pasującej machine family:

Jeśli commitment jest na N2, a instancje działają na E2 - zmień 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) Zakup dodatkowego CUD w prawidłowym regionie/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-optimized (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

Uwaga: Przed zakupem dodatkowego CUD upewnij się, że istniejący commitment nie jest wystarczający. Sprawdź utilization za pomocą rekomendacji w Billing → Commitments.

D) Flex CUD dla scenariuszy multi-region:

Jeśli workloady zmieniają regiony (np. DR failover, seasonal migration) - rozważ podejście z wieloma krótszymi commitmentami:

# 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;

Walidacja

Po wdrożeniu zmian zweryfikuj, że CUD credits zaczynają się pojawiać:

-- 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)

Pełna walidacja finansowa - porównaj koszt bieżącego miesiąca z poprzednim:

-- 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
Niewykorzystany CUD to podwójny koszt: płacisz za commitment (zobowiązanie jest nieodwołalne przez 1 lub 3 lata), a jednocześnie za instancje on-demand, które powinny być nim pokryte. Przy 32 vCPU N2 w us-central1 różnica między on-demand a CUD 3-letnim to ~$450/miesiąc. Przez rok nienaprawionej konfiguracji to ponad $5,400 przepalonego budżetu. Regularnie monitoruj commitment utilization i natychmiast reaguj na spadek pokrycia poniżej 80%.

 

Jerzy Kopaczewski

CUD nie obniża rachunku za GCP?

Umów bezpłatną 30-minutową rozmowę. Przeanalizujemy Twoje commitmenty, zidentyfikujemy luki w pokryciu i dobierzemy optymalną strategię CUD dla Twojej infrastruktury.