All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m37s
CI / backend-tests (pull_request) Successful in 7m8s
Две тревоги шумели в канале каждую ночь и каждые три часа, обе врали текстом.
NoActiveCeleryWorkers горела с 12.09 без перерыва при живом воркере
(`Up 29 hours`, 15 задач за 10 минут). Экспортёр не поднимался ни разу:
тега danihodovic/celery-exporter:0.13.0 на Docker Hub не существует
(`pull` → `not found`, логи джобы agent-apps 12.09 и 16.09), а под
`set -e` упавший pull обрывал ВСЮ джобу — с 12.09 на Poincare не доезжало
ничего из агента метрик: ни этот экспортёр, ни redis-exporter, ни новый
конфиг Alloy. Второй дефект за первым: флага `--queue` в 0.12.2 нет,
опция зовётся `--queues` — с одним исправленным тегом экспортёр упал бы
при старте. Тег и флаг сверены с исходником (src/cli.py, src/exporter.py)
и пробой на боевом брокере: временный контейнер 0.12.2 отдал
`celery_worker_up{hostname="687f9212bebb"} 1.0`. Адрес брокера не тронут —
алиас `gendesign-redis` на сети gendesign_shared существует и резолвится.
Правило разделено на две новости. NoActiveCeleryWorkers — только когда
экспортёр жив, а воркеров нет. QueueExporterDown (warning) — когда не
отвечает сам экспортёр, с текстом «авария наблюдаемости, не продукта».
Заодно прежняя ветка `count(celery_worker_up == 1) == 0` не сработала бы
никогда: count() от пустого вектора пуст, мёртвый воркер она пропускала.
ContainerNearMemoryLimit на tradein-postgres: working_set включает активный
кэш страниц, ночные сканы поднимают его к потолку. Анонимная память за
неделю 11–170 МБ из 3 ГБ, oom_kill = 0 за 21 сутки — «дальше OOM-kill» был
ложью. Добавлено второе условие: анонимная память > 50 % лимита.
Калибровка ЭТИМ выражением по 14 суткам истории: tradein-postgres 5 → 0
эпизодов, tradein-browser 2 → 2 (06.09 и 12.09, rss до 95 % — настоящие).
Юнит-тесты правил (ops/metrics/prometheus/tests/infra_test.yml, 6 случаев,
каждый уже случался на проде) исполняются деплоем перед reload. На старых
правилах promtool test rules → rc=1 (6 расхождений), на новых → rc=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
5.7 KiB
YAML
117 lines
5.7 KiB
YAML
# Юнит-тесты правил `promtool test rules` (#3493). Исполняются деплоем метрик
|
||
# перед reload Prometheus (.forgejo/workflows/deploy-metrics.yml): упавший тест
|
||
# оставляет работающий Prometheus на прежних правилах.
|
||
#
|
||
# Каждый случай — ровно тот, что уже случился на проде, а не придуманный.
|
||
rule_files:
|
||
- ../rules/infra.yml
|
||
|
||
evaluation_interval: 1m
|
||
|
||
tests:
|
||
# tradein-postgres, ночь 17.09: рабочий набор 98 % от лимита, но почти весь —
|
||
# кэш страниц; анонимная память ~1 %. OOM не грозит — тревоги быть не должно.
|
||
- interval: 1m
|
||
input_series:
|
||
- series: 'container_memory_working_set_bytes{host="apps",name="tradein-postgres"}'
|
||
values: '2940x40'
|
||
- series: 'container_memory_rss{host="apps",name="tradein-postgres"}'
|
||
values: '40x40'
|
||
- series: 'container_spec_memory_limit_bytes{host="apps",name="tradein-postgres"}'
|
||
values: '3000x40'
|
||
alert_rule_test:
|
||
- eval_time: 30m
|
||
alertname: ContainerNearMemoryLimit
|
||
exp_alerts: []
|
||
|
||
# tradein-browser, 12.09: у потолка, и это анонимная память незакрытых
|
||
# инстансов браузера. Тревога обязана прийти, и в тексте — доля, а не байты.
|
||
- interval: 1m
|
||
input_series:
|
||
- series: 'container_memory_working_set_bytes{host="apps",name="tradein-browser"}'
|
||
values: '1960x40'
|
||
- series: 'container_memory_rss{host="apps",name="tradein-browser"}'
|
||
values: '1900x40'
|
||
- series: 'container_spec_memory_limit_bytes{host="apps",name="tradein-browser"}'
|
||
values: '2000x40'
|
||
alert_rule_test:
|
||
- eval_time: 30m
|
||
alertname: ContainerNearMemoryLimit
|
||
exp_alerts:
|
||
- exp_labels:
|
||
severity: warning
|
||
host: apps
|
||
name: tradein-browser
|
||
exp_annotations:
|
||
summary: "Контейнер у своего потолка памяти"
|
||
description: "apps / tradein-browser: 98% от mem_limit, и больше половины лимита — анонимная память процессов, которую ядро не вытеснит как кэш. Дальше OOM-kill."
|
||
|
||
# 12.09–17.09: экспортёр Celery не поднят, серий нет вовсе, воркер при этом жив.
|
||
# Должна гореть QueueExporterDown про celery — и НЕ должна NoActiveCeleryWorkers.
|
||
- interval: 1m
|
||
input_series:
|
||
- series: 'up{job="redis",host="apps"}'
|
||
values: '1x40'
|
||
alert_rule_test:
|
||
- eval_time: 30m
|
||
alertname: NoActiveCeleryWorkers
|
||
exp_alerts: []
|
||
- eval_time: 30m
|
||
alertname: QueueExporterDown
|
||
exp_alerts:
|
||
- exp_labels:
|
||
severity: warning
|
||
job: celery
|
||
exp_annotations:
|
||
summary: "Метрики очереди не собираются"
|
||
description: "celery-экспортёр на Poincare не отдаёт метрики (up=0 или серии нет вовсе). Это авария наблюдаемости, а не продукта: Redis и воркеры могут быть живы — проверь `docker ps`. Пока она горит, RedisDown и NoActiveCeleryWorkers молчат по построению."
|
||
|
||
# Нормальная работа: экспортёр жив, воркер шлёт heartbeat — тишина.
|
||
- interval: 1m
|
||
input_series:
|
||
- series: 'up{job="celery",host="apps"}'
|
||
values: '1x40'
|
||
- series: 'up{job="redis",host="apps"}'
|
||
values: '1x40'
|
||
- series: 'celery_worker_up{hostname="celery@worker",host="apps"}'
|
||
values: '1x40'
|
||
alert_rule_test:
|
||
- eval_time: 30m
|
||
alertname: NoActiveCeleryWorkers
|
||
exp_alerts: []
|
||
- eval_time: 30m
|
||
alertname: QueueExporterDown
|
||
exp_alerts: []
|
||
|
||
# Воркер умер: экспортёр жив, серия воркера в 0. Прежнее правило
|
||
# (`count(x == 1) == 0`) этот случай пропускало — count() от пустого вектора пуст.
|
||
- interval: 1m
|
||
input_series:
|
||
- series: 'up{job="celery",host="apps"}'
|
||
values: '1x40'
|
||
- series: 'celery_worker_up{hostname="celery@worker",host="apps"}'
|
||
values: '0x40'
|
||
alert_rule_test:
|
||
- eval_time: 30m
|
||
alertname: NoActiveCeleryWorkers
|
||
exp_alerts:
|
||
- exp_labels:
|
||
severity: critical
|
||
exp_annotations:
|
||
summary: "Ни одного живого воркера Celery"
|
||
description: "celery-exporter работает, но не видит ни одного heartbeat от воркера Site Finder. Все periodic-таски (парсинг, аналитика, синк слоёв) встали."
|
||
|
||
# Воркер умер давно: экспортёр вычистил его серию, осталось только `up`.
|
||
- interval: 1m
|
||
input_series:
|
||
- series: 'up{job="celery",host="apps"}'
|
||
values: '1x40'
|
||
alert_rule_test:
|
||
- eval_time: 30m
|
||
alertname: NoActiveCeleryWorkers
|
||
exp_alerts:
|
||
- exp_labels:
|
||
severity: critical
|
||
exp_annotations:
|
||
summary: "Ни одного живого воркера Celery"
|
||
description: "celery-exporter работает, но не видит ни одного heartbeat от воркера Site Finder. Все periodic-таски (парсинг, аналитика, синк слоёв) встали."
|