Три отказа алертинга из одного корня: имя там, где должен быть ID
Кто-то переименовал канал в Slack, и доставка алертов продакшен-системы молча умерла. Это был третий отказ на одном и том же идентификаторе — все прошлые фиксы меняли транспорт и оставляли имя.
В Gambchamp доставка алертов для продакшен-системы уровня T0 была полностью мертва, и ничто об этом не говорило. Кто-то переименовал канал в Slack. Alertmanager продолжал слать в старое имя, а Slack каждые десять секунд отвечал channel_not_found. Всё это время в очереди висели два живых алерта. Единственным внешним признаком был alertmanager_notifications_failed_total — счётчик, на который никто не смотрел.
Третий раз на том же идентификаторе
Это был третий отказ подряд на одном и том же:
- сначала общий incoming webhook игнорировал поле
channel, и алерты падали в общий канал. Фикс — отдельный Alertmanager; - затем bot-токен уважал
channel, и доставка заработала — но была привязана к имени канала. Выглядело решённым; - наконец, канал переименовали, и доставка умерла.
Каждый фикс менял транспорт и не трогал привязку, поэтому класс отказа пережил все ремонты.
Ярлык — не идентификатор
Имя канала в Slack — человеческий ярлык: владелец может поменять его когда угодно, никого не предупредив. ID канала выдаётся один раз и не меняется никогда — ни при переименовании, ни при смене приватности. Наш конфиг ссылался на ярлык.
Тихим, а не громким отказ сделали две вещи. Slack помечает channel_not_found как неисправимую ошибку, поэтому Alertmanager не ретраит: уведомления выбрасываются, а не откладываются. И мёртвый путь доставки выглядит ровно как спокойная система. Ничего не срабатывает, когда ничто не может сработать.
Фикс
- Закрепить канал по ID в receiver’е. Канал приватный, а у бота нет права его перечислять, поэтому API не может проверить ID в режиме только чтения. Я проверил его отложенным сообщением, которое удалил до того, как оно было доставлено, — проба, которая разрешает ID, ничего не публикуя.
- Маршрутизировать по явному лейблу, а не по имени. Когда алерты той же системы разделили на прикладной и инфраструктурный каналы, очевидный regex по префиксу имени алерта отправил бы пять guardrail’ов RDS — слоты репликации, хранилище — в прикладной канал. Лейбл
signal, выставленный в группе правил, позволяет каждому правилу самому объявить свой слой, а не зависеть от того, как его кто-то назвал.
После изменения я проверил, что конфигурация действительно перечитана, а не просто синхронизирована, и смотрел на производную счётчиков: неудачные уведомления — в ноль, отправленные — растут. Накопительные счётчики сами по себе ничего не говорят.
Выводы
- Всё, что человек может переименовать, — ярлык, а не идентификатор. Привязывайтесь к неизменяемому хэндлу — ID — или к явному объявлению, например к лейблу, который автор ставит намеренно.
- Тестируйте путь доставки, а не только компонент. Здоровый Alertmanager, который не может доставить, неотличим от отсутствия алертов.
- Заведите алерт на
increase(alertmanager_notifications_failed_total[5m]) > 0. Это самый дешёвый dead-man switch, какой вы когда-либо добавите. - Проверьте тот же класс в других местах: consumer group ID, собранные из имён подов, экспортеры с фильтром по соглашению об именах, политики, матчащие объекты по имени.