Алерты, которые не могут сработать: слепой ServiceMonitor и мёртвый канал доставки
Пир Vault 32 часа был sealed, а секреты неделю не синхронизировались — при алерте VaultSealed в репозитории и здоровом Alertmanager. Инцидент спрятали два независимых сбоя.
В Optimacros пир Vault 32 часа простоял в состоянии sealed, а 53 из 55 объектов VaultStaticSecret больше недели не синхронизировались. Алертов не было никому. При этом алерт VaultSealed лежал в репозитории, Prometheus был здоров, Alertmanager работал.
Друг на друга наложились два независимых сбоя, и любого одного хватило бы, чтобы спрятать инцидент.
Сбой первый: алерт был слеп по построению
Helm-чарт Vault ставит ServiceMonitor, который выбирает поды с меткой vault-active: "true". В трёхнодовом Vault это ровно один под — активный лидер. А активный под по определению распечатан: запечатанная нода не может быть лидером Raft.
Получается, Prometheus скрейпил только тот под, который никогда не мог оказаться sealed. Резервные пиры — как раз те, что возвращаются запечатанными после рестарта, — не скрейпились вовсе. Выражению vault_core_unsealed == 0 было не на чем вычисляться.
Ирония в том, что метрика была доступна. Неаутентифицированный доступ к метрикам был включён, и запечатанный под спокойно бы её отдал. Повязкой на глазах был селектор, а не экспортер.
Сбой второй: доставка два дня была мертва
Каждое уведомление Alertmanager падало с connect: connection timed out. Хост API Telegram из этого кластера резолвился в адрес, который был недоступен, а другой адрес дата-центра Telegram отвечал нормально и с валидным сертификатом. Общий выход наружу работал — другие внешние сервисы отвечали, — значит, это была блокировка конкретного адреса, а не политика файрвола. Небольшой статус-бот на том же API тоже молча падал.
Алерты срабатывали. Они просто не покидали кластер.
Исправление
- Направить ServiceMonitor на все пиры: поменять селектор на метку headless-сервиса, за которым стоят все поды Vault. Чарт позволяет это через values, патчить шаблоны не нужно.
- Добавить алерт по истине, независимой от телеметрии Vault: запечатанный под никогда не бывает
Ready, а kube-state-metrics всегда отдаёт готовность. Алерт наkube_pod_status_ready == 0для подов Vault ловит seal, даже когда собственных метрик Vault нет. - Алертить на отсутствие:
absent(vault_core_unsealed). Правило вида== 0никогда не сработает на ряд, которого нет. - Восстановить доставку: закрепить доступный адрес Telegram через
hostAliasesв Alertmanager и в статус-боте.
Неудобные оговорки
- Закрепление IP — обходной путь. Если заблокируют и этот адрес, доставка снова молча умрёт. Надёжный ответ — второй, независимый канал (почта для критических алертов) или egress-прокси.
AlertmanagerFailedToSendAlertsсуществует, но доставляется через тот же сломанный канал. Этот класс отказов ловит только dead man’s switch — внешний получатель, который ждёт регулярный heartbeat и поднимает шум, когда тот пропадает.- Клиентская библиотека Telegram писала в лог полный URL запроса вместе с токеном бота открытым текстом. Любой с доступом к логам владел ботом. Токен мы перевыпустили.
Выводы
metric == 0слеп к отсутствию метрики. Пишите отдельный алерт на отсутствие.- Селектор целей может тихо исключить ровно те экземпляры, которые ломаются. Сверяйте число реально скрейпящихся целей с числом экземпляров.
- Предпочитайте истину самооценке. Сломанный компонент хуже всех способен сообщить, что он сломан.
- Проверяйте путь доставки, а не только правило. Сработавший алерт, который не покинул кластер, неотличим от отсутствия алерта.