✻ Урок 3.2 · Тема 3: Дашборды и алерты
Алерты и Alertmanager: симптомы, маршруты, runbook
Содержание урока
Зачем это нужно
Конец первой недели в магазине. Дашборды готовы, и менеджер доволен: экран на стене показывает, что всё зелёное. Потом он спрашивает: «А если упадёт ночью, кто это увидит?» Ответ «дежурный посмотрит на экран» не годится. Ночью экран никто не читает.
Расскажу случай, который я наблюдал на соседней команде. У них было два алерта (автоматические проверки, которые зовут человека, когда плохо): «процессор выше 70%» и «диск заполнен на 90%». Первый срабатывал раз в час от любой выгрузки отчётов, и через месяц про него забыли. Второй молчал, пока диск не кончился. В пятницу вечером оплата начала отвечать по десять секунд, покупатели уходили, а ни один алерт не загорелся: процессор был в норме. Об этом команда узнала из чата поддержки, спустя сорок минут.
Урок о том, как узнавать о беде раньше покупателя и не превращаться в человека, которого все алерты давно перестали будить. Ты напишешь правила на симптомы, настроишь Alertmanager (программа, которая решает, кому, когда и в каком виде отправить тревогу), проверишь всё на настоящей поломке и сделаешь инструкцию для дежурного.
Шаг проекта: в ~/monitoring-lab/03-dashboards-alerts/alerting лежат твои правила алертов (записи, в которых сказано, при каком условии Prometheus считает, что плохо) и их тесты, конфиг Alertmanager с маршрутами и runbook-payment.md. Всё это проверено поломкой оплаты на стенде.
Что нужно знать
- Метрики и Prometheus, метки: урок 2.1.
rate,sum by,histogram_quantile,up: урок 2.2. - Дашборды RED и USE, скрипт
orders.shи поломка оплаты: урок 3.1, практика 5. - Бюджет ошибок и SLO: урок 1.2. Симптом против причины: урок 1.1.
- Стенд с профилем
monitoringзапущен.docker compose,curl,jq: load-tester, урок 5.3 и урок 1.1.
Картина целиком
Представь пожарную сигнализацию в офисе. Датчик дыма сам решает, что запахло гарью, и выдерживает пару секунд: вдруг это тостер. Потом сигнал идёт на пульт. Пульт не звонит каждому сотруднику: он объединяет сигналы с одного этажа в один вызов, выбирает, кому звонить, и не шлёт повторов, пока пожарные едут. Рядом с телефоном лежит листок «что делать».
В нашей системе датчик это правило в Prometheus, пульт это Alertmanager, листок это runbook (короткая инструкция на один алерт).
flowchart LR
R["Правило:<br>условие + for"] --> P["Prometheus<br>pending, firing"]
P --> A["Alertmanager:<br>группа, маршрут,<br>подавление"]
A --> C["Получатель:<br>звонок, задача, чат"]
C --> H["Человек<br>с runbook"]
Читай схему слева направо: условие превращается в алерт, алерт проходит через пульт, а человек получает одно понятное уведомление со ссылкой на инструкцию. Каждую стрелку разберём ниже.
Теория
Алерт это вопрос, который Prometheus задаёт сам
Дашборд отвечает, когда ты на него смотришь. Алерт (alert, тревога) задаёт вопрос сам, каждые пять секунд, днём и ночью: «долго ли уже плохо?» Если да, он зовёт человека. Условие это обычное выражение PromQL, которое ты уже писал на дашборде.
Алертинг ломается четырьмя способами. Слишком чувствительный алерт звонит по пустякам, и через неделю его никто не читает. Слишком тугой молчит до тех пор, пока покупатели не уйдут. Алерт без инструкции будит человека, который не знает, что делать. А самый коварный алерт молчит из-за опечатки в метрике: он выглядит живым, но никогда не сработает.
Прикинь сам: алерт «процессор выше 70%» срабатывает дважды в день и каждый раз проходит сам. Что с ним делать: поднять порог до 95% или убрать его?
Скорее убрать. Порог 95% будет молчать, пока сервис не встанет, а 70% будит зря. Процессор это причина, а не жалоба покупателя. Алерт на неё хорош как предупреждение для тикета, но не как повод для звонка. О звонке думай по симптомам, о них ниже.
Осторожно: алерт без for и без runbook почти всегда вреден. Он срабатывает от одного плохого замера и ничего не объясняет.
Главное: алерт это автоматический вопрос «плохо ли покупателю и давно ли»; ловушки четыре: слишком чувствительный, слишком тугой, без инструкции и молчащий из-за опечатки.
Теперь посмотрим, из чего состоит такой вопрос.
Правило алерта: из чего оно состоит
Правило пишут в YAML-файле, который Prometheus читает при старте и по сигналу перечитывания. Файл делится на группы, группа содержит правила. Вот алерт про ошибки покупателей:
- alert: LabHighErrorRate
expr: sum(rate(http_requests_total{status=~"5.."}[1m])) / sum(rate(http_requests_total[1m])) > 0.05
for: 1m
labels: {severity: critical}
annotations:
summary: "Покупатели получают ошибки"
description: "Доля 5xx {{ $value | humanizePercentage }}."
runbook_url: "https://wiki.example.com/runbooks/LabHighErrorRate"
Поля правила по порядку:
alertэто имя, и оно называет симптом: «много ошибок», а не «проверка 7». Оно же становится меткойalertname.exprэто условие: ряд появляется в результате, только когда доля больше 5%.forэто выдержка, о ней следующий раздел.labelsдобавляют метки алерту, и по ним Alertmanager выбирает адресата.annotationsэто подписи для человека: краткоеsummary, подробноеdescriptionи ссылкаrunbook_url.
В аннотациях можно подставлять значения. {{ $value }} это число из выражения, {{ $labels.instance }} это метка. Шаблон в стиле Go, его подставляет Prometheus в момент срабатывания. Функции вроде humanizePercentage превращают 0.1667 в «16.67%».
Различай метки и аннотации. Метки идут в маршрутизацию и в группировку, поэтому должны быть короткими и стабильными: severity, service. Аннотации это текст для человека, и менять его можно сколько угодно: маршруты от него не зависят.
Прикинь сам: ты положил в метку алерта значение запроса, например
labels: {доля: "{{ $value }}"}. Что произойдёт с группировкой?
Метка будет меняться при каждой проверке, и каждый вычисленный алерт окажется «новым». Alertmanager не сможет их объединить, и повторы посыплются как из мешка. Значение кладут в аннотацию, не в метку.
Главное: в правиле
alertназывает симптом,exprего условие,forвыдержку; метки нужны для маршрутов, аннотации (summary,description,runbook_url) для человека.
Осталось понять, что происходит с алертом, пока условие выполняется.
Три состояния алерта и выдержка for
У алерта в Prometheus три состояния. inactive (неактивен): условие не выполняется. pending (ожидание): условие выполнено, но for ещё не истёк. firing (горит): условие держится дольше for, алерт отправлен в Alertmanager. Стоит условию хоть раз перестать выполняться, алерт возвращается в inactive, и отсчёт начинается заново.
Зачем нужен for? Метрики шумят. Один плохой замер из двенадцати за минуту не повод будить человека. Выдержка отсекает всплески, которые проходят сами. Платой становится задержка: алерт с for: 5m не сработает раньше, чем через пять минут после начала беды.
Ниже модель. Двигай порог и for и смотри, какой всплеск разбудил бы дежурного. Состояние pending видно только в Prometheus: Alertmanager узнаёт об алерте, когда он стал firing.
Прикинь сам: ошибки превышают порог 40 секунд и пропадают. У алерта
for: 2m. Что увидит дежурный?
Ничего. Алерт побывает в pending, не дойдёт до firing и вернётся в inactive. Так и задумано: сорок секунд это всплеск. Если ты хочешь ловить и такие всплески, ставь for короче, но готовься к ложным тревогам.
Теперь тот же механизм на настоящей поломке из практики 4: восемь покупателей, оплата замедлена до 5 секунд. На графике доля ошибок 5xx (в процентах), порог 5% и выдержка минута (четыре точки по 15 секунд).
Смотри на полосу состояний под графиком. Условие впервые выполнено в 12:22:45, но алерт ещё pending: выдержка минута. Горит он позже, а уведомление уходит ещё чуть позже, после group_wait. Ошибки пошли в 12:22:00, дежурный узнаёт о них примерно через две минуты.
Как выбрать порог? Не придумывай, а смотри на здоровую систему. Открой дашборд за неделю: какая доля ошибок бывает в обычный день? Если не выше 0,5%, порог 5% оставляет запас в десять раз.
Вот как выглядят сутки здорового магазина и выбранный порог:
Линия ошибок живёт ниже одного процента, один всплеск до 0,9% в обед не страшен. Порог 5% стоит в пять с лишним раз выше самого высокого значения: шума нет, а настоящая беда его пробьёт.
for подбери под цену ошибки: для критичного симптома одна-две минуты, для предупреждения пять-пятнадцать. Потом проверь на настоящей поломке: сработало ли вовремя и не слишком ли шумно.
Осторожно: у алертов на долю нужен минимум трафика. Один упавший запрос из одного даёт 100% ошибок. Добавь условие and sum(rate(http_requests_total[1m])) > 1 и ночью пустой сайт тебя не разбудит.
Главное: алерт проходит inactive, pending, firing;
forотсекает всплески ценой задержки, а числа берут из наблюдений за здоровой системой.
Когда условие и выдержка есть, надо решить, на что вообще реагировать.
Симптом или причина, critical или warning
Первый принцип: симптом, а не причина. «Доля ошибок больше 5%» говорит, что покупателям плохо. «Процессор выше 70%» говорит только, что сервис загружен. Причины ищут потом, по дашборду и логам. Алерт на симптом срабатывает при любой причине, даже той, которую ты не предвидел.
Второй принцип: срочность равна важности. Метка severity: critical значит «разбуди человека сейчас». Метка warning значит «заведи задачу и посмотри утром». Если всё critical, то через неделю не читают ничего.
Из этого следует простая раскладка для нашего магазина. Критичные: магазин недоступен (up == 0) и доля ошибок покупателей выше порога. Предупреждения: медленный p95 и очередь за соединением с базой. Очередь это уже не жалоба покупателя, а механизм: она сигналит, что скоро будет плохо, но человека ночью ради неё будить рано.
Прикинь сам: где у нас граница между «p95 выше секунды» и «ошибок больше 5%»: что из них critical, а что warning?
Ошибки покупателей critical: заказ потерян, деньги потеряны. Медленный p95 warning: люди ждут, но заказ проходит. Если p95 растёт до десяти секунд и вызывает ошибки, сработает уже critical-алерт на ошибки.
Главное: алерт на симптом сработает при любой причине; critical будит человека, warning заводит задачу.
Какие алерты поставить первыми
Новый сервис, список алертов пуст. Хочется поставить всё: на каждую метрику свой порог. Не делай так. Я начинаю с короткого списка, где каждый алерт отвечает на отдельный вопрос покупателя, а вместе они ловят почти любую беду, даже ту, причину которой ты не предвидел.
Первых алертов пять. Не отвечает: сервис совсем недоступен (up == 0). Ошибки покупателя: доля плохих ответов выше порога. Медленно: p95 выше цели. Тишина: трафик пропал в обычный день, хотя полчаса назад был. Скоро станет плохо: ресурс подходит к пределу, например очередь за соединением с базой, а не «процессор выше 70%». Первые два будят человека, третий и пятый заводят задачу, четвёртый только сообщает: причина тишины бывает безобидной (ночь, праздник).
Все пять уже есть в стенде «Магазин», в файле monitoring/prometheus/rules/alerts.yml:
Столбец «Вопрос покупателя» важнее столбца «Условие»: если для алерта нельзя сформулировать такой вопрос, это, скорее всего, алерт на причину, и ему место на дашборде. Четвёртая строка самая необычная: тишина не вызывает ошибок, поэтому алерт на ошибки о ней не скажет.
Прикинь сам: сломался вход: ни один покупатель не может войти, до сервиса запросы почти не доходят. Ошибок 0%, запросов ноль. Какие из пяти алертов сработают?
Сработает «Тишина» (ShopNoTraffic), а при упавшем процессе ещё и «Не отвечает». Алерт на долю ошибок промолчит: при нуле запросов деление даёт NaN, а он ничего не пробивает. Поэтому пять алертов покрывают разные случаи, и ни один из них не заменяет остальные.
Осторожно: «первыми» не значит «навсегда». Каждый алерт проходит ревизию (ниже в этом уроке), и список растёт только вместе с настоящими инцидентами: после разбора сбоя спрашивай, какой алерт сказал бы о нём раньше.
Главное: первые пять алертов: не отвечает, ошибки покупателя, медленно, тишина и ресурс на подходе; каждый отвечает на вопрос покупателя.
Алерт сработал и ушёл в Alertmanager. Что с ним происходит дальше?
Alertmanager: что он делает с алертами
Если бы Prometheus сам звонил людям, во время инцидента он завалил бы дежурного: пять экземпляров сервиса, десять правил, каждые пять секунд. Prometheus умеет считать условия, а решать, кому, когда и сколько раз писать, ему неудобно. Это отдельная работа, и её делает Alertmanager. У него три задачи. Он убирает повторы (дедупликация: один и тот же алерт от Prometheus приходит раз в минуту, а ты получаешь одно сообщение). Он группирует алерты, чтобы пять похожих превратились в одно сообщение. И он направляет алерты по адресатам.
Группировка это объединение алертов с одинаковыми значениями выбранных меток в одно уведомление. Её задаёт group_by. В стенде и в нашей лаборатории стоит group_by: [alertname]: все экземпляры одного алерта приходят вместе. Ловушка: не добавляй в group_by метку instance. Тогда каждый экземпляр получит своё уведомление, и группировка бесполезна.
Три таймера управляют ритмом. group_wait (10 секунд) это ожидание первого уведомления в новой группе: за это время в неё успевают прийти соседи. group_interval (у нас 30 секунд) это пауза перед уведомлением о новых алертах в уже известной группе. repeat_interval (1 час) это пауза перед напоминанием о всё ещё горящем алерте.
Вот как это выглядит по минутам. В 12:00:00 загорается LabHighErrorRate, в 12:00:20 к нему в ту же группу присоединяется второй алерт:
| Время | Что происходит |
|---|---|
| 12:00:00 | Первый алерт пришёл, группа новая, таймер group_wait пошёл |
| 12:00:10 | Прошло 10 секунд: уведомление №1 с одним алертом |
| 12:00:20 | Пришёл второй алерт, он ждёт group_interval |
| 12:00:40 | Прошло 30 секунд после первой отправки: уведомление №2 с двумя алертами |
| 13:00:40 | Оба всё ещё горят, прошёл repeat_interval: напоминание №3 |
Прикинь сам: поломка длится три часа,
repeat_interval: 1h. Сколько уведомлений об одном горящем алерте получит дежурный?
Три или четыре: сразу, и потом напоминание каждый час. Не десятки. Напоминания не дают забыть про не закрытую беду, а не заваливают сообщениями.
Осторожно: у тебя два места, где живёт «ожидание». for в правиле Prometheus и group_wait в Alertmanager. Первое решает, начался ли алерт, второе только собирает группу перед отправкой.
Главное: Alertmanager убирает повторы, группирует по
group_byи направляет по получателям; таймерыgroup_wait,group_interval,repeat_intervalзадают ритм уведомлений.
Группа готова. Теперь нужно выбрать адресата.
Маршруты и получатели
Пример. Пришёл алерт LabShopDown с меткой severity: critical. Alertmanager смотрит ветки сверху вниз: первая требует severity = "critical", метка совпала, значит, алерт уходит получателю page и дальше ветки не проверяются. Алерт с severity: warning первую ветку пропустит и попадёт во вторую, в ticket.
Маршрут (route) это правило «алерты с такими метками отправь вот этому получателю». Маршруты образуют дерево. У корня есть получатель по умолчанию, у веток условия matchers. Alertmanager идёт по веткам сверху вниз и берёт первую подходящую. Если не подошла ни одна, алерт уходит получателю корня.
Получатель (receiver) это адрес, куда доставляют: почта, чат, дежурная система, webhook. Webhook это обычный HTTP-запрос с JSON-телом: так Alertmanager «звонит» произвольному сервису. В лаборатории получателей три: page, ticket и chat, и все три просто HTTP-запросы на маленький скрипт, который печатает тело.
route:
receiver: chat
routes:
- matchers: [severity = "critical"]
receiver: page
- matchers: [severity = "warning"]
receiver: ticket
Порядок веток важен: сработает первая подошедшая. Сравнение идёт буква в букву. severity: Critical с большой буквы не совпадёт с critical и тихо уйдёт в общий чат: так «критичный» алерт ночью никого не будит. Попробуй этот сбой в модели.
Прикинь сам: алерт без метки
severityвообще. Куда он попадёт?
Ни одна ветка не подошла, значит, он уйдёт получателю корня, в чат. Поэтому получателя по умолчанию делают безопасным: чат, который читают, а не «чёрную дыру». Проверить маршрут без настоящего алерта можно утилитой amtool config routes test, мы сделаем это в практике.
Для любопытных: continue и вложенные ветки
По умолчанию после первой подошедшей ветки Alertmanager останавливается. Флаг continue: true заставляет идти дальше и проверять следующие ветки: так один алерт попадёт сразу двум получателям, например в звонок и в общий журнал. Ветки можно вкладывать: внутри ветки service = "shop" свои routes разведут critical и warning. Для первой недели хватает плоского списка, но знать, что вложенность есть, полезно: в чужих конфигах она встречается часто.
Главное: маршруты это дерево по меткам, срабатывает первая подошедшая ветка, остальное уходит получателю корня; метки сравниваются буква в букву.
Иногда один алерт заведомо делает бесполезными другие. Для этого есть подавление.
Подавление и тишина
Когда магазин лежит целиком, задержка и очередь пула тоже вне нормы. Дежурный получит три тревоги вместо одной и потратит время на лишнее. Подавление (inhibit) глушит алерты-следствия, пока горит алерт-причина. В конфиге Alertmanager оно задаётся списком inhibit_rules.
inhibit_rules:
- source_matchers: [alertname = "LabShopDown"] # пока горит этот
target_matchers: [severity = "warning"] # эти не отправлять
equal: [service] # но только с тем же service
source_matchers описывают подавляющий алерт, target_matchers подавляемые, equal перечисляет метки, которые у обоих должны совпадать. Без equal алерт про один сервис заглушил бы предупреждения про все остальные.
Другое средство это тишина (silence): ты вручную говоришь «этот алерт не присылать десять минут». Нужна на плановые работы: ты перезапускаешь сервис и знаешь, что алерт сработает. Тишина создаётся командой amtool silence add или в интерфейсе Alertmanager. У неё всегда есть срок, и у каждой есть автор и комментарий.
Прикинь сам: чем подавление отличается от тишины?
Подавление автоматическое и зависит от других алертов: правило заглушает следствие, пока горит причина. Тишина ручная и привязана ко времени: ты сам решил, что в эти минуты алерт ожидаем. Забытая бессрочная тишина опаснее всего: алерт молчит, а сервис уже сломан.
Главное: подавление автоматически глушит следствия при горящей причине, тишина вручную гасит ожидаемый алерт на заданное время.
Шторм алертов и усталость дежурного
Домашняя сигнализация, которая срабатывает каждый вечер из-за кота, держится неделю. Потом её отключают, и в ту ночь, когда зайдёт настоящий вор, она молчит. С алертами то же самое: когда звонков много, а действий по ним нет, человек привыкает их пропускать.
У этой беды два названия. Шторм алертов (alert storm): одна поломка порождает десятки уведомлений за минуты, и среди них не найти главное. Усталость от алертов (alert fatigue): уведомлений каждый день много, почти все ложные или ничего не требуют, и внимание притупляется. Первое случается в один плохой час, второе накапливается неделями, но лечатся они одинаково.
Рычагов три, и их порядок важен. Меньше алертов: убери те, по которым человек ничего не делает (три вопроса к алерту ниже, в разделе «Как понять, что алерт плохой»). Это самый сильный рычаг, потому что лишний алерт нельзя ни сгруппировать, ни подавить, он просто шум. Группировка и подавление: одна поломка даёт одно уведомление, а следствия глушатся, пока горит причина (разобрали выше). Больше смысла: в summary симптом покупателя, в runbook_url первый шаг; тогда даже оставшийся звонок не тратит минуты на «что это».
Посмотри, как это работает у нас. Если магазин лёг целиком, задержка и очередь пула тоже могут выйти за норму, и без подавления дежурный получил бы три уведомления вместо одного:
flowchart TD
S["Магазин лёг"] --> A1["LabShopDown<br>critical"]
S --> A2["LabHighLatencyP95<br>warning"]
S --> A3["LabDbPoolQueue<br>warning"]
A1 --> M["Alertmanager:<br>inhibit по service"]
A2 --> M
A3 --> M
M --> N["Одно уведомление:<br>LabShopDown"]
Три алерта входят, одно уведомление выходит: так будет в практике ниже, когда правило подавления заглушит предупреждения. Группировка по alertname при этом решает другую задачу: не слать по отдельному звонку на каждый экземпляр одного и того же алерта.
Прикинь сам: у дежурного за ночь 14 уведомлений, на 12 из них он ничего не делал. Что чинить первым: группировку или список алертов?
Список. Двенадцать пустых звонков это алерты без действия, и никакая группировка их не оправдает: надо убрать или перевести в задачи. Группировка уменьшит число одинаковых уведомлений, но шум от самих правил останется.
Осторожно: нельзя «лечить» шторм увеличением repeat_interval или тишиной на всё подряд. Так ты скроешь и настоящую беду. Тишина закрывает ожидаемое событие и только на срок, а чинить надо источник шума.
Главное: шторм и усталость лечатся в порядке «меньше алертов, потом группировка и подавление, потом больше смысла в тексте»; лишний алерт группировкой не исправить.
Алерт дошёл до человека. Теперь важно, чтобы человек знал, что делать.
Runbook: инструкция на случай тревоги
В три часа ночи ты не хочешь думать, ты хочешь выполнять шаги. Runbook (книга запуска, инструкция оператора) это короткий документ на один алерт. Ссылка на него лежит в runbook_url и приходит прямо в уведомлении.
Хороший runbook умещается на экран и отвечает на шесть вопросов:
- Что значит этот алерт, одной фразой.
- Как оценить масштаб: готовый запрос.
- Что проверить, по схеме «если A, то B».
- Как чинить: команды целиком, а не «перезапусти сервис».
- Как убедиться, что починено.
- Кому передать дальше, если не помогло.
Прикинь сам: в runbook написано «проверь базу и перезапусти сервис при необходимости». Чего не хватает?
Всего, что делает инструкцию инструкцией: что именно проверять в базе, по какому признаку «необходимость», какой командой перезапускать и как понять, что помогло. Дежурный в три часа ночи не угадает.
Осторожно: runbook устаревает. После каждого инцидента задай вопрос «что в инструкции оказалось лишним, а чего не хватило?» и поправь её.
Главное: runbook на один алерт отвечает на шесть вопросов (что значит, масштаб, проверки, починка, проверка починки, эскалация) и делает реакцию одинаковой у любого дежурного.
Теперь научимся алертить не на порог, а на скорость, с которой мы тратим допустимые ошибки.
Сгорание бюджета: два окна вместо одного порога
Из урока 1.2 ты помнишь бюджет ошибок: при цели 99% можно потерять 1% запросов за 30 суток. Если ошибок ровно 1%, бюджет кончится к концу месяца и всё в порядке. Если ошибок 10%, он кончится за три дня. Хочется алерт, который реагирует не на «много ошибок сейчас», а на «такими темпами бюджет не доживёт до конца месяца».
Burn rate по-русски скорость сгорания бюджета: во сколько раз бюджет тратится быстрее, чем нужно, чтобы дотянуть до конца месяца. Считается просто: доля ошибок, делённая на бюджет. Ошибок 1% при бюджете 1%: burn rate 1, всё идёт по плану. Ошибок 14,4%: burn rate 14,4, бюджет сгорит за 720 часов делить на 14,4, то есть за 50 часов, примерно двое суток.
Откуда именно 14,4? Решаем, что будить человека стоит, если за один час сгорело 2% месячного бюджета. В месяце 30 дней по 24 часа, то есть 720 часов. При burn rate 1 бюджет тратится ровно за 720 часов, при burn rate X за час уходит в X раз больше обычного. Нужно, чтобы за час ушло 2%: X = 30 × 24 × 0,02 = 14,4.
Один порог не годится: короткий всплеск его пробьёт, а долгая тихая утечка пройдёт мимо. Поэтому берут два окна и связывают их через and. Окно в час подтверждает, что беда настоящая. Окно в пять минут (двенадцатая часть часа) подтверждает, что она идёт прямо сейчас, а не закончилась полчаса назад. Для критичного алерта берут 14,4 и окна 1 час и 5 минут, для предупреждения 6 и окна 6 часов и 30 минут: бюджет кончится за пять суток, это повод завести задачу, а не звонить.
Прикинь сам: 4 минуты подряд 50% ошибок, потом всё чисто. Сработает ли алерт на быстрое сгорание?
Нет. За пять минут ошибок около 40%, это выше порога, а за час всего около 3,3%, это ниже. Нужны оба окна. Это не потеря: четыре минуты забрали около 7% часового бюджета, и дежурного будить незачем. Если же такой темп продержится минут восемнадцать, окно часа дойдёт до порога и правило сработает.
Вот те самые четыре минуты с ошибками на двух окнах сразу:
Синяя линия пятиминутного окна уходит до 40%, выше порога. Фиолетовая часовая доходит только до 3,3% и порога не видит. Правило требует обе линии выше порога сразу, поэтому не срабатывает: беда была настоящей, но короткой.
Осторожно: доля это дробь, и на пустом сайте знаменатель равен нулю. Результат деления на ноль в PromQL не ошибка, а NaN (не число), и сравнение с ним ложно.
Значит, алерт молча не сработает. Поэтому в правилах мы делим на sum(rate(...)) и отдельно требуем минимум трафика, как делал стенд.
Главное: burn rate это доля ошибок делить на бюджет; быстрое правило берёт 14,4 в окнах 1 час и 5 минут через
and, медленное 6 в окнах 6 часов и 30 минут.
Хороший алерт: что в нём написать
Алерт приходит человеку, которого разбудили. Он читает сообщение полусонным и за секунды решает: вставать или нет. Поэтому текст алерта это часть правила, а не украшение.
В summary пишут одну короткую фразу о симптоме: «Покупатели получают ошибки: доля 5xx выше 5%». Не «alert 17» и не «проверка не прошла». В description добавляют подробности: какая доля сейчас, сколько длится, какой сервис. В runbook_url ставят ссылку на инструкцию (мы разберём её ниже). Метка severity определяет адресата, метка service помогает группировать и подавлять.
Проверка текста простая: дай сообщение коллеге и спроси, что случилось и что делать первым шагом. Если он отвечает «не понимаю», перепиши. Типичные плохие тексты: название метрики вместо смысла («shop_db_pool_waiting > 3»), слово «проблема» без уточнения, ссылка на устаревшую страницу.
Пример. Плохо: «HighLatency на shop». Хорошо: «p95 каталога выше секунды уже 10 минут; покупатели ждут; открой runbook, шаг 1: пул соединений». Во втором случае человек может действовать сразу.
Про язык алерта ещё одно правило: пиши то, что видит покупатель, а не то, что видит программист. «Оплата не проходит» понятно любому, кто получил сообщение, а «ошибка 502 на upstream» поймёт только тот, кто знает устройство системы. Техническую деталь клади в description, а в summary оставляй смысл. Так алерт прочитают и дежурный из другой команды, и менеджер, которому его пересланы для решения.
Хорошее сообщение читается за пять секунд и содержит три вещи: что случилось у покупателя, насколько это серьёзно и куда идти дальше. Если одной из трёх нет, дежурный потратит первые минуты на выяснение вместо починки, а ночью эти минуты стоят дорого.
Ещё одна привычка: после каждого ночного звонка дежурный пишет одну строку в журнал алерта: что сделал и помогло ли. Через месяц в журнале видны алерты, на которые никто не реагировал, и инструкции, которые не работали. Это самый дешёвый способ улучшать алертинг: он не требует новых инструментов, только дисциплины.
Главное: summary называет симптом, description даёт числа, runbook_url ведёт к шагам, severity выбирает адресата.
Как понять, что алерт плохой
Алерты, как и код, нужно разбирать. Раз в месяц полезно пройти по списку сработавших и задать каждому три вопроса.
Первый: человек что-то сделал? Если алерт сработал и через минуту закрылся сам, а дежурный ничего не делал, это шум. Его убирают, расширяют for или переводят в предупреждение.
Второй: это было важно покупателю? Если алерт про причину (процессор, диск), а покупатель ничего не заметил, место ему в задаче, а не в звонке.
Третий: раньше ли он узнал, чем сообщили покупатели? Если о беде вы узнаёте из поддержки, алерт опоздал или его не было. Это самый тяжёлый случай: правило надо править сразу.
Есть и обратная метрика: сколько алертов пришло дежурному за неделю. Хорошая цифра для первой недели: несколько звонков, а не десятки. Десятки значат, что на настоящий звонок уже не обращают внимания. Это усталость от алертов: человек привыкает игнорировать.
Проверь понимание: алерт «процессор выше 70%» сработал 14 раз за месяц, и ни разу дежурный ничего не делал. Что с ним делать?
Ответ
Убрать или перевести в предупреждение без звонка: ни одного действия за 14 срабатываний значит, что он шумит. Для настоящей поломки нужен алерт на симптом, а процессор остаётся на дашборде.
Есть и обратная ловушка: слишком тихий алерт. Он молчит, пока покупатели не ушли. Порог 50% ошибок выглядит безопасным: «если уж звонит, значит, правда плохо». Но магазин с 10% ошибок теряет каждый десятый заказ, а алерт спит.
Поэтому порог выбирают по здоровой системе: если обычно ошибок не выше 0,5%, порог 5% даёт запас в десять раз и ловит настоящую беду. Проверь его на поломке, как мы сделаем в практике: шум и тишина видны только на деле.
Главное: раз в месяц разбирай сработавшие алерты по трём вопросам: действовал ли человек, было ли это важно покупателю, узнали ли раньше жалоб.
Эскалация и ночные алерты как мерило качества
Что, если алерт дошёл, а человек его не видит: телефон на беззвучном, он в метро, он спит крепче, чем думал? Алерт без реакции хуже, чем без алерта: все думают, что кто-то уже занимается. Поэтому у дежурства есть эскалация (escalation): если за заданное время никто не подтвердил, что взял тревогу в работу, она идёт дальше, ко второму дежурному, потом к руководителю. Подтверждение (acknowledge) это нажатая кнопка «взял»: время от алерта до неё называют MTTA, подробно про него и про хронологию инцидента в уроке 5.1.
flowchart TD
A["Алерт critical"] --> B["Звонок<br>дежурному 1"]
B --> C{"Взял в работу<br>за 10 минут?"}
C -->|да| D["Работа по runbook"]
C -->|нет| E["Звонок дежурному 2,<br>позже руководителю"]
Десять минут здесь пример, срок выбирает команда по тому, как быстро нужно реагировать на такой алерт. Важная деталь: в Alertmanager нет понятия «подтвердил». Он умеет отправить уведомление, но кнопки «взял» и таймера «никто не взял» у него нет. Эскалацию настраивают в системе дежурств, куда Alertmanager отправляет алерты (так делают сервисы вроде PagerDuty или Grafana OnCall), а в Alertmanager остаётся только маршрут к этой системе.
Пример на цифрах. Днём подтверждение занимает 5 минут, ночью 45: телефон лежит далеко, человек спит. Эскалация через 10 минут на второго дежурного сокращает худший случай с 45 до примерно 10 плюс время реакции второго. Покупатели ждут меньше, а дежурный знает, что за ним есть страховка.
Второе мерило качества: ночные алерты (alerts out of hours). Это уведомления, которые разбудили человека ночью или потревожили в выходной. Их число за неделю простая и честная метрика дежурства. Ноль или единицы: алертинг тихий, каждый звонок настоящий. Десять и больше: человека будят зря, значит, пороги нужно пересмотреть или звонки перевести в задачи на утро. Если ночных алертов много, а действий по ним мало, это возвращает нас к трём вопросам про плохой алерт.
И третье, как проверять, что всё это работает. На учениях (drill) порог алерта нарочно ставят низким, чтобы он сработал без настоящей беды, и смотрят, дошло ли уведомление, взял ли дежурный, сработала ли эскалация. Так проверяют цепочку целиком, не дожидаясь аварии. Как проводить учебную тревогу и считать по ней MTTA, написано в уроке 5.1.
Прикинь сам: алерт critical сработал в три часа ночи, дежурный не ответил за десять минут. Что должно произойти без участия человека?
Система дежурств сама позвонит второму дежурному (и дальше по цепочке). Если для этого нужен человек, который заметит пропуск, эскалации нет: есть только надежда.
Осторожно: эскалация не замена нормальным порогам. Если она срабатывает каждую неделю, смотри, почему дежурные не берут тревоги: мало людей в ротации, шумные алерты или неудобный канал.
Главное: без подтверждения тревога идёт дальше, второму дежурному; число ночных алертов в неделю мерило качества, а низкий порог на учениях проверяет всю цепочку.
Осталось проверить, что все эти правила не молчат из-за опечатки.
Как проверить правила: promtool и «сторож»
Правило с опечаткой в имени метрики проходит проверку синтаксиса и не срабатывает никогда: Prometheus не знает, что метрики с таким именем не бывает. Для таких случаев нужны два средства.
Первое: promtool check rules проверяет синтаксис, а promtool test rules прогоняет правила на выдуманных данных. Ты описываешь ряды (input_series), момент времени и что должно гореть (alert_rule_test с exp_alerts). Запись 0+150x40 значит «начни с нуля, прибавляй 150 каждый шаг, сорок шагов». Так тестируют и срабатывание, и молчание.
Второе: сторож (в конфигах он обычно называется Watchdog). Это алерт, который горит всегда: expr: vector(1). Если сторож перестал приходить, сломан сам алертинг: Alertmanager недоступен, маршруты поломаны. Об этом сигналит внешняя система, которая ждёт его каждые несколько минут. Нужен в большой системе, для первой недели знать о нём достаточно.
Прикинь сам: правило ссылается на
shop_db_pool_waitngвместоshop_db_pool_waiting. Что скажетpromtool check rules?
SUCCESS. Синтаксис правильный, а метрики Prometheus не проверяет. Поймать опечатку можно двумя способами: выполнить выражение в Prometheus и убедиться, что результат не пустой, или написать тест на реальных рядах.
Главное:
promtool checkловит синтаксис,promtool testпроверяет логику на выдуманных данных, а пустой результат выражения в Prometheus значит опечатку или отсутствие метрики.
Мониторинг мониторинга: Watchdog и внешняя проверка
Самое неприятное отказ, о котором некому сообщить. Если упал сам Alertmanager, сломались маршруты или Prometheus перестал считать правила, алертов не будет, а на экране «всё чисто». Тишина выглядит как здоровье. Поэтому за самой системой алертов нужен свой сторож.
Приём простой, его называют dead man’s switch («выключатель мёртвой руки»: машинист должен всё время держать рычаг, отпустил и поезд тормозит). Алерт Watchdog с выражением vector(1) горит всегда, и Alertmanager регулярно шлёт его получателю. Пока пинги идут, цепочка Prometheus, Alertmanager, получатель жива. Пинги прекратились: сломано что-то в цепочке, и об этом должен сказать не сам Alertmanager, а внешняя проверка: другой сервис (в другой сети, в другом месте), который ждёт пинг каждые несколько минут и звонит по независимому каналу, если его нет.
sequenceDiagram
participant P as Prometheus
participant A as Alertmanager
participant W as Внешний сторож
loop каждую минуту
P->>A: Watchdog горит
A->>W: пинг
end
Note over W: пинга нет 5 минут
W-->>W: звонок дежурному: алертинг молчит
Слово «внешняя» главное. Если сторож живёт на том же сервере и в той же сети, что Prometheus, он упадёт вместе с ним и тоже промолчит. Проверка должна жить отдельно и опираться на другой канал связи.
Пример на цифрах. Watchdog шлют раз в минуту, внешний сторож ждёт пять минут. Пинги шли и вдруг пропали: через пять минут тишины сторож звонит дежурному по независимому каналу. О поломке алертинга ты узнаёшь через пять минут, а не после первой настоящей аварии, которая пройдёт без единого алерта.
Прикинь сам: в Alertmanager забыли правильный маршрут, и все алерты уходят в никуда. Какой алерт скажет об этом?
Никакой алерт из самой цепочки: она сломана. Скажет внешний сторож по отсутствию пингов Watchdog. Поэтому проверять надо именно доставку, а не наличие правила.
Осторожно: «Watchdog настроен» ещё не значит «Watchdog контролируется». Алерт, который горит и приходит в никуда, бесполезен. Проверяй, что есть получатель, который заметит пропажу, и время от времени останавливай Prometheus на учениях, чтобы увидеть, что сторож сработает.
Главное: Watchdog горит всегда, а его пропажу замечает внешняя проверка по другому каналу: так узнают, что сломан сам алертинг.
Практика
Файлы кладём в ~/monitoring-lab/03-dashboards-alerts/alerting. Нагрузку и поломки делаем только на своём локальном стенде. Стенд запущен с профилем monitoring. Все числа в блоках «Что должно получиться» это пример: у тебя они будут другими. Стенд мы не меняем: у него свой Prometheus на порту 9090 и свой Alertmanager на 9093. Рядом поднимем свои: Prometheus на 9091, Alertmanager на 9094 и webhook, который печатает получаемые уведомления.
1. Поднимаем лабораторию алертов
Создадим папку и четыре файла: описание контейнеров, конфиг Prometheus, простой конфиг Alertmanager и webhook-приёмник.
mkdir -p ~/monitoring-lab/03-dashboards-alerts/alerting/{rules,tests} && cd ~/monitoring-lab/03-dashboards-alerts/alerting
cat > lab.yaml <<'EOF'
# Лаборатория алертов: свои Prometheus, Alertmanager и webhook рядом со стендом.
# Стенд не меняется: контейнеры подключаются к его сети shop_default и опрашивают shop и payment по имени.
name: lab-alerting
services:
lab-prometheus:
image: prom/prometheus:v3.15.0
command: ["--config.file=/etc/prometheus/prometheus.yml", "--storage.tsdb.path=/prometheus", "--web.enable-lifecycle"]
ports: ["127.0.0.1:9091:9090"]
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./rules:/etc/prometheus/rules:ro
lab-alertmanager:
image: prom/alertmanager:v0.34.1
# Пустой адрес кластера: одиночному Alertmanager соседи не нужны.
command: ["--config.file=/etc/alertmanager/alertmanager.yml", "--storage.path=/alertmanager", "--cluster.listen-address="]
ports: ["127.0.0.1:9094:9093"]
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
lab-webhook:
image: python:3.14.8-slim
command: ["python", "/app/webhook.py"]
volumes:
- ./webhook.py:/app/webhook.py:ro
networks:
default:
name: shop_default
external: true
EOF
cat > prometheus.yml <<'EOF'
# Лабораторный Prometheus: опрашивает те же сервисы стенда и шлёт алерты в свой Alertmanager.
global:
scrape_interval: 5s
evaluation_interval: 5s
rule_files:
- /etc/prometheus/rules/*.yml
alerting:
alertmanagers:
- static_configs:
- targets: ["lab-alertmanager:9093"]
scrape_configs:
- job_name: shop
static_configs: [{targets: ["shop:8000"]}]
- job_name: payment
static_configs: [{targets: ["payment:8001"]}]
EOF
cat > webhook.py <<'EOF'
# webhook.py: печатает каждое уведомление Alertmanager одной строкой.
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
names = sorted(a["labels"].get("alertname", "?") for a in body["alerts"])
print(self.path, body["status"], names, flush=True)
self.send_response(200)
self.end_headers()
def log_message(self, *args):
pass
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
EOF
cat > alertmanager.yml <<'EOF'
# Первая версия: все алерты идут одному получателю.
route:
receiver: chat
group_by: [alertname]
group_wait: 10s
group_interval: 30s
repeat_interval: 1h
receivers:
- name: chat
webhook_configs: [{url: "http://lab-webhook:8080/chat"}]
EOF
docker compose -f lab.yaml up -d
Разбор lab.yaml:
- это отдельный compose-проект
lab-alerting; - он подключается к сети стенда
shop_default(полеexternal: true), поэтому видитshopиpaymentпо имени; --web.enable-lifecycleразрешает перечитывать конфиг запросом;- пустой
--cluster.listen-address=отключает кластерный режим Alertmanager, он нам не нужен.
Webhook на Python только печатает путь, статус и имена алертов.
Проверь, что Prometheus видит цели:
curl -s localhost:9091/api/v1/targets | jq -r '.data.activeTargets[] | [.labels.job, .health] | @tsv'
Что должно получиться (пример):
shop up
payment up
Как читать вывод: две строки, у обеих up: твой Prometheus опрашивает те же сервисы, что и стендовый. Если down, посмотри lastError в том же ответе.
Это тот же экран, что на http://localhost:9091/targets. Смотри на счётчик у группы (1/1 up) и на колонку State: зелёный UP значит, что метрики свежие.
А вот та же страница, когда магазин остановлен:
Первым делом читай красный текст ошибки: connection refused значит, что на адресе никто не слушает порт, то есть контейнер остановлен. Именно на состояние up == 0 смотрит алерт LabShopDown.
Типичные ошибки:
network shop_default declared as external, but could not be found: стенд не запущен или проект называется иначе. Проверьdocker network ls | grep shop.port is already allocatedна 9091 или 9094: порт занят. Замени левую часть вportsвlab.yamlна свободную и используй её в командах ниже.- Пустой вывод: подожди 10 секунд, цели появляются после первого опроса.
2. Правила на симптомы и их проверка
Положим в rules/lab-alerts.yml четыре алерта: два на симптомы покупателя (критичные) и два на задержку и очередь (предупреждения). Выдержки for короче боевых, чтобы прогон занял минуты.
cat > rules/lab-alerts.yml <<'EOF'
# Алерты на симптомы покупателя и один на механизм (очередь в пуле БД).
# Выдержки for короче боевых, чтобы учебный прогон шёл минуты, а не полчаса.
groups:
- name: lab-symptoms
rules:
- alert: LabShopDown
expr: up{job="shop"} == 0
for: 30s
labels: {severity: critical, service: shop}
annotations:
summary: "Магазин недоступен"
description: "Prometheus не может снять метрики с {{ $labels.instance }} дольше 30 секунд."
runbook_url: "https://wiki.example.com/runbooks/LabShopDown"
- alert: LabHighErrorRate
# Знаменатель не меньше 1 запроса в секунду: на пустом сайте доля ошибок ничего не значит.
expr: |
(sum(rate(http_requests_total{status=~"5.."}[1m])) or vector(0))
/ sum(rate(http_requests_total[1m])) > 0.05
and sum(rate(http_requests_total[1m])) > 1
for: 1m
labels: {severity: critical, service: shop}
annotations:
summary: "Покупатели получают ошибки"
description: "Доля 5xx {{ $value | humanizePercentage }} больше 5% минуту подряд."
runbook_url: "https://wiki.example.com/runbooks/LabHighErrorRate"
- alert: LabHighLatencyP95
expr: histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[1m]))) > 1
for: 1m
labels: {severity: warning, service: shop}
annotations:
summary: "Магазин отвечает медленно"
description: "p95 запросов {{ $value | humanizeDuration }}, порог 1 секунда."
runbook_url: "https://wiki.example.com/runbooks/LabHighLatencyP95"
- alert: LabDbPoolQueue
expr: shop_db_pool_waiting > 0
for: 30s
labels: {severity: warning, service: shop}
annotations:
summary: "Очередь за соединением с базой"
description: "Запросов в очереди: {{ $value }}. Пул исчерпан."
runbook_url: "https://wiki.example.com/runbooks/LabDbPoolQueue"
EOF
Разбор. Условие на ошибки делит ошибки на все запросы, а or vector(0) подставляет ноль, когда пятисотых нет. Последняя строка требует больше одного запроса в секунду: пустой сайт не будит. Метка service: shop понадобится подавлению. Ссылки runbook_url ведут на выдуманную вики: в боевой системе здесь твоя страница.
Проверим синтаксис и загрузим правила:
docker run --rm -v "$PWD/rules:/r:ro" --entrypoint promtool prom/prometheus:v3.15.0 check rules /r/lab-alerts.yml
curl -s -X POST localhost:9091/-/reload
curl -s localhost:9091/api/v1/rules | jq -r '.data.groups[].rules[] | select(.type=="alerting") | [.name, .state] | @tsv'
Что должно получиться (пример):
Checking /r/lab-alerts.yml
SUCCESS: 4 rules found
LabShopDown inactive
LabHighErrorRate inactive
LabHighLatencyP95 inactive
LabDbPoolQueue inactive
Как читать вывод: SUCCESS: 4 rules found значит, что синтаксис верный и правил четыре. Второй запрос показывает, что Prometheus загрузил их и ни одно не горит: магазин здоров.
Теперь тест логики. Он подставляет выдуманные ряды: 10 успешных и 2 неудачных запроса в секунду. Алерт должен молчать в первую минуту (for), гореть на третьей и не гореть на пустом сайте.
cat > tests/lab-alerts-test.yml <<'EOF'
# Тесты правил: подставляем выдуманные ряды и проверяем, что алерт горит, когда должен, и молчит, когда не должен.
rule_files:
- ../rules/lab-alerts.yml
evaluation_interval: 15s
tests:
# Сценарий 1: 10 успешных запросов в секунду и 2 ошибки в секунду: доля ошибок 16,7%.
- interval: 15s
input_series:
- series: 'http_requests_total{status="200"}'
values: '0+150x40'
- series: 'http_requests_total{status="500"}'
values: '0+30x40'
alert_rule_test:
- eval_time: 1m # выдержка for: 1m ещё не прошла
alertname: LabHighErrorRate
exp_alerts: []
- eval_time: 3m
alertname: LabHighErrorRate
exp_alerts:
- exp_labels: {severity: critical, service: shop}
exp_annotations:
summary: "Покупатели получают ошибки"
description: "Доля 5xx 16.67% больше 5% минуту подряд."
runbook_url: "https://wiki.example.com/runbooks/LabHighErrorRate"
# Сценарий 2: пустой магазин. Ошибок 100%, но запросов меньше одного в секунду: молчим.
- interval: 15s
input_series:
- series: 'http_requests_total{status="500"}'
values: '0+1x40'
alert_rule_test:
- eval_time: 3m
alertname: LabHighErrorRate
exp_alerts: []
EOF
docker run --rm -v "$PWD:/w:ro" -w /w --entrypoint promtool prom/prometheus:v3.15.0 test rules tests/lab-alerts-test.yml
Что должно получиться (пример):
SUCCESS
Как читать вывод: SUCCESS значит, что на выдуманных данных алерт ведёт себя как задумано. Если ты изменишь порог на 0.5, тест упадёт блоком FAILED: с ожидаемым и полученным: так тест ловит случайную порчу правила.
Типичные ошибки:
yaml: line N: did not find expected key: сломаны отступы. Правила в списке начинаются с- alert:на одном уровне.rule_files ... no such file: путь в тесте относительный, от папки теста. Запускай команду изalerting, как показано.FAILEDсgot: []: правило не сработало. Проверь порог,forи минимальный трафик.
3. Маршруты, подавление и тишина
Заменим простой конфиг на конфиг с маршрутами и подавлением. Сначала проверим дерево без запуска, потом применим.
cat > alertmanager.yml <<'EOF'
# Лабораторный Alertmanager: маршруты по severity, подавление и webhook-получатели.
route:
receiver: chat # всё, что не подошло ни под одну ветку
group_by: [alertname]
group_wait: 10s
group_interval: 30s
repeat_interval: 1h
routes:
- matchers: [severity = "critical"]
receiver: page
- matchers: [severity = "warning"]
receiver: ticket
inhibit_rules:
- source_matchers: [alertname = "LabShopDown"]
target_matchers: [severity = "warning"]
equal: [service]
receivers:
- name: page
webhook_configs: [{url: "http://lab-webhook:8080/page"}]
- name: ticket
webhook_configs: [{url: "http://lab-webhook:8080/ticket"}]
- name: chat
webhook_configs: [{url: "http://lab-webhook:8080/chat"}]
EOF
docker run --rm -v "$PWD/alertmanager.yml:/a.yml:ro" --entrypoint amtool prom/alertmanager:v0.34.1 check-config /a.yml
for s in critical warning info; do docker run --rm -v "$PWD/alertmanager.yml:/a.yml:ro" --entrypoint amtool prom/alertmanager:v0.34.1 config routes test --config.file=/a.yml severity=$s; done
Что должно получиться (пример):
Checking '/a.yml' SUCCESS
Found:
- global config
- route
- 1 inhibit rules
- 3 receivers
- 0 templates
page
ticket
chat
Как читать вывод: три последние строки показывают, куда уйдёт алерт с severity=critical, warning и info. Так проверяют маршруты без настоящей поломки. Если ты запишешь severity=Critical, увидишь chat: опечатка в метке.
Применим конфиг и отправим вручную три тестовых алерта: предупреждение, критичный и один без подходящей важности.
curl -s -X POST localhost:9094/-/reload
curl -s -X POST localhost:9094/api/v2/alerts -H 'Content-Type: application/json' -d '[
{"labels":{"alertname":"LabDbPoolQueue","severity":"warning","service":"shop"}},
{"labels":{"alertname":"LabHighErrorRate","severity":"critical","service":"shop"}},
{"labels":{"alertname":"Other","severity":"info"}}]'
sleep 15
docker compose -f lab.yaml logs lab-webhook --no-log-prefix | tail -3
Что должно получиться (пример):
/chat firing ['Other']
/ticket firing ['LabDbPoolQueue']
/page firing ['LabHighErrorRate']
Как читать вывод: каждый алерт пришёл на свой адрес, группировка по alertname сделала по одному уведомлению, а алерт без подходящей ветки ушёл по умолчанию, в chat. Ждать пришлось 10 секунд: это group_wait.
Теперь подавление. Шлём «магазин лежит» вместе с предупреждением:
curl -s -X POST localhost:9094/api/v2/alerts -H 'Content-Type: application/json' -d '[
{"labels":{"alertname":"LabShopDown","severity":"critical","service":"shop"}},
{"labels":{"alertname":"LabHighLatencyP95","severity":"warning","service":"shop"}}]'
sleep 15
docker compose -f lab.yaml logs lab-webhook --no-log-prefix | tail -1
docker compose -f lab.yaml exec lab-alertmanager amtool --alertmanager.url=http://localhost:9093 alert query --inhibited
Что должно получиться (пример):
/page firing ['LabShopDown']
Alertname Starts At Summary State
LabDbPoolQueue 2026-10-04 12:27:19 +0300 suppressed
LabHighLatencyP95 2026-10-04 12:27:37 +0300 suppressed
Как читать вывод: на webhook пришёл только LabShopDown. Предупреждения про задержку и очередь заглушены подавлением: они видны с ключом --inhibited, состояние suppressed. Подавились они потому, что у всех service: shop.
И тишина. Заглушим Other на десять минут и посмотрим список:
docker compose -f lab.yaml exec lab-alertmanager amtool --alertmanager.url=http://localhost:9093 silence add alertname=Other --duration=10m --comment="проверка тишины" --author=me
docker compose -f lab.yaml exec lab-alertmanager amtool --alertmanager.url=http://localhost:9093 silence query
Как читать вывод: команда печатает идентификатор тишины, а silence query показывает её условия и время окончания. Тестовые алерты без endsAt сами закроются через 5 минут: тогда на webhook придут строки со статусом resolved.
Типичные ошибки:
Error: ... connection refusedуamtool: адресlocalhost:9093верен только внутри контейнера, поэтому команда идёт черезexec.- В webhook пусто: прошло меньше 10 секунд (
group_wait) или Alertmanager не перечитал конфиг. Проверьdocker compose -f lab.yaml logs lab-alertmanager. - Тестовый алерт не подавился: у
LabShopDownи предупреждения разные значенияservice. Правилоequal: [service]требует совпадения.
4. Настоящий инцидент: оплата отвечает по 5 секунд
Теперь проверим систему на настоящей поломке. Открой три окна: http://localhost:9091/alerts (твой Prometheus), http://localhost:9094 (твой Alertmanager) и лог webhook.
cd ~/monitoring-lab/03-dashboards-alerts
for i in 1 2 3 4 5 6 7 8; do ./orders.sh "$i" 480 & done
sleep 120
curl -s -X POST localhost:8001/admin/config -H 'Content-Type: application/json' -d '{"delay_ms": 5000}'
docker compose -f alerting/lab.yaml logs -f lab-webhook --no-log-prefix
Цикл запускает восемь покупателей на восемь минут, sleep 120 даёт две минуты спокойной работы для сравнения, потом оплата замедляется (это то же самое, что в уроке 3.1). Последняя команда показывает уведомления по мере поступления. Остановить просмотр можно Ctrl+C. Через четыре минуты после замедления верни оплату командой ниже.
Что должно получиться (пример, время идёт от начала поломки):
/ticket firing ['LabDbPoolQueue'] # около 45 с: очередь + for 30 с + group_wait 10 с
/ticket firing ['LabHighLatencyP95'] # около 1,5 минут: p95 выше секунды + for 1 м
/page firing ['LabHighErrorRate'] # когда доля 5xx станет больше 5% и продержится минуту
Как читать вывод: сначала приходят предупреждения (очередь и задержка), потом, если ошибки пошли, критичный алерт. Каждое уведомление пришло ровно один раз, а не каждые пять секунд. Порядок и время у тебя будут другими.
Пока алерты горят, посмотри на поломку теми же глазами, что у дежурного. Ниже примеры того, что ты увидишь у себя (числа другие, картина та же).
Карточки читай слева направо: ошибки и задержка красные, очередь за соединением оранжевая, процессор зелёный. Алерт «процессор выше 70%» тут молчал бы, а покупатели уже получают ошибки: так выглядит разница между причиной и симптомом.
Смотри на два флажка: до первого линия лежит у нуля, после него взлетает за порог через 45 секунд, после второго возвращается к нулю. Видно полную жизнь поломки: от нуля к плато около 19% и обратно.
Главное здесь не красная полоска, а высота всей стопки: поток упал с 46 до 3 запросов в секунду. Покупатели застряли в оплате, поэтому новых заказов почти нет. Красная часть при этом около пятой доли стопки: это те самые 19% с графика выше.
Теперь посмотри на сами алерты в своём Prometheus: http://localhost:9091/graph, вкладка Table.
В таблице нет цифр про ошибки, только имена и метки: она отвечает на вопрос «что горит». Обрати внимание на severity: единственный critical это алерт на ошибки покупателей, остальные два предупреждения.
Алерт сказал «ошибки». Причину ищем в логе: открой Explore (Loki) на время поломки и спроси про долгие заказы.
Посмотри на duration_ms у всех строк: около пяти секунд и у ошибок, и у успешных заказов. У ошибок error называет причину: couldn't get a connection after 5.00 sec, то есть запрос не дождался соединения с базой. Время в Grafana московское, а ts внутри строки в UTC, разница три часа.
Возьми trace_id из строки с 201 и нажми «Открыть трейс». Вот заказ, который получил соединение, но застрял в оплате:
Почти вся ширина водопада это одна полоса POST /pay в сервисе payment: около 5,2 секунды. SQL и Redis занимают миллисекунды, ожидание соединения db.pool.getconn почти ноль. Этот заказ повезло получить соединение, и оплата держит его всё это время.
А так выглядит заказ из строки с 503: он соединения не дождался.
Тут нет ни SQL, ни POST /pay: запрос пять секунд простоял в db.pool.getconn (красная полоса) и отказался. Две картинки вместе дают полную причину: оплата держит соединения, пул из пяти кончился, остальные ждут и падают.
Верни оплату и дождись разрешения:
curl -s -X POST localhost:8001/admin/config -H 'Content-Type: application/json' -d '{"delay_ms": 50}'
Через пару минут в логе webhook появятся строки со статусом resolved. Запиши в ~/monitoring-lab/03-dashboards-alerts/alerting/incident-notes.md время начала поломки, время каждого уведомления и вывод: какой алерт сказал о беде первым и был ли он симптомом.
Типичные ошибки:
LabHighErrorRateне сработал, а очередь есть: проверь на дашбордеlab-redдолю ошибок и Rate. Если запросов меньше одного в секунду, условие «минимум трафика» не выполнено, и это правильно. Подними число копийorders.sh.- Уведомлений нет совсем: открой
http://localhost:9091/alerts. Алерт вpendingзначит, чтоforне истёк. Алерта нет вообще: проверь, чтоorders.shзапущен (jobs). - Алерты горят, а в Alertmanager пусто: в
prometheus.ymlневерный адрес. Проверьcurl -s localhost:9091/api/v1/alertmanagers | jq.
5. Сгорание бюджета: два окна на выдуманных данных
На живом стенде час истории не накопишь за урок, поэтому проверим burn rate тестом. Правило берёт два окна, быстрое сгорание, как в slo.yml стенда.
cd ~/monitoring-lab/03-dashboards-alerts/alerting
cat > rules/lab-burn.yml <<'EOF'
# Сгорание бюджета ошибок. SLO 99%: бюджет 1%, значит норма расхода это 1% ошибок.
groups:
- name: lab-sli
rules:
# Доля ошибок 5xx за окно. `or vector(0)`: без единой пятисотки ряда нет, а нужен ноль.
- record: lab:error_ratio:rate5m
expr: (sum(rate(http_requests_total{status=~"5.."}[5m])) or vector(0)) / sum(rate(http_requests_total[5m]))
- record: lab:error_ratio:rate1h
expr: (sum(rate(http_requests_total{status=~"5.."}[1h])) or vector(0)) / sum(rate(http_requests_total[1h]))
- name: lab-burn
rules:
# Бюджет горит в 14,4 раза быстрее нормы: порог 14,4 * 1% = 14,4% ошибок.
# Час говорит, что беда настоящая, пять минут: что она идёт прямо сейчас.
- alert: LabBudgetBurnFast
expr: lab:error_ratio:rate1h > (14.4 * 0.01) and lab:error_ratio:rate5m > (14.4 * 0.01)
for: 2m
labels: {severity: critical, service: shop}
annotations:
summary: "Месячный бюджет ошибок кончится меньше чем за 2 суток"
runbook_url: "https://wiki.example.com/runbooks/LabBudgetBurnFast"
EOF
cat > tests/lab-burn-test.yml <<'EOF'
# Два сценария: короткий всплеск не будит, долгая беда будит.
rule_files:
- ../rules/lab-burn.yml
evaluation_interval: 1m
tests:
# Всплеск: 55 минут чисто, потом 5 минут по 50% ошибок. За 5 минут ошибок 50%, но за час всего 7,7%: меньше порога 14,4%.
- interval: 1m
input_series:
- series: 'http_requests_total{status="200"}'
values: '0+600x70'
- series: 'http_requests_total{status="500"}'
values: '0x55 600+600x4 3000x10'
alert_rule_test:
- eval_time: 60m
alertname: LabBudgetBurnFast
exp_alerts: []
# Долгая беда: все 70 минут по 2 ошибки на 10 успешных в секунду, около 16,7%.
- interval: 1m
input_series:
- series: 'http_requests_total{status="200"}'
values: '0+600x70'
- series: 'http_requests_total{status="500"}'
values: '0+120x70'
alert_rule_test:
- eval_time: 65m
alertname: LabBudgetBurnFast
exp_alerts:
- exp_labels: {severity: critical, service: shop}
exp_annotations:
summary: "Месячный бюджет ошибок кончится меньше чем за 2 суток"
runbook_url: "https://wiki.example.com/runbooks/LabBudgetBurnFast"
EOF
docker run --rm -v "$PWD:/w:ro" -w /w --entrypoint promtool prom/prometheus:v3.15.0 check rules rules/lab-burn.yml rules/lab-alerts.yml
docker run --rm -v "$PWD:/w:ro" -w /w --entrypoint promtool prom/prometheus:v3.15.0 test rules tests/lab-burn-test.yml
curl -s -X POST localhost:9091/-/reload
Что должно получиться (пример):
Checking rules/lab-burn.yml
SUCCESS: 3 rules found
Checking rules/lab-alerts.yml
SUCCESS: 4 rules found
SUCCESS
Как читать вывод: три правила в lab-burn.yml: два записанных (lab:error_ratio:rate5m и rate1h) и один алерт. Тест проверил два сценария: короткий всплеск не будит (за час всего 7,7%), долгая беда с 16,7% ошибок будит.
Открой rules/lab-burn.yml и сравни его с ~/learning/load-tester/project/shop/monitoring/prometheus/rules/slo.yml: те же записанные правила, те же 14,4 и два окна. Найди в стенде и медленный вариант на шести часах и 30 минутах.
Типичные ошибки:
- На пустом стенде
lab:error_ratio:rate1hравенNaNили не имеет значения: трафика не было, знаменатель ноль. Алерт в этом случае молчит, что верно. - Тест падает с
got: []вместо алерта: правило ждётfor: 2mи два окна; ставь в тестеeval_timeне раньше, чем условие продержалосьfor: 2mпосле начала нарушения, и чтобы в окнеrateхватало точек (минимум две). В наших сценариях 60 и 65 минут взяты с запасом: окно в час уже заполнено. check rulesругается наlab:error_ratio:rate5m: двоеточия в имени записанного правила допустимы, а вот пробелы нет.
6. Runbook на шесть вопросов: шаг проекта
Напиши runbook-payment.md для алерта LabDbPoolQueue. Ориентируйся на шесть вопросов из теории и на запросы, которые ты уже использовал на дашборде. Заполни все ____ своими командами и значениями.
# Runbook: LabDbPoolQueue и LabHighErrorRate при медленной оплате
**Что значит:** пул соединений с базой исчерпан, запросы стоят в очереди или получают 503.
**Масштаб:** `sum by (route, status) (rate(http_requests_total{status=~"5.."}[1m]))`
**Проверки:**
1. `shop_db_pool_waiting` больше 0 и `shop_db_pool_available` равно 0? Если нет, это не пул.
2. p95 оплаты: `histogram_quantile(0.95, sum by (le) (rate(shop_payment_duration_seconds_bucket[1m])))`. Выросло? Тогда причина в оплате.
3. Если оплата в норме: ____ (что проверить дальше в базе).
**Починка:** ____
**Проверка починки:** ____
**Эскалация:** ____
Сверь текст с шестью вопросами. Если можешь выполнить его с закрытыми глазами, не зная стенда, он годится. Проверь запросы в Prometheus: каждая команда должна возвращать результат.
Вот что вернёт запрос «Масштаб» из шаблона во время поломки оплаты:
Одна строка: ошибки идут только из /api/orders, остальные маршруты чистые, поток около 0,6 ответа в секунду. Дежурный по такому результату понимает, что это не весь магазин, а один маршрут.
Добавь итоги в git:
cd ~/monitoring-lab && git add 03-dashboards-alerts && git commit -m "3.2: алерты, Alertmanager, runbook"
7. Сторож: проверяем, что алертинг жив
Добавим Watchdog и отдельный получатель deadman, а потом остановим Prometheus и посмотрим, как выглядит тишина.
cd ~/monitoring-lab/03-dashboards-alerts/alerting
cat > rules/lab-watchdog.yml <<'EOF'
# Сторож: горит всегда. Пока он приходит, цепочка Prometheus, Alertmanager, получатель жива.
groups:
- name: lab-meta
rules:
- alert: Watchdog
expr: vector(1)
labels: {severity: none}
annotations:
summary: "Сторож: алертинг работает"
EOF
cat > alertmanager.yml <<'EOF'
# Лабораторный Alertmanager: сторож идёт первой веткой и повторяется раз в минуту.
route:
receiver: chat
group_by: [alertname]
group_wait: 10s
group_interval: 30s
repeat_interval: 1h
routes:
- matchers: [alertname = "Watchdog"]
receiver: deadman
repeat_interval: 1m
- matchers: [severity = "critical"]
receiver: page
- matchers: [severity = "warning"]
receiver: ticket
inhibit_rules:
- source_matchers: [alertname = "LabShopDown"]
target_matchers: [severity = "warning"]
equal: [service]
receivers:
- name: deadman
webhook_configs: [{url: "http://lab-webhook:8080/deadman"}]
- name: page
webhook_configs: [{url: "http://lab-webhook:8080/page"}]
- name: ticket
webhook_configs: [{url: "http://lab-webhook:8080/ticket"}]
- name: chat
webhook_configs: [{url: "http://lab-webhook:8080/chat"}]
EOF
curl -s -X POST localhost:9091/-/reload
curl -s -X POST localhost:9094/-/reload
sleep 150
docker compose -f lab.yaml logs lab-webhook --no-log-prefix | grep deadman | tail -3
Первый файл создаёт алерт, который горит всегда (vector(1) даёт единицу, условие выполнено сразу). Второй повторяет маршруты из практики 3 и добавляет первой ветку для сторожа: адрес /deadman и repeat_interval: 1m, чтобы пинги шли примерно раз в минуту. Остальное в файле без изменений.
Что должно получиться (пример):
/deadman firing ['Watchdog']
/deadman firing ['Watchdog']
Как читать вывод: строки приходят примерно раз в минуту, пока цепочка жива: это и есть пинги сторожа. Теперь остановим Prometheus, как будто сломался алертинг:
docker compose -f lab.yaml stop lab-prometheus
sleep 180
docker compose -f lab.yaml logs lab-webhook --no-log-prefix --since 2m | grep -c deadman
docker compose -f lab.yaml start lab-prometheus
Счётчик должен показать 0 или заметно меньше, чем раньше: новых пингов нет. Именно такой тишины ждёт внешний сторож в боевой системе, чтобы позвонить. Мы смотрим в лог сами, а настоящий сторож следит за этим без тебя и по независимому каналу.
Типичные ошибки:
- Пинги не идут: проверь
docker compose -f lab.yaml logs lab-alertmanager(ошибка вalertmanager.yml) и что/-/reloadвернул пустой ответ. АлертWatchdogдолжен быть вhttp://localhost:9091/alertsв состоянииfiring. - Пинги приходят чаще минуты или реже: Alertmanager отправляет не чаще, чем раз в
group_interval(30 секунд), поэтому интервал округляется вверх до его кратного. - После
startпинги не вернулись сразу: Prometheus считает правила заново, дай ему минуту-две.
Сломай и почини
Сломаем алерт так, чтобы он выглядел живым, а сработать не мог.
Симптом
В rules/lab-alerts.yml ты (специально или нечаянно) написал shop_db_pool_waitng вместо shop_db_pool_waiting, перечитал правила и запустил поломку из практики 4. Очередь на lab-use растёт, а алерт LabDbPoolQueue остаётся inactive. promtool check rules показывает SUCCESS.
Гипотезы
- Prometheus не перечитал правила.
- Условие не выполняется, потому что очередь мала.
- Выражение не возвращает ряды: метрики с таким именем нет.
Проверки
Выполни выражение алерта в своём Prometheus, на http://localhost:9091/graph, или так:
curl -s localhost:9091/api/v1/query --data-urlencode 'query=shop_db_pool_waitng > 0' | jq '.data.result'
curl -s localhost:9091/api/v1/query --data-urlencode 'query=shop_db_pool_waiting > 0' | jq '.data.result | length'
Что должно получиться (пример):
[]
1
Как читать вывод: первый запрос с опечаткой вернул пустой список, второй с верным именем нашёл ряд. Значит, правила Prometheus читает (гипотеза 1 отпала), очередь есть (гипотеза 2 отпала), а у нас опечатка в имени метрики.
Исправление
Разбор
Верна гипотеза 3. Prometheus не проверяет, что метрика существует: пустой результат для него обычное дело. promtool check rules проверяет только синтаксис. Исправь имя на shop_db_pool_waiting, перечитай правила (curl -s -X POST localhost:9091/-/reload) и повтори поломку.
Правило на будущее: каждое новое выражение сначала выполни в Prometheus и убедись, что результат не пуст. Для критичных алертов напиши ещё тест promtool test rules на реальных именах метрик.
ИИ в помощь
Нейросеть быстро набросает правило и шаблон runbook, но выдумывает имена метрик и перепутывает версии синтаксиса Alertmanager. Общие правила: ИИ-помощник.
Задача: составить правило алерта.
Prometheus 3.15. Нужно правило алерта на долю ответов 5xx магазина.
Метрика: http_requests_total с метками method, route, status.
Условие: больше 5% за минуту, не меньше 1 запроса в секунду, for 1m, severity critical.
Добавь annotations summary и runbook_url. Выдай YAML для файла правил.
Проверь ответ: сверь имена с метриками стенда (Explore в Grafana или curl localhost:9091/api/v1/label/__name__/values). Выполни выражение: результат не должен быть пустым. Типичные ошибки: status="5.." вместо =~, отсутствие or vector(0), severity в аннотациях вместо меток, for внутри expr.
Задача: составить runbook.
Составь runbook для алерта «очередь за соединением с базой» (shop_db_pool_waiting больше нуля).
Структура: что значит, масштаб (запрос PromQL), проверки в формате «если A, то B»,
починка командами, как убедиться, что починено, эскалация. До 15 строк.
Проверь ответ: каждая команда должна выполняться на твоём стенде. Нейросеть любит писать «перезапусти сервис» без команды и без критерия успеха: допиши их сам.
Сначала напиши runbook сам, потом сравни с ответом нейросети: если ты не можешь объяснить пункт, которому она тебя научила, не клади его в инструкцию.
Словарик урока
| Термин | Простыми словами |
|---|---|
| Алерт (alert) | Автоматическая проверка условия, которая зовёт человека, когда плохо и долго |
| Правило алерта | Запись в YAML: alert, expr, for, метки и аннотации |
for |
Выдержка: сколько условие должно держаться подряд, чтобы алерт загорелся |
| pending, firing | Состояния: ждёт окончания for, горит и отправлен в Alertmanager |
| Метки и аннотации | Метки идут в маршруты, аннотации (summary, runbook_url) для человека |
| Alertmanager | Программа, которая группирует алерты, убирает повторы и направляет их получателям |
Группировка (group_by) |
Объединение алертов с одинаковыми значениями меток в одно уведомление |
| Маршрут (route) | Правило «алерты с такими метками отправь этому получателю»; первая подошедшая ветка |
| Получатель (receiver) | Адрес доставки: чат, почта, webhook |
| Подавление (inhibit) | Автоматическое глушение алертов-следствий, пока горит алерт-причина |
| Тишина (silence) | Ручное отключение алерта на заданное время |
| Runbook | Короткая инструкция на один алерт |
| Burn rate | Доля ошибок, делённая на бюджет ошибок: темп сгорания бюджета |
| Watchdog | Алерт, который горит всегда; его пропажа значит, что сломан сам алертинг |
| Dead man’s switch | Приём «сторож»: пока сигнал приходит, всё жив; пропажа сигнала сама тревога, её ловит внешняя проверка |
| Шторм алертов (alert storm) | Одна поломка даёт десятки уведомлений за минуты |
| Усталость от алертов (alert fatigue) | Звонков много, почти все пустые: человек перестаёт на них реагировать |
| Эскалация (escalation) | Если тревогу никто не подтвердил за заданное время, она идёт дальше: второму дежурному, потом руководителю |
| Ночные алерты | Уведомления, которые тревожат человека ночью или в выходной; их число за неделю мерило качества алертинга |
Вопросы с собеседований
Раздел для повторения: ответь вслух, потом открой ответ. Короткие вопросы с пометкой [на скорость] тренируй на время: ответ за 30 секунд.
1. [junior] [часто] Чем алерт отличается от дашборда и зачем нужны оба?
Ответ
Дашборд отвечает, когда на него смотрят: он нужен для разбора и для вопроса «где именно?». Алерт задаёт вопрос сам, круглые сутки, и зовёт человека, когда плохо и достаточно долго. Без алертов о поломке узнают от покупателей, без дашбордов дежурный получил звонок и не знает, куда смотреть.
Что хотят услышать: дашборд пассивен, алерт активен, алерт ведёт на дашборд и runbook.
Красный флаг: говорят «алерты не нужны, мы смотрим на графики».
2. [junior] [часто] Что такое выдержка for в правиле алерта и что будет без неё?
Ответ
for задаёт, сколько условие должно выполняться подряд, прежде чем алерт станет firing. До этого он в состоянии pending. Без for каждый короткий всплеск звонит дежурному: люди устают от ложных тревог и перестают на них реагировать. Цена выдержки: задержка для настоящей поломки.
Что хотят услышать: pending и firing, отсев всплесков, компромисс со скоростью.
Красный флаг: ставят for: 0s для скорости или for: 1h для тишины, не думая о цене.
3. [junior] [часто] Какие алерты должны будить человека ночью, а какие нет?
Ответ
Будить должны симптомы, которые чувствует покупатель: много ошибок, долгий ответ, сайт не отвечает. Причины (процессор, диск, очередь пула) идут предупреждением в задачу или чат на утро. Правило: если до утра нечего делать, ночью не звонят.
Что хотят услышать: симптом против причины, critical и warning, срочность равна важности.
Красный флаг: делают critical все алерты «на всякий случай».
4. [middle] [часто] Что делает Alertmanager, если Prometheus сам вычисляет алерты?
Ответ
Prometheus решает, что алерт горит, и шлёт его часто. Alertmanager убирает повторы, группирует похожие алерты в одно уведомление, направляет по получателям через маршруты, подавляет следствия и принимает тишины. Без него дежурному пришло бы по сообщению на каждый алерт каждые несколько секунд.
Что хотят услышать: дедупликация, группировка, маршруты, подавление, тишины; разделение ролей с Prometheus.
Красный флаг: считают, что Alertmanager вычисляет условия по метрикам.
5. [middle] Как работают маршруты в Alertmanager и что будет с алертом без подходящей ветки?
Ответ
Алерт идёт по веткам сверху вниз, берёт первую подошедшую по меткам (если нет continue: true). Метки сравниваются буква в букву, поэтому Critical и critical разные. Если ничего не подошло, алерт уходит получателю корня. Поэтому корень делают безопасным, например чатом, который читают.
Что хотят услышать: первая подошедшая, сравнение меток буква в букву, корень как запасной вариант, amtool config routes test.
Красный флаг: думают, что неподходящий алерт отбрасывается.
6. [middle] [на скорость] Чем подавление (inhibit) отличается от тишины (silence)?
Ответ
Подавление автоматическое: пока горит причина (например, магазин лежит), следствия (задержка, очередь) не уведомляют. Тишина ручная: человек на время гасит ожидаемый алерт, например на время работ. Тишину всегда ставят с сроком и комментарием: забытая бессрочная опаснее шума.
Что хотят услышать: автоматика против ручной, equal, срок и комментарий у тишины.
Красный флаг: ставят бессрочную тишину и забывают.
7. [junior] Что такое runbook и что в нём должно быть?
Ответ
Runbook это инструкция на один алерт, которую открывают в три часа ночи. Шесть вопросов: что значит алерт, каков масштаб для покупателей, что проверить (команды целиком), как чинить, как убедиться, что починено, кому передать, если не вышло. Ссылка на него лежит в аннотации runbook_url.
Что хотят услышать: конкретные проверки и команды, признак «починено», эскалация.
Красный флаг: пишут «проверь базу и перезапусти при необходимости».
8. [middle] Что такое burn rate и зачем два окна в алерте на сгорание бюджета?
Ответ
Burn rate это доля ошибок, делённая на бюджет: во сколько раз бюджет сгорает быстрее нормы. При цели 99% и 14,4% ошибок бюджет месяца кончится за двое суток. Два окна через and (например, час и пять минут): длинное подтверждает, что беда настоящая, короткое: что она идёт сейчас. Всплеск на четыре минуты не пробьёт часовое окно.
Что хотят услышать: burn rate как скорость, два окна против всплесков и против затихшей беды.
Красный флаг: ставят один фиксированный порог на минутные ошибки и страдают от ложных звонков.
9. [middle] [на скорость] Правило с опечаткой в имени метрики проходит promtool check rules. Почему и как ловить такое?
Ответ
check rules проверяет синтаксис и не знает, какие метрики существуют. Опечатка даёт пустой результат, и алерт молчит вечно. Ловят выполнением выражения в Prometheus (пустой ответ) и тестами promtool test rules на выдуманных рядах: они проверяют и срабатывание, и молчание.
Что хотят услышать: разница синтаксиса и смысла, unit-тесты правил, пустой вектор как сигнал.
Красный флаг: считают SUCCESS гарантией, что правило работает.
10. [middle] Как узнать, что сам алертинг сломан и молчит?
Ответ
Ставят алерт-сторож (Watchdog) с expr: vector(1): он горит всегда и уходит во внешнюю систему, которая звонит, если он пропал. Молчание алертинга иначе не отличить от «всё хорошо». Дополнительно следят за тем, что Prometheus видит все цели (up) и что Alertmanager доступен.
Что хотят услышать: постоянно горящий алерт, внешняя проверка его отсутствия, up.
Красный флаг: полагаются на то, что «если сломается, нам придёт алерт».
11. [junior] [часто] Какие алерты настроить в первую очередь на новом сервисе?
Ответ
Начать с немногих алертов на симптомы, которые чувствует пользователь: сервис недоступен (проба снаружи, up == 0), доля ошибок 5xx выше порога, задержка p95 или p99 выше цели SLO. Потом алерт на сгорание бюджета ошибок и несколько алертов «до беды»: диск заполнится через несколько часов, нет места в базе. Отдельно алерт на сам мониторинг: Prometheus или Alertmanager молчат, данные перестали приходить. К каждому алерту нужен runbook: кому он адресован и что делать. Остальное, например процессор, остаётся на дашборде и в предупреждениях без ночного звонка.
Что хотят услышать: симптомы, золотые сигналы, бюджет ошибок, алерт на молчание мониторинга, runbook.
Красный флаг: «алерт на каждую метрику, чтобы ничего не пропустить».
12. [middle] [часто] Что такое усталость от алертов и как с ней бороться?
Ответ
Звонков много, почти все пустые или без действия, и дежурный привыкает их пропускать. Тогда настоящая тревога тонет. Бороться надо в таком порядке: убрать алерты без действия (или перевести их в задачи), потом сгруппировать и подавить следствия, потом улучшить текст (симптом в summary, шаг в runbook). Мерило: сколько ночных уведомлений за неделю и по скольким из них что-то сделали.
Что хотят услышать: порядок «меньше алертов, группировка, текст», ночные алерты как метрика, эскалация как страховка.
Красный флаг: предлагают отключить уведомления или увеличить repeat_interval на всё подряд.
Проверено на версиях
Стенд «Магазин» из load-tester/project/shop: Prometheus 3.15 (prom/prometheus:v3.15.0), Alertmanager 0.34 (prom/alertmanager:v0.34.1), Python 3.14 (python:3.14.8-slim), Docker Compose v2, jq 1.7. Октябрь 2026.
Итог урока: ты умеешь
- Назвать четыре ловушки алертинга и объяснить, чем плох алерт без
forи без runbook. - Написать правило с
alert,expr,for, метками и аннотациями и объяснить состояния pending и firing. - Выбрать порог и
forпо наблюдениям, а не «на глаз», и требовать минимум трафика для долей. - Отличить алерт на симптом от алерта на причину и разделить critical и warning.
- Настроить в Alertmanager группировку, маршруты, подавление и тишину.
- Проверить маршруты через
amtool config routes test, а правила черезpromtool check rulesиpromtool test rules. - Объяснить burn rate и два окна и найти опечатку, из-за которой алерт молчит.
- Назвать первые пять алертов нового сервиса и отличить шторм алертов от усталости.
- Объяснить эскалацию, ночные алерты как метрику качества и сторож с внешней проверкой.
- Написать runbook на шесть вопросов и проверить всю цепочку на поломке оплаты.
Где это применить
- DevOps, урок 8.5: Alertmanager: те же правила и маршруты на сервисе «Заметки» с настоящими получателями.
- DevOps, урок 8.11: паттерны надёжности и burn rate: алерты по сгоранию бюджета на нескольких окнах и в Kubernetes.
- load-tester, урок 7.6: алерты и инцидент: алерты стенда и разбор первого инцидента с хронологией.
Дальше: тема 4, урок 4.1. Метрики и алерты говорят, что что-то сломалось и где. Чтобы понять почему, нужны логи: ты научишься искать в них нужное и связывать их с запросами.
Проверь себя
Короткий тест по уроку: 5 вопросов из банка в 30. Засчитывается только полностью правильный ответ, порог 60%. Каждая новая попытка даёт другие вопросы, пока банк не закончится. Ответы видны после проверки.
Тест работает с включённым JavaScript.