Контроль затрат в GCP

Облако не выставляет предоплату — оно просто присылает счёт. Разберёмся, как сделать так, чтобы этот счёт вас не удивил.

Бюджет (Budget) в Google Cloud — это не предохранитель, а сигнализация: при превышении порога он отправляет письмо, но НЕ выключает ресурсы и не останавливает списания. Понимание этой разницы стоит недёшево: истории про «уснул с включённым кластером и проснулся с четырёхзначным счётом» рождаются именно здесь.

Все предыдущие разделы курса добавляли вам возможностей. Этот — добавляет самоконтроль. Учебный проект, забытый на выходные, вполне способен потратить больше, чем стоит месяц обычного VPS. Хорошая новость: почти весь риск концентрируется в трёх местах, и все три закрываются за полчаса.

Откуда вообще берётся счёт

Структура простая: Billing Account (платёжный аккаунт, к нему привязана карта) → к нему подключены проекты → внутри проектов работают ресурсы. Каждый ресурс тикает по своей единице тарификации: виртуальная машина — за секунды работы, Cloud Storage — за гигабайто-месяцы, BigQuery — за просканированные байты, сеть — за исходящие гигабайты. Никаких «тарифных планов» и абонентской платы: сколько натикало — столько и списали в конце месяца.

Отсюда следует главный принцип: деньги тратят не сервисы, а забытые ресурсы. Разберёмся, как их видеть.

Бюджеты и алерты: минимальная гигиена

Первое, что нужно сделать в новом проекте — до всякой разработки:

gcloud billing accounts list

gcloud billing budgets create \
  --billing-account=0X0X0X-0X0X0X-0X0X0X \
  --display-name="Учебный проект" \
  --budget-amount=20USD \
  --threshold-rule=percent=0.5 \
  --threshold-rule=percent=0.9 \
  --threshold-rule=percent=1.0 \
  --filter-projects="projects/my-project"

Три порога — 50%, 90%, 100% — дают три письма разной степени срочности. Дополнительно стоит включить алерт по прогнозируемым расходам (forecasted spend): он срабатывает не когда деньги уже потрачены, а когда текущая скорость трат ведёт к превышению бюджета к концу месяца. Это единственный алерт, который приходит вовремя.

Хотите настоящий предохранитель, а не сигнализацию? Классический паттерн: бюджет публикует событие в тему Pub/Sub, на неё подписана Cloud Function, которая при достижении 100% отвязывает платёжный аккаунт от проекта — и всё в проекте останавливается.

import base64
import json
import os
from googleapiclient import discovery

PROJECT_ID = os.environ["GCP_PROJECT"]
billing = discovery.build("cloudbilling", "v1")

def stop_billing(event, context):
    data = json.loads(base64.b64decode(event["data"]).decode("utf-8"))
    cost = data["costAmount"]
    budget = data["budgetAmount"]

    if cost <= budget:
        print(f"Ещё в пределах бюджета: {cost} из {budget}")
        return

    name = f"projects/{PROJECT_ID}"
    # пустой billingAccountName = отвязать платёжный аккаунт
    billing.projects().updateBillingInfo(
        name=name, body={"billingAccountName": ""}
    ).execute()
    print(f"Биллинг отключён: потрачено {cost} при бюджете {budget}")

Предупреждение: это ядерная кнопка. Отвязка биллинга останавливает в проекте всё — включая базы, которые вы, возможно, потом не восстановите. Для учебного проекта — отличная страховка. Для продакшена — так делать нельзя.

Ловушка №1: full scan в BigQuery

BigQuery в режиме on-demand берёт около $6,25 за терабайт просканированных данных. Ключевое слово — «просканированных», а не «возвращённых». Запрос SELECT * FROM events LIMIT 10 вернёт десять строк, но прочитает всю таблицу — и вы заплатите за всю таблицу. Столбцовый формат означает, что деньги считаются по колонкам: читаете две колонки из сорока — платите за две.

Посчитаем реальный сценарий: дашборд обновляется каждые полчаса поверх таблицы на 800 ГиБ.

PRICE_PER_TIB = 6.25          # $ за 1 TiB просканированных данных (on-demand)
TIB = 1024 ** 4

def cost(bytes_scanned, runs):
    return bytes_scanned / TIB * PRICE_PER_TIB * runs

full_scan = 800 * 1024 ** 3   # SELECT * — читаем всю таблицу, 800 GiB
two_cols  = 20 * 1024 ** 3    # только две нужные колонки, 20 GiB
runs = 30 * 30                # 30 обновлений в день, 30 дней

a = cost(full_scan, runs)
b = cost(two_cols, runs)

print(f"SELECT *    : ${a:,.2f} в месяц")
print(f"две колонки : ${b:,.2f} в месяц")
print(f"разница     : ${a - b:,.2f} (дороже в {a / b:.0f} раз)")

Результат:

SELECT *    : $4,394.53 в месяц
две колонки : $109.86 в месяц
разница     : $4,284.67 (дороже в 40 раз)

Четыре тысячи долларов за одну звёздочку. Как защищаться:

  • Сухой прогон. Перед тяжёлым запросом всегда спрашивайте, сколько он прочитает — это бесплатно.
  • Жёсткий потолок. Флаг maximum_bytes_billed заставит запрос упасть с ошибкой вместо того, чтобы прочитать лишнее.
  • Партиционирование. Таблица, разбитая по дате, при фильтре по дате читает только нужные партиции. А опция require_partition_filter вообще запрещает запросы без фильтра по партиции.
  • Квота на пользователя. В разделе IAM & Admin → Quotas есть лимит «Query usage per day» — жёсткий, в отличие от бюджета.
bq query --dry_run --use_legacy_sql=false \
  'SELECT user_id, event_type FROM `my-project.analytics.events`'

bq query --use_legacy_sql=false --maximum_bytes_billed=10000000000 \
  'SELECT user_id, event_type FROM `my-project.analytics.events`'

Результат:

Query successfully validated. Assuming the tables are not modified,
running this query will process 21474836480 bytes of data.

Приятная деталь: первый терабайт запросов в месяц бесплатен. Учебные эксперименты на таблицах-игрушках вам не будут стоить ничего.

Ловушка №2: забытые ресурсы

Второй источник неожиданных счетов — то, что вы создали и не удалили. Причём удалить виртуальную машину недостаточно: у неё остаются «хвосты», которые продолжают тарифицироваться.

Что забываютПочему это тикает
Остановленная VMCPU не тарифицируется, но загрузочный диск — да, круглосуточно
Статический внешний IP без VMНеиспользуемый зарезервированный IP стоит дороже, чем привязанный — так GCP борется с дефицитом адресов
Persistent Disk и снапшотыЖивут отдельно от VM и переживают её удаление
Балансировщик (forwarding rule)Тарифицируется почасово, даже если трафика нет
Кластер GKE StandardУправляющий слой — около $0,10 в час, это ~$73 в месяц ещё до единой ноды

Раз в неделю прогоняйте инвентаризацию:

gcloud compute instances list
gcloud compute disks list --filter="-users:*"          # диски, не привязанные ни к одной VM
gcloud compute addresses list --filter="status=RESERVED"  # висящие внешние IP
gcloud container clusters list
gcloud run services list

Радикальный и очень действенный приём для учебных экспериментов: держать каждый эксперимент в отдельном проекте и по окончании удалять проект целиком (gcloud projects delete my-experiment). Проект — это граница жизненного цикла: удалили — гарантированно ничего не осталось.

Ловушка №3: egress-трафик

Правило простое: входящий трафик бесплатен, исходящий — нет. Данные, которые ваш сервис отдаёт в интернет, стоят примерно $0,08–0,12 за гигабайт в зависимости от направления. Трафик между регионами дешевле (около 1–2 центов за гигабайт), внутри одной зоны — бесплатен.

Что это значит на практике. Раздаёте картинки из Cloud Storage напрямую в браузеры, и сайт отдал терабайт за месяц — это около $85 сверх копеечной платы за само хранение. Хранение того же терабайта стоит примерно $20. То есть трафик обошёлся дороже, чем данные. Лечится это Cloud CDN: кэш отдаёт контент с краевых узлов и дешевле, и быстрее.

Вторая типичная история — сервис в europe-west1 ходит в базу в us-central1, потому что «так исторически сложилось». Каждый запрос — межрегиональный egress плюс сотня миллисекунд задержки. Держите приложение и его базу в одном регионе: это одновременно быстрее и дешевле.

Как это работает: считать заранее и видеть постфактум

Калькулятор

Перед тем как что-то поднимать, посчитайте: cloud.google.com/products/calculator. Он поддерживает почти все сервисы, учитывает регион, тип диска, обязательства (committed use discounts) и вытесняемые машины (Spot VM — до 60–91% дешевле обычных, но GCP вправе забрать их в любой момент). Три минуты в калькуляторе экономят недели удивления.

Billing export в BigQuery

Отчёты в консоли хороши, но настоящий инструмент — выгрузка детализации счёта в BigQuery (включается в настройках Billing одной галкой). После этого свои расходы можно просто запрашивать:

SELECT
  service.description AS service,
  ROUND(SUM(cost), 2)  AS cost_usd
FROM `my-project.billing.gcp_billing_export_v1_0X0X0X`
WHERE DATE(usage_start_time) BETWEEN '2026-06-01' AND '2026-06-30'
GROUP BY service
HAVING cost_usd > 0
ORDER BY cost_usd DESC
LIMIT 10;

Чтобы такие отчёты были осмысленными, вешайте на ресурсы метки (labels): --labels=env=dev,team=backend,project=demo. Тогда вопрос «сколько нам стоит окружение dev» становится обычной группировкой по метке, а не археологией.

Бесплатные лимиты

У GCP два разных «бесплатно», и их часто путают:

  • Стартовый кредит — $300 на 90 дней для новых аккаунтов. Он заканчивается, и по умолчанию аккаунт не переходит на платный режим автоматически, пока вы сами не подтвердите переход.
  • Always Free — постоянные бесплатные лимиты, которые действуют всегда: одна машина e2-micro в регионах us-west1/us-central1/us-east1, 5 ГБ Cloud Storage там же, 1 ТиБ запросов BigQuery в месяц, около 2 млн запросов Cloud Run, 2 млн вызовов Cloud Functions.

Учебный проект, аккуратно собранный из Always Free-ресурсов, стоит ноль долларов бессрочно. Но обратите внимание на регионы: та же e2-micro, поднятая в europe-west1, уже платная.

Частые ошибки

  • Считать, что бюджет останавливает траты. Не останавливает. Он шлёт письмо. Останавливает только квота, удаление ресурса или отвязка биллинга.
  • Ставить бюджет на платёжный аккаунт целиком, а не на проект. Тогда вы узнаете о проблеме, только когда сумма по всем проектам перевалит порог, и не поймёте, кто именно её съел.
  • Алерт только на 100%. К моменту письма деньги уже потрачены. Ставьте 50% и алерт по прогнозу.
  • SELECT * в BigQuery «просто посмотреть». Самый дорогой способ посмотреть данные. Для этого есть бесплатный предпросмотр таблицы в консоли и --dry_run.
  • Удалить VM и считать, что всё убрано. Диски, снапшоты, зарезервированные IP и правила балансировщика остаются и продолжают тарифицироваться.
  • Не смотреть на регион. Ресурсы Always Free бесплатны только в конкретных регионах; трафик между регионами платный; разные регионы имеют разные цены на одни и те же машины.
  • Оставлять сервис-аккаунт с широкими правами и ключом в репозитории. Утёкший ключ — это не только дыра в безопасности, но и потенциальный счёт: чужие майнеры очень любят чужие квоты на GPU.

Итоги

  • Бюджет — сигнализация, а не предохранитель: он уведомляет, но не выключает. Жёсткие ограничители — это квоты и отвязка биллинга.
  • Первым делом в новом проекте: бюджет на проект, пороги 50/90/100% и алерт по прогнозу.
  • BigQuery берёт деньги за просканированные байты: --dry_run, maximum_bytes_billed, партиционирование и никаких SELECT *.
  • Забытые диски, IP-адреса и кластеры тикают молча. Учебные эксперименты держите в отдельном проекте и удаляйте проект целиком.
  • Входящий трафик бесплатен, исходящий — нет. Держите приложение и данные в одном регионе, а для раздачи контента используйте CDN.
  • Считайте в калькуляторе заранее, а фактические расходы разбирайте через billing export в BigQuery и метки на ресурсах.
Проверьте себя
1. Вы настроили бюджет $50 с алертом на 100%. Расходы превысили $50. Что произойдёт?
AGoogle Cloud автоматически остановит все ресурсы проекта
BПридёт уведомление, но ресурсы продолжат работать и тратить деньги
CСписания приостановятся до конца месяца, ресурсы перейдут в режим только чтения
DПроект будет удалён вместе с данными
2. Какое действие в BigQuery с наибольшей вероятностью приведёт к крупному счёту?
ASELECT двух колонок из партиционированной таблицы с фильтром по дате
BЗапрос с --dry_run для оценки объёма данных
CSELECT * с LIMIT 10 по огромной непартиционированной таблице
DЗагрузка CSV-файла в новую таблицу