Инциденты
В мае 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 показывает количество запросов, частоту ошибок и задержку по каждому методу:
4. Увеличиваем квоту, если использование обосновано. Часть квот можно увеличить самостоятельно.
Некоторые квоты помечены как is_fixed и требуют обращения в поддержку. Квота на динамические маршруты VPC, ставшая причиной первого инцидента, относилась именно к таким.
Какие API нужно включить
На каждом отслеживаемом проекте необходимо включить три API, чтобы метрики квот поступали корректно:
| API | Зачем |
|---|---|
|
Точные данные о квотах. По умолчанию не включён. |
|
Видимость квот Google Cloud Storage |
|
Видимость квот Google Cloud Storage |
Все три включаются через Terraform на всех отслеживаемых проектах единожды при первоначальной настройке и добавлены в Terraform-модуль для новых проектов, так что все последующие проекты получают их автоматически.
Ссылки и ресурсы
Cloud Monitoring: Using quota metrics ⧉ — официальная документация Google по оповещениям о квотах
Metrics scopes overview ⧉ — мониторинг нескольких проектов (лимит по умолчанию — 375 проектов)
PromQL metric name transformation ⧉ — как имена метрик GCP преобразуются в имена PromQL