Перейти к основному контенту

Мониторинг и автоматизация серверов: infra

Для разработчиков. Состояние на 28.09.2026. Подробности, переменные ролей и команды — README репозитория infra и files/heartbeat/README.md. Как считается, кто и чем пользуется, — статья «Телеметрия использования: usage_telemetry».

Коротко

Репозиторий infra — единственный источник правды о серверах: инвентарь Ansible, роли базовой настройки, стек мониторинга, правила алертов, дашборды Grafana, статус-страница и служба окон обслуживания. Главное правило проекта: изменение, сделанное мимо репозитория, не существует. Накликанное на сервере или в веб-интерфейсе затрётся следующим прогоном роли.

ЧтоГде в репозиторииСнаружи
Prometheus, Alertmanager, blackbox_exporter, Pushgateway, Grafanaroles/monitoring_server, правила — prometheus/rules/Grafana — grafana.donula26.ru (вход по логину); остальное наружу не смотрит
Агент метрик на каждом сервереroles/monitoring_agent—
Метрики PostgreSQLroles/postgres_exporter—
Статус-страницаroles/status_pagestatus.donula26.ru
Окна обслуживания (админка и MCP)roles/status_adminstatus.donula26.ru/admin/, status.donula26.ru/mcp
Heartbeat периодических задачfiles/heartbeat/heartbeat.pypush.donula26.ru
GlitchTip (ошибки приложений)роли нет; проба и реестр проектов — docs/projects.mdmonitoring.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».

Через админку

  1. Открыть status.donula26.ru/admin/. Вход по именному логину: имя вошедшего записывается автором окна.
  2. Выбрать «Сервис», заполнить «Причина», проверить «Начало» и «Конец». По умолчанию окно идёт от текущего момента на 30 минут.
  3. Нажать «Объявить работы».
  4. После работ нажать «Закончить сейчас» в строке окна. Конец окна переносится на текущий момент, тишины снимаются, запись остаётся в истории.

Кнопка «Убрать» удаляет запись целиком, вместе с синим сегментом. Она нужна только для окон, заведённых по ошибке. В таблице видны окна за последние трое суток и все предстоящие.

Через 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_maintenancehours (по умолчанию 72)идущие, предстоящие и недавние окна с их id
open_maintenanceservice, minutes (не больше 120), reason, необязательный start в ISO по Москвеоткрывает окно; по умолчанию оно начинается сейчас
close_maintenanceidзакрывает окно сейчас и снимает тишины
  • В 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.