Контроль затрат в 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: забытые ресурсы
Второй источник неожиданных счетов — то, что вы создали и не удалили. Причём удалить виртуальную машину недостаточно: у неё остаются «хвосты», которые продолжают тарифицироваться.
| Что забывают | Почему это тикает |
| Остановленная VM | CPU не тарифицируется, но загрузочный диск — да, круглосуточно |
| Статический внешний 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 и метки на ресурсах.