Мониторинг стоял на том же сервере и умер вместе с ним
Сервер упал в пятницу вечером, а команда узнала об этом не от Prometheus и не от Telegram-бота, а от клиента, который написал в поддержку: «у вас всё лежит». Дашборды показывали зелёный статус, алерты молчали — просто потому, что сам мониторинг жил на том же хосте, что и всё остальное, и умер в ту же секунду, что и прод. Ниже — как это выяснилось, какие версии отбросили по пути и что в итоге поменяли в архитектуре, чтобы больше не полагаться на удачу.
Содержание
Что случилось
Небольшой сервис (интернет-магазин на связке nginx + PHP-FPM + PostgreSQL) жил на одном VPS. На этом же VPS, в соседних контейнерах docker-compose, крутился весь стек наблюдения: Prometheus, Grafana, Alertmanager и бот, который слал уведомления в Telegram-чат команды. Экономия ресурсов казалась разумной: зачем платить за второй сервер ради мониторинга, если первого хватает с запасом.
В 19:42 по логам хостинг-провайдера гипервизор, на котором находилась виртуалка, ушёл в перезагрузку из-за аппаратного сбоя на хост-ноде (отказал один из дисков в RAID-массиве хранилища, и планировщик миграций не успел перенести машину живой). VPS был недоступен около полутора часов — сеть просто не отвечала, ни ssh, ни http, ничего.
Проблема была не в самом падении — от аппаратных сбоев не застрахован никто, для этого и существуют мониторинг и алерт при падении сайта. Проблема в том, что об этом никто не узнал вовремя. Alertmanager должен был увидеть недоступность up{job="nginx"} == 0, отправить правило в Telegram и разбудить дежурного. Ничего этого не произошло, потому что сам Alertmanager лежал в той же самой недоступной виртуалке.
Что видели в логах и метриках
Когда сервис подняли обратно вручную (через панель хостинга, форс-перезапуском VM), первым делом посмотрели, что вообще происходило с точки зрения мониторинга. И тут началось самое интересное — с точки зрения Prometheus не происходило вообще ничего:
# на восстановленном сервере
journalctl -u docker --since "19:30" --until "21:30" | grep -i prometheus
# пусто — контейнер prometheus просто не работал, писать было нечему
docker compose ps
# NAME STATUS
# prometheus Exited (137) 90 minutes ago
# alertmanager Exited (137) 90 minutes ago
# grafana Exited (137) 90 minutes ago
# tg-notifier Exited (137) 90 minutes ago
Код выхода 137 — это SIGKILL, что ожидаемо при жёстком отключении хоста. Никакой аномалии в контейнерах не было: они не упали из-за бага, они умерли вместе с хостовой ОС, потому что физически исполнялись на ней же.
Посмотрели логи Grafana — там просто обрыв временных рядов ровно на отметке 19:42, дальше пустота до момента, когда контейнеры подняли обратно (docker-compose с restart: unless-stopped поднял их автоматически после старта VM, но это случилось только когда провайдер сам восстановил ноду).
Проверили историю Alertmanager через API:
curl -s http://localhost:9093/api/v2/alerts | jq '.[].status'
# после восстановления там появился один алерт InstanceDown,
# созданный уже ПОСЛЕ того как prometheus снова начал скрейпить —
# то есть алерт зафиксировал факт, что сервис лежал,
# но отправить его было некому, потому что Alertmanager
# в момент самого падения тоже не работал
Формально мониторинг «увидел» проблему только после того, как проблема сама себя решила — сервер перезагрузился, контейнеры поднялись, и Prometheus честно отчитался, что минуту назад всё было плохо. Это как пожарная сигнализация, которая рапортует о пожаре после того, как он потушен.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверКакие гипотезы проверили и отбросили
Прежде чем понять реальную причину, проверили три версии, которые казались правдоподобнее.
Версия 1: сломалось правило алерта. Первая мысль — где-то в alert.rules.yml опечатка, и правило InstanceDown не сработало вовремя. Проверили конфиг построчно:
groups:
- name: instance
rules:
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} down"
Правило было корректным и триггернулось бы за минуту — но только если бы сам Alertmanager в этот момент работал и было кому его обработать. Версию отбросили: дело не в логике правила.
Версия 2: истёк токен Telegram-бота. Проверили tg-notifier — токен был живой, бот отвечал на getMe, тестовое сообщение через curl доходило до чата без проблем:
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d chat_id="$CHAT_ID" -d text="test ok"
# {"ok":true, ...}
Токен ни при чём — версию отбросили за пять минут.
Версия 3: провайдер зарезал исходящий трафик или заблокировал webhook. Проверили правила firewall на VPS и историю инцидентов у хостера — никаких блокировок исходящих соединений не было, DNS резолвился нормально, TLS-хендшейк с api.telegram.org проходил штатно в обычное время. Проблема была не в сети и не в правах — версию тоже отбросили.
Все три гипотезы объединяло одно: они предполагали, что мониторинг работал, но что-то мешало ему достучаться наружу. На деле мониторинг не работал вообще, потому что физически не существовал в момент сбоя — ни как процесс, ни как виртуальная машина.
В чём была реальная причина
Единая точка отказа (single point of failure): наблюдатель и наблюдаемое находились на одном физическом хосте. Когда упал гипервизор, вместе с продовым сервисом легла и вся система, которая должна была об этом сообщить. Это классическая, хорошо описанная в SRE-литературе ошибка — тот же антипаттерн мониторинга, который никто не смотрит, только в его архитектурной форме: систему поставили «до кучи» на единственный доступный сервер, а не спроектировали как отдельный контур.
Дополнительно вскрылось два усугубляющих фактора:
- Не было внешнего дозора (dead man's switch). Никто не проверял снаружи, жив ли сам мониторинг. Классическая связка — внешний сервис вроде healthchecks.io или UptimeRobot, которому Prometheus или cron периодически шлют пинг «я жив», и если пинг не пришёл вовремя, сработает отдельный алерт «мониторинг молчит». Такого дозора не было вообще.
- Уведомления шли в один канал без резерва. Даже если бы Alertmanager как-то дожил до отправки, единственным получателем был Telegram-бот с одним chat_id. Ни SMS, ни звонка, ни второго канала на случай, если у Telegram проблемы или у дежурного отключены уведомления.
Итог: сбой длился около полутора часов простоя сервиса плюс ещё примерно 45 минут до того, как о нём узнали — уже от клиента, а не от собственной системы наблюдения. Совокупно почти два с половиной часа сервис был недоступен без осознанной реакции команды.
Что изменили после инцидента
Первое и главное решение — разнести мониторинг и то, что он проверяет, физически. Стек Prometheus + Alertmanager + Grafana переехал на отдельный сервер, в другом регионе, у другого поставщика инфраструктуры, чем прод. Идея простая: если оба провайдера упадут одновременно — это уже совсем другой уровень катастрофы, а от рядового сбоя одной ноды такая схема защищает надёжно.
# prometheus.yml на ВЫНЕСЕННОМ сервере мониторинга
scrape_configs:
- job_name: 'prod-app'
scrape_interval: 15s
static_configs:
- targets: ['prod.example.com:9100']
scheme: https
tls_config:
insecure_skip_verify: false
Второе — добавили внешний dead man's switch. Отдельный лёгкий пинг-чек раз в минуту сообщает внешнему сервису, что сам стек мониторинга жив:
# crontab на сервере мониторинга
* * * * * curl -fsS -m 10 --retry 3 https://hc-ping.com/xxxxxxxx-xxxx-xxxx > /dev/null
Если пинг не приходит дольше заданного окна (например, 3 минут), внешний сервис сам шлёт алерт на резервный канал — независимо от того, жив ли Prometheus, жив ли Alertmanager и жив ли сервер мониторинга целиком.
Третье — развели каналы уведомлений. Критичные алерты (severity: critical) теперь уходят не только в Telegram, но и продублированы через отдельный канал с другой инфраструктурой доставки, чтобы отказ одного канала не значил тишину полностью:
# alertmanager.yml, фрагмент маршрутизации
route:
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'critical-multi-channel'
continue: true
receivers:
- name: 'critical-multi-channel'
telegram_configs:
- bot_token: '$TG_TOKEN'
chat_id: -1000000000000
webhook_configs:
- url: 'https://backup-notify.example.com/hook'
Четвёртое — прописали в рантайм-книге (runbook) явное правило: любой новый сервис или инструмент наблюдения по умолчанию ставится не на прод-хост, а на выделенный узел мониторинга, и это отдельная строка в чек-листе перед деплоем нового проекта. Заодно пересмотрели и то, как настроен сам стек — по той же схеме, что описана в статье про установку и настройку Uptime Kuma на VPS, но уже на отдельном сервере.
Как проверить, что у вас настроено так же
Небольшой чек-лист для самодиагностики — если хотя бы один пункт про вас, стоит пересобрать схему до того, как это сделает за вас авария:
- Prometheus/Grafana/Zabbix/Alertmanager стоят на той же машине (или в той же виртуальной сети без внешнего резерва), что и продовые сервисы.
- Нет ни одного внешнего чекера, который следит именно за тем, жив ли сам мониторинг, а не только прод.
- Уведомления идут в единственный канал без альтернативного маршрута доставки.
- Никто никогда не тестировал сценарий «что если упадёт весь хост целиком, а не один процесс».
- Дежурный узнаёт об инцидентах из жалоб пользователей чаще, чем из алертов.
Сравнение двух схем размещения:
| Параметр | Мониторинг на проде | Мониторинг вынесен отдельно |
|---|---|---|
| Стоимость | Ниже (одна машина) | Выше (нужен второй хост) |
| Видимость при падении хоста | Отсутствует | Полная |
| Точка отказа | Общая с продом | Независимая |
| Нужен ли dead man's switch | Всё равно нужен | Обязателен как второй уровень |
| Сложность настройки | Проще | Требует сети между хостами/VPN |
Разнесение стоит денег — второй сервер, пусть и небольшой, это дополнительная статья расходов, и здесь стоит учесть тот же урок, что и в истории про резервный сервер, который не включился, когда понадобился: резерв, который не проверяли вживую, работает не лучше, чем его отсутствие. Но сравнивать нужно не с нулём, а с ценой простоя без вовремя полученного алерта: в этом случае цена — почти два с половиной часа недоступности сервиса и доверие клиента, который узнал о проблеме раньше службы поддержки.
Для мониторинга отдельный сервер не обязан быть мощным — небольшого VPS с 2 ядрами и парой гигабайт памяти достаточно для Prometheus, Grafana и Alertmanager на нагрузке в несколько десятков целей скрейпинга. Часто такой узел разумно ставить в другом регионе, чем прод, чтобы не зависеть от общей сетевой инфраструктуры одного дата-центра.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Обязательно ли выносить мониторинг на отдельный физический сервер, или хватит другой виртуалки у того же провайдера?
Отдельная VM снижает риск, но не убирает его полностью — при сбое на уровне дата-центра или сети провайдера могут упасть обе машины одновременно. Более надёжный вариант — другой провайдер или хотя бы другой регион/зона доступности того же провайдера.
Что делать, если бюджет не позволяет держать второй сервер только под мониторинг?
Как минимум настройте внешний dead man's switch вроде healthchecks.io или UptimeRobot бесплатного тарифа — он не заменит полноценный вынесенный стек, но хотя бы предупредит, если основной мониторинг замолчал вместе с сервером.
Как проверить, что новая схема действительно работает, а не только выглядит правильно на бумаге?
Проведите учения: остановите прод-сервер (в тестовое окно, предупредив команду) и засеките, придёт ли алерт с вынесенного мониторинга и сработает ли dead man's switch, если остановить заодно и сам стек наблюдения.
Нужно ли резервировать сам сервер мониторинга, если он уже вынесен отдельно?
Для небольших проектов обычно достаточно связки «вынесенный мониторинг плюс внешний дозор» — это уже два независимых уровня. Полное резервирование самого мониторинга (второй Prometheus в active-passive) имеет смысл для крупных инфраструктур, где простой наблюдения на несколько минут критичен сам по себе.
Как быть с алертами, если сеть между продом и сервером мониторинга ляжет, а обе машины будут живы?
Именно для этого случая и нужен dead man's switch с внешней стороны: он не проверяет прод напрямую, а проверяет, доходят ли пинги от мониторинга наружу, — если связь между сегментами оборвалась, вы узнаете об этом отдельно от падения самого сервера.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →