Квоты GCP: автомониторинг для 300 проектов

Инциденты

В мае 2026 года квота на динамические маршруты VPC была тихо исчерпана. GCP начал отбрасывать маршруты, изученные по протоколу BGP (Border Gateway Protocol), не выдав ни единого предупреждения. Трафик до адресов в корпоративной сети вместо нужного пути уходил через интернет-шлюз по умолчанию и бесследно «тонул» (dropping без уведомления).

Несколько продакшн-сервисов упали, прежде чем удалось отследить причину — поле routeStatus: DROPPED в выводе Cloud Router.

Устранить проблему помогло увеличение квоты и повторная синхронизация BGP, однако поиск первопричины занял очень много времени, потому что ничто не указывало на то, что квота вообще была достигнута.

В отдельном инциденте речь шла о хранилище Persistent Disk в GKE: в ходе обновления версии GKE использование выросло с 900 ГБ до предела в 1000 ГБ, и никто этого не заметил — до тех пор, пока рабочие нагрузки с запросами на выделение томов не начали падать с ошибками.

Оба инцидента имели одну корневую причину: полное отсутствие видимости квот GCP в проектах организации.

Почему встроенные решения не справляются

В GCP есть встроенный мониторинг квот в составе GCP Cloud Monitoring. Лучшая документация по этой теме ⧉. Можно создавать политики оповещений, охватывающие сразу несколько проектов, которые срабатывают при приближении квоты к пределу. Почему же это не использовалось?

Проблема в том, что в GCP существуют две совершенно отдельные системы метрик квот, и ни одна из них не поддерживает простой подход «оповещать обо всём подряд».

Потребительские квоты (Consumer Quotas)

Первая система использует метрики serviceruntime.googleapis.com/quota/* с обобщённым типом ресурса consumer_quota. Они охватывают квоты на уровне API: частоту запросов, лимиты выделения ресурсов, квоты хранилища и тому подобное. Метка quota_metric однозначно идентифицирует конкретную квоту в каждом временном ряду.

Хорошая новость: можно написать запрос на PromQL ⧉ (Prometheus Query Language, язык выборки и агрегации метрических временных рядов), который будет захватывать все потребительские квоты без указания конкретных сервисов или названий квот. GCP Cloud Monitoring поддерживает PromQL как альтернативу своему родному языку запросов — именно он лежит в основе описываемых ниже условий оповещений.

Ресурсоспецифичные квоты (Resource-Specific Quotas)

Вторая система использует метрики, привязанные к конкретным сервисам, например compute.googleapis.com/quota/dynamic_routes_per_region_per_peering_group/usage. Каждый сервис определяет собственный тип отслеживаемого ресурса (monitored resource type) и набор меток. Эти квоты относятся к уровню инфраструктуры: маршруты VPC, количество инстансов в сети, узлы GKE в кластере.

Запросы к потребительским квотам их не охватывают. Каждая такая метрика имеет свой путь и свой набор меток для секции on() в PromQL. На сегодняшний день существует более 370 подобных метрик в 28 сервисах.

Инцидент с маршрутизацией VPC? Это была именно ресурсоспецифичная квота. Стандартные оповещения по потребительским квотам её бы никогда не поймали.

Решение

Ключевые требования:

  • Охватывает обе системы квот

  • Автоматически подхватывает новые квоты и сервисы

  • Работает для всех 300 отслеживаемых проектов из единой точки

  • Не требует ручной настройки для каждой квоты

Архитектура

graph LR
subgraph "Scoping Project"
MS[Metrics Scope] --> AP[Alert Policies]
AP --> NC[Slack Channel]
end
subgraph "Monitored Projects"
P1[project-1] --> MS
P2[project-2] --> MS
P3[project-N] --> MS
end
subgraph "Auto-Discovery via github repo for managing Scoping Project"
TF[Terraform] -->|data external| SH[get_quota_metrics.sh]
SH -->|Cloud Monitoring API| PY[build_quota_promql.py]
PY -->|per-service PromQL| TF
end
TF --> AP

Все оповещения работают в едином проекте-области (scoping project). Каждый отслеживаемый проект добавляется в его область метрик (metrics scope) ⧉, и один набор политик оповещений покрывает все проекты. Запросы PromQL группируют данные по project_id, quota_metric, location и service, поэтому каждая уникальная комбинация формирует отдельный инцидент. Вы точно знаете, какая квота, в каком проекте и в каком регионе находится под угрозой.

Оповещения по потребительским квотам

Для потребительских квот созданы три политики оповещений — без фильтра по сервису и без фильтра по имени квоты. Они автоматически охватывают все потребительские квоты.

Использование выделенной квоты (allocation) > 80%: ресурсные лимиты — диски, CPU, IP-адреса:

(
max by (project_id, quota_metric, location, service) (
last_over_time(
serviceruntime_googleapis_com:quota_allocation_usage{
monitored_resource="consumer_quota"
}[6h]
)
)
/
min by (project_id, quota_metric, location, service) (
last_over_time(
serviceruntime_googleapis_com:quota_limit{
monitored_resource="consumer_quota"
}[6h]
)
)
) > 0.8

Использование квоты скорости (rate) > 80%: частота API-запросов; read-only API исключены, чтобы снизить шум — достижение лимита на чтение вызывает повторные попытки, но не аварию:

(
sum by (project_id, quota_metric, location, service) (
increase(
serviceruntime_googleapis_com:quota_rate_net_usage{
monitored_resource="consumer_quota",
quota_metric!~".*/get_.*|.*/list_.*|.*read_requests.*|.*/read$|.*/fetch_.*|.*search_requests.*"
}[1m]
)
)
/
max by (project_id, quota_metric, location, service) (
last_over_time(
serviceruntime_googleapis_com:quota_limit{
monitored_resource="consumer_quota",
quota_metric!~".*/get_.*|.*/list_.*|.*read_requests.*|.*/read$|.*/fetch_.*|.*search_requests.*"
}[6h]
)
)
) > 0.8

Квота превышена: страховочная сетка для всего, что проскочило мимо 80%-го порога, с теми же исключениями для read-only:

max by (project_id, quota_metric, location, service) (
last_over_time(
serviceruntime_googleapis_com:quota_exceeded{
monitored_resource="consumer_quota",
quota_metric!~".*/get_.*|.*/list_.*|.*read_requests.*|.*/read$|.*/fetch_.*|.*search_requests.*"
}[6h]
)
) > 0

Когда кто-то включает новый GCP API или Google добавляет новую квоту, эти запросы подхватывают её без каких-либо изменений в конфигурации.

Оповещения по ресурсоспецифичным квотам

Один запрос здесь не справится: у каждой метрики разные метки. Квота сети VPC имеет метку network_id, квота GKE — cluster_name, квота AI Platform — base_model. Секция on() в PromQL должна подбираться под каждую метрику отдельно.

Вместо того чтобы вести статичный список, запускается скрипт автообнаружения — в качестве источника данных data "external" в Terraform. Вот как он работает по шагам.

Шаг 1: получить дескрипторы метрик

Bash-обёртка обращается к GCP Cloud Monitoring API и получает все дескрипторы метрик в проекте-области. Это включает метрики из всех проектов в области видимости:

curl -s -H "Authorization: Bearer ${TOKEN}" \
"${BASE_URL}/metricDescriptors" > "${METRICS_FILE}"
curl -s -H "Authorization: Bearer ${TOKEN}" \
"${BASE_URL}/monitoredResourceDescriptors" \
> "${RESOURCES_FILE}"

Дескрипторы метрик сообщают, какие метрики квот существуют (например, compute.googleapis.com/quota/dynamic_routes_per_region_per_peering_group/usage) и какие метки метрики у каждой из них есть (например, limit_name).

Дескрипторы ресурсов сообщают, какие метки ресурса есть у каждого типа отслеживаемого ресурса. Например, compute.googleapis.com/VpcNetwork имеет метки resource_container, location и network_id.

Шаг 2: отфильтровать ресурсоспецифичные метрики квот

Python-скрипт обрабатывает JSON. Он находит все метрики, соответствующие шаблону <service>.googleapis.com/quota/<name>/usage и */limit, исключая serviceruntime (это потребительские квоты, обрабатываемые отдельно) и метрики *_internal (у них есть дескрипторы, но alerting API их отклоняет).

Шаг 3: определить правильные метки для on()

Это самая сложная часть. Чтобы деление usage / limit в PromQL работало корректно, секция on() должна перечислять все метки, общие для обеих сторон. Эти метки берутся из двух источников: типа ресурса и самой метрики.

Один подводный камень: в дескрипторах ресурсов API GCP Cloud Monitoring метка проекта называется resource_container, тогда как в реальных PromQL-запросах она фигурирует как project_id. Это было обнаружено при запросе к API временных рядов напрямую и последующем сравнении имён меток.

Для квот, у которых метрика /usage имеет дополнительные метки, отсутствующие в /limit (в первую очередь метрики AI Platform с меткой method), используется group_left() для выполнения соединения «многие к одному».

Шаг 4: преобразовать имена метрик и сгенерировать PromQL

GCP Cloud Monitoring PromQL ⧉ использует иное соглашение об именовании, чем API. Первый символ / заменяется на :, все остальные специальные символы — на _.

Каждая квота превращается в одну PromQL-конструкцию:

clause = (
f"last_over_time({usage_name}[{lookback}])"
f" / on({on_labels}) group_left() "
f"last_over_time({limit_name}[{lookback}])"
f" > {threshold}"
)

Шаг 5: сгруппировать по сервису

Конструкции группируются по имени сервиса, извлечённому из пути метрики, и объединяются оператором or. Скрипт возвращает плоский JSON-объект, где ключи — имена сервисов, а значения — готовые PromQL-запросы:

{
"compute": "last_over_time(...) / on(...) ... > 0.8\nor\nlast_over_time(...) ...",
"container": "...",
"storage": "..."
}

Terraform перебирает эту карту с помощью for_each, создавая по одной политике оповещений на каждый сервис. На данный момент это 28 сервисов, охватывающих более 370 метрик квот. Когда Google добавит новый сервис с ресурсоспецифичными квотами, следующий запуск terraform apply автоматически создаст для него новую политику.

Сгенерированный запрос для квот compute выглядит так (одна конструкция на квоту, объединены через or):

last_over_time(compute_googleapis_com:quota_dynamic_routes_per_region_per_peering_group_usage[6h])
/ on(limit_name, location, network_id, project_id) group_left()
last_over_time(compute_googleapis_com:quota_dynamic_routes_per_region_per_peering_group_limit[6h])
> 0.8
or
last_over_time(compute_googleapis_com:quota_instances_per_vpc_network_usage[6h])
/ on(limit_name, location, network_id, project_id) group_left()
last_over_time(compute_googleapis_com:quota_instances_per_vpc_network_limit[6h])
> 0.8

Когда в каком-либо сервисе появятся новые ресурсоспецифичные метрики квот, следующий terraform apply автоматически создаст для него новую политику оповещений.

Конфигурация Terraform

Конфигурация Terraform связывает всё воедино. Применение for_each по результату скрипта обнаружения создаёт по одной политике оповещений на каждый сервис:

data "external" "quota_metrics" {
program = [
"bash",
"${path.module}/scripts/get_quota_metrics.sh",
local.quota_monitoring_project_id,
tostring(local.quota_alert_threshold),
local.quota_alert_lookback,
]
query = {
exclusions = jsonencode(local.quota_alert_exclusions)
}
}
resource "google_monitoring_alert_policy" "quota_resource_specific" {
for_each = data.external.quota_metrics.result
project = local.quota_monitoring_project_id
display_name = "Quota > 80% - ${each.key} resource quotas"
conditions {
display_name = "${each.key} resource quota > 80%"
condition_prometheus_query_language {
query = each.value
duration = "0s"
evaluation_interval = "30s"
}
}
notification_channels = local.quota_alert_notification_channels
}

Технические сложности

Редкая дискретизация и «мерцание» оповещений

Метрики квот обновляются нечасто. Точки данных поступают каждые 5–15 минут с пробелами между ними. Из-за этого оповещения вели себя непредсказуемо: срабатывали, когда точка данных показывала превышение 80%, тут же закрывались, когда при следующей проверке данных не было, и снова срабатывали при появлении следующей точки.

Условия оповещений на PromQL не поддерживают evaluation_missing_data = "EVALUATION_MISSING_DATA_ACTIVE" — это параметр доступен только для condition_threshold. Решение — обернуть каждый селектор метрики в last_over_time(…​[6h]), который возвращает последнюю точку данных в пределах окна ретроспективы. Мерцание прекратилось.

Подводный камень с resource_container

API GCP Cloud Monitoring в дескрипторах ресурсов указывает метку resource_container, но в реальных PromQL-запросах она фигурирует как project_id. Это было обнаружено путём прямого запроса к API временных рядов и сравнения имён меток. Скрипт автоматически заменяет resource_container на project_id.

Несовпадение меток между usage и limit

Для ряда квот (в основном AI Platform) метрика /usage содержит дополнительную метку method, которой нет в /limit. Простое деление в таком случае не работает, потому что PromQL не может сопоставить временные ряды с разным набором меток. Применение group_left() решает эту проблему, реализуя соединение «многие к одному».

Шум от квот read-only API

Оповещения по квотам скорости оказались крайне шумными. Квоты типа read_requests, list_requests и search_requests срабатывали постоянно. Достижение лимита на чтение приводит к повторным попыткам, но не к авариям — это малозначимый шум, который заглушает настоящие проблемы.

Фильтр по регулярному выражению на метке quota_metric исключает паттерны read-only:

quota_metric!~".*/get_.*|.*/list_.*|.*read_requests.*|.*/read$|.*/fetch_.*|.*search_requests.*"

Процесс: от оповещения к устранению

Когда срабатывает оповещение по квоте, расследование идёт по следующей схеме:

1. Оповещение приходит в Slack с идентификатором проекта, именем квоты, сервисом и текущим соотношением использования.

2. Открываем страницу Quotas в консоли GCP для затронутого проекта. На странице Quotas & System Limits ⧉ отображается текущее использование рядом с лимитами.

3. Проверяем использование API и частоту ошибок, чтобы понять, что именно потребляет квоту. Дашборд API показывает количество запросов, частоту ошибок и задержку по каждому методу:

Дашборд методов GCP API с количеством запросов и частотой ошибок DNS API

4. Увеличиваем квоту, если использование обосновано. Часть квот можно увеличить самостоятельно.

Некоторые квоты помечены как is_fixed и требуют обращения в поддержку. Квота на динамические маршруты VPC, ставшая причиной первого инцидента, относилась именно к таким.

Какие API нужно включить

На каждом отслеживаемом проекте необходимо включить три API, чтобы метрики квот поступали корректно:

API Зачем

cloudquotas.googleapis.com

Точные данные о квотах. По умолчанию не включён.

storage-component.googleapis.com

Видимость квот Google Cloud Storage

storage.googleapis.com

Видимость квот Google Cloud Storage

Все три включаются через Terraform на всех отслеживаемых проектах единожды при первоначальной настройке и добавлены в Terraform-модуль для новых проектов, так что все последующие проекты получают их автоматически.

Ссылки и ресурсы

Cloud Monitoring: Using quota metrics ⧉ — официальная документация Google по оповещениям о квотах

Metrics scopes overview ⧉ — мониторинг нескольких проектов (лимит по умолчанию — 375 проектов)

PromQL metric name transformation ⧉ — как имена метрик GCP преобразуются в имена PromQL

© 2026 meganuke