Мониторинг и автоматизация серверов: infra
Для разработчиков. Состояние на 28.09.2026. Подробности, переменные ролей и команды — README репозитория infra и files/heartbeat/README.md. Как считается, кто и чем пользуется, — статья «Телеметрия использования: usage_telemetry».
Коротко
Репозиторий infra — единственный источник правды о серверах: инвентарь Ansible, роли базовой настройки, стек мониторинга, правила алертов, дашборды Grafana, статус-страница и служба окон обслуживания. Главное правило проекта: изменение, сделанное мимо репозитория, не существует. Накликанное на сервере или в веб-интерфейсе затрётся следующим прогоном роли.
| Что | Где в репозитории | Снаружи |
|---|---|---|
| Prometheus, Alertmanager, blackbox_exporter, Pushgateway, Grafana | roles/monitoring_server, правила — prometheus/rules/ | Grafana — grafana.donula26.ru (вход по логину); остальное наружу не смотрит |
| Агент метрик на каждом сервере | roles/monitoring_agent | — |
| Метрики PostgreSQL | roles/postgres_exporter | — |
| Статус-страница | roles/status_page | status.donula26.ru |
| Окна обслуживания (админка и MCP) | roles/status_admin | status.donula26.ru/admin/, status.donula26.ru/mcp |
| Heartbeat периодических задач | files/heartbeat/heartbeat.py | push.donula26.ru |
| GlitchTip (ошибки приложений) | роли нет; проба и реестр проектов — docs/projects.md | monitoring.donula26.ru |
| Бэкапы баз, фаервол, базовая настройка, аудит | roles/db_backup, roles/firewall, roles/base, playbooks/audit.yml | — |
| Коллектор телеметрии использования | roles/telemetry_collector | — |
Метрики и алерты
- Prometheus опрашивает цели раз в 30 секунд и хранит данные 90 дней. Список серверов для опроса собирается из инвентаря (
inventory/hosts.yml), поэтому новый сервер появляется в мониторинге только после прогонаmonitoring_server. - Сервер изнутри — node_exporter с TLS и basic auth, плюс скрипты для textfile collector: состояние контейнеров (статус, число перезапусков, healthcheck, время старта), метрики хоста, возраст свежих бэкапов. cAdvisor не используется, поэтому CPU и памяти по отдельным контейнерам в метриках нет.
- Снаружи — blackbox_exporter проверяет публичные адреса из
monitoring_probe_targets(inventory/group_vars/all/main.yml) и срок действия TLS-сертификатов. По умолчанию модульhttp_2xx, для остальных случаев модуль задаётся явно. Пробы бьют в/health, где он есть. - Базы — postgres_exporter у
amo_dbи у баз мобильного оператора: соединения, архивация WAL, отставание синхронизации. - Правила —
prometheus/rules/*.yml:hosts,containers,blackbox,backups,amo_db,umobile,contur_transcribe,meta(сам мониторинг и heartbeat). В Grafana алерты не заводятся.
Alertmanager шлёт уведомления в Telegram через прокси (напрямую api.telegram.org недоступен), в два чата:
| Алерт | Куда и когда |
|---|---|
severity: critical | В критический чат сразу, с 08:00 до 23:00 по Москве. Если сработал ночью, придёт в 08:00, при условии что проблема ещё держится. Повтор раз в час. |
severity: warning + delivery: prompt | В тот же чат, днём сразу, ночью придерживается до утра. Повтор раз в 6 часов. Метка для того, что не срочно, но нужно узнать в тот же день. |
Остальные warning и info | Утренняя сводка: одно сообщение в окне 08:00–08:20. |
Подавления: при HostDown молчит всё, что есть на этом хосте. При DockerDown и DockerMetricsStale молчат контейнерные алерты. При AmoDbPostgresDown молчат производные проверки amo_db. При DiskUsageCritical молчат предупреждения о том же диске. Истёкшие тишины Alertmanager хранит 30 дней: это журнал объявленных работ.
Дашборды Grafana лежат в папке «Инфраструктура»: «Инфраструктура: серверы», «Инфраструктура: контейнеры», «Доступность снаружи», «Периодические задачи», «amo_db», «Использование приложений». Их JSON генерируется скриптами generate.py и generate_usage.py в roles/monitoring_server/files/dashboards/.
Правка дашборда в браузере не сохранится: включено allowUiUpdates: false. Дашборды меняют в генераторе, потом прогоняют роль.
GlitchTip
GlitchTip на monitoring.donula26.ru собирает исключения приложений через SENTRY_DSN. Своя запись в нём есть у каждого нашего проекта. Одной переменной SENTRY_DSN мало: в коде нужны зависимость sentry-sdk и вызов инициализации. Проще всего взять init_sentry() из пакета usage_telemetry: install_telemetry() вызывает её сам.
- Об ошибке в чат приходит уведомление, когда одна и та же ошибка случилась пять раз за десять минут. Разовые ошибки видны только в интерфейсе GlitchTip.
- Uptime-мониторы в GlitchTip есть, но уведомления о доступности от них выключены с 18.09.2026. О том, что сервис недоступен, сообщает только Alertmanager, чтобы одно событие не приходило двумя сообщениями.
- Роли Ansible для GlitchTip нет: проекты и правила уведомлений заводятся в его интерфейсе. Сам GlitchTip проверяется внешней пробой. Эндпоинта здоровья у него нет, поэтому проба смотрит корень сайта.
Статус-страница
status.donula26.ru открыта без авторизации и сделана для сотрудников. Это не Uptime Kuma и не отдельный монитор: страницу генерирует скрипт roles/status_page/files/generate.py. Он запускается по таймеру раз в минуту, берёт из Prometheus ту же метрику probe_success, на которой построены алерты, и пишет статический HTML. Поэтому страница не может показывать не то, что пишут в чат алерты.
Список сервисов задаётся в status_page_services. Поля: probe — адрес пробы, как в monitoring_probe_targets; title — название, понятное сотруднику; visible — показывать ли на странице (инфраструктурные сервисы проверяются, но скрыты); project и job на странице не видны, по ним окно обслуживания глушит алерты. Внутренние имена, адреса и версии на страницу не попадают.
| Состояние | Когда |
|---|---|
| Работает | проба успешна, и за сутки вне окон обслуживания сбоев не было |
| С перебоями | сейчас отвечает, но за сутки были сбои; или падает, но меньше двух минут подряд |
| Не работает | все пробы за последние 2 минуты неуспешны (status_page_down_window) |
| На обслуживании | сейчас идёт объявленное окно; перекрывает все остальные состояния |
| Нет данных | по пробе нет метрик, например недоступен Prometheus |
У каждого сервиса есть полоса за сутки из 48 получасовых сегментов и процент доступности за сутки. Минуты внутри окон обслуживания в процент не входят.
Окна обслуживания
Любой деплой, обновление или фикс сервиса, у которого есть внешняя проба, идёт в окне обслуживания. Одна запись об окне делает три вещи:
- на статус-странице сервис становится синим, с бейджем «На обслуживании», а причина работ видна в подсказке к сегменту;
- его сбои во время окна не портят процент за сутки;
- в Alertmanager заводятся тишины на все имена сервиса:
instance(проба),project(контейнеры) иjob(отметки heartbeat). Алерты по сервису в чат не приходят.
Порядок работ: открыть окно, выкатить и проверить, сразу закрыть окно. Пока окно открыто, мониторинг сервиса слепой, поэтому лучше открыть короткое окно и продлить его, чем забыть длинное. Причину пишут человеческим языком, её видит любой сотрудник: «Обновление дашборда», а не «деплой v2.3.1».
Через админку
- Открыть status.donula26.ru/admin/. Вход по именному логину: имя вошедшего записывается автором окна.
- Выбрать «Сервис», заполнить «Причина», проверить «Начало» и «Конец». По умолчанию окно идёт от текущего момента на 30 минут.
- Нажать «Объявить работы».
- После работ нажать «Закончить сейчас» в строке окна. Конец окна переносится на текущий момент, тишины снимаются, запись остаётся в истории.
Кнопка «Убрать» удаляет запись целиком, вместе с синим сегментом. Она нужна только для окон, заведённых по ошибке. В таблице видны окна за последние трое суток и все предстоящие.
Через MCP
Та же служба отвечает по MCP на https://status.donula26.ru/mcp. Имя сервера — status-maintenance, авторизация — заголовок Authorization: Bearer <токен>. Токен у каждого разработчика свой. Пример подключения в Claude Code:
claude mcp add --transport http status-maintenance https://status.donula26.ru/mcp \
--header "Authorization: Bearer $STATUS_MCP_TOKEN"
| Инструмент | Параметры | Что делает |
|---|---|---|
list_maintenance | hours (по умолчанию 72) | идущие, предстоящие и недавние окна с их id |
open_maintenance | service, minutes (не больше 120), reason, необязательный start в ISO по Москве | открывает окно; по умолчанию оно начинается сейчас |
close_maintenance | id | закрывает окно сейчас и снимает тишины |
- В
serviceпередают название со статус-страницы или адрес пробы. Если название подходит нескольким сервисам (например, «МПП2» — и дашборду, и виджету), служба возвращает ошибку со списком подходящих, а не выбирает первый. - Автором окна записывается «помощник <владелец токена>».
- Окно длиннее двух часов через MCP не открыть: такие работы объявляются вручную в админке.
Доступы задаются в Ansible Vault: status_admin_web_users (логины админки) и status_admin_mcp_tokens (токены MCP), затем прогоняется playbooks/status_admin.yml. Чтобы отозвать доступ у одного человека, его строку удаляют и снова прогоняют роль. Остальные доступы продолжают работать.
Heartbeat
Проблема: Celery Beat или APScheduler умирают, не роняя контейнер. Процесс жив, healthcheck зелёный, проба отвечает 200, а задача не запускалась третьи сутки. Такое видит только сама задача, поэтому после каждого успешного прогона она отправляет отметку в Pushgateway.
- Подключение. Скопировать
files/heartbeat/heartbeat.pyв проект (зависимостей нет) и вызвать одну функцию:install_celery(celery_app, job="…"),install_apscheduler(scheduler, job="…")илиping(job=…, task=…, max_age_seconds=…)для разовой отметки. Сейчас так подключены шесть проектов. - Переменные:
HEARTBEAT_URL(https://push.donula26.ru; пустое значение выключает отправку — так работает локальная разработка),HEARTBEAT_USER,HEARTBEAT_PASSWORD(лежит в Vault),HEARTBEAT_INSTANCE(имя источника; для контейнеров его задают явно). - Порог задача объявляет сама метрикой
heartbeat_max_age_seconds. По умолчанию это полтора интервала расписания плюс пять минут, отдельные задачи переопределяют его черезmax_age_overrides. ПравилоHeartbeatMissing(critical) срабатывает, если отметки нет дольше порога. - Отметку отправляет воркер после успешного выполнения. Упавшая задача отметку не обновляет, и молчание становится сигналом. Ошибки отправки пишутся в лог и не роняют задачу.
Ansible: как выкатывать
- Запуск — с ноутбука разработчика по ssh, Ansible стоит в
.venvвнутри проекта (pip install -r requirements.txt). Секреты лежат вinventory/group_vars/all/vault.ymlпод Ansible Vault. Пароль от хранилища — в файле.vault_pass, в git он не попадает, копия есть в VaultWarden. - Порядок любого прогона:
--check --diff, затем--limitна одном хосте, затем на всех. - Новый сервер: завести в
inventory/hosts.yml, затем прогнатьbase→firewall→monitoring_agent→monitoring_server. Если фаервол настроить после агента, агент не создаст правило для своего порта. - Службу окон обслуживания ставят до nginx: сначала
playbooks/status_admin.yml, потомplaybooks/web_proxy.yml. playbooks/audit.ymlтолько читает: что запущено на серверах и что требует внимания. Результат попадает вreports/, этот каталог в git не коммитится.- Работы на сервисе с пробой проводятся в окне обслуживания, см. выше.
На что наступают
- У сервиса три имени, тишина нужна на каждое. Если у сервиса в
status_page_servicesне заполненыprojectиjob, окно заглушит только пробу, а контейнеры и heartbeat продолжат писать в чат. Лишнее имя тоже вредно: чужойprojectзаглушит соседний сервис. Заполняют только то, что сверено с метками в Prometheus. - Сервиса нет в пробах — окно открыть не на что. Сначала сервис заводят в
monitoring_probe_targetsиstatus_page_services, только потом объявляют для него работы. - Окно, заведённое задним числом, красит историю, но тишины не создаёт: Alertmanager не принимает тишины в прошлом.
- Обслуживание не прячет соседнюю аварию. Сегмент на полосе синеет, только если все неудачные минуты получаса попали внутрь окна.
- Heartbeat после деплоя молчит до первого прогона. Правило отсчитывает возраст от более позднего из двух событий: последней отметки или последнего старта контейнеров проекта. Это работает, только если
jobотметок совпадает с именем compose-проекта. Проект с другим именем заалертит сразу после деплоя. - Переехал проект — поправьте
HEARTBEAT_INSTANCEи удалите старую группу из Pushgateway. То же с задачей, убранной из расписания: Pushgateway помнит отметки бесконечно, и старая группа будет держать алерт. - Порог heartbeat считается при старте процесса. Новое расписание подхватится только после перезапуска контейнера.
- Лимит docker-логов не действует сразу. Роль
baseпишетdaemon.json, но нужен полный перезапуск docker (в окне обслуживания) и пересоздание контейнеров. - Docker публикует порты в обход ufw. Роль
firewallзакрывает этот обход. Порт контейнера, который должен смотреть наружу, открывают маршрутным правилом черезfirewall_route_rules. В файлах правил ufw пишите комментарии только латиницей, иначе ufw падает. - Ansible ставят только в
.venv. Фильтрpassword_hashне работает (passlibнесовместим сbcrypt 5), поэтому bcrypt-хеши хранятся в Vault готовыми. --checkна чистой машине падает на первой же задачеapt: нетpython3-apt. Первый прогон на пустом сервере делается сразу боевым.- С ноутбука ssh может прийти не на тот сервер: локальный прокси подменяет адрес. Признаки — «Permission denied» с рабочим ключом и предупреждение о смене ключа хоста. Сервер проверяют по
hostname, а не по тому, что подключение прошло. - Хост вне инвентаря не мониторится. Инвентарь пополняется по мере того, как доступы переносятся в VaultWarden.
Нет комментариев для отображения
Нет комментариев для отображения