Skip to content

ТЗ — Мониторинг/health-check (cms/health)

Слой: 🔵 инфра-модуль (обязательный в managed-парке) · Зрелость доноров: ★★ · Донор: er (SEO health), catalog Статус: ТЗ к разработке

Назначение и возможности

Централизованный health-check сайта на базе spatie/laravel-health: агрегирует системные проверки (БД, Redis, очереди, диск, cron) и health-классы, которые объявляют о себе модули через манифест. Используется как гейт процедуры cms:upgrade и источник алертов.

Граница ядро/модуль (core-criteria §10): маршрут /api/v1/system/health и таймауты чеков — контракт ядра (жив и без установленного модуля); этот модуль поставляет содержимое агрегата — системные health-классы, дашборд, историю, алерты и эскалацию в cms/fleet-dashboard. Таймауты — два контура (решение 14.07.2026, противоречия нет): синхронный/api/v1/system/health держит бюджет ядра «≤ 2 с, чек ≤ 500 мс» (core: производительность) — эндпоинт отдаёт закешированные результаты последнего прогона, а чек, не уложившийся в 500 мс на месте, возвращает прошлый статус с пометкой stale; фоновый прогон по расписанию (где живут медленные проверки внешних API) работает с health.check_timeout_seconds (дефолт 10 с) и обновляет кеш результатов.

  • Проверки: подключение БД, Redis, длина очередей Horizon, свободное место на диске, факт выполнения cron
  • Сбор health-классов из extra.cms.health каждого установленного модуля
  • Единый дашборд статусов в Filament (зелёный/жёлтый/красный)
  • Публичный endpoint для внешнего аптайм-мониторинга под токеном
  • Гейт для cms:upgrade — апгрейд не стартует при красном статусе
  • Алерты об ухудшении статуса через cms/notifications-bus
  • История статусов с таймлайном деградаций

Зависимости и выключение

requires: ядро · suggests: cms/notifications-bus

Модуль обязателен в managed-парке (§3 стандарта, подраздел «Обязательные модули managed-парка», наряду с cms/updates, cms/sentry, cms/backup, cms/attack-monitor): disable недоступен клиентским ролям — доступен только studio-роли, с обязательным подтверждением и записью в аудит; кнопка выключения в админке клиента показывает «модуль обязателен по условиям поддержки». На standalone-инсталляциях выключение доступно клиентским ролям свободнее — паттерн идентичен cms/updates. Правило «выключение = деградация» при этом не отменяется ни в одном из режимов: health-чеки не выполняются, cms:upgrade пропускает гейт с предупреждением в лог — само приложение продолжает работать, деградирует только наблюдаемость. См. также ревизию контрактов 14.07.2026.

Внешних платных API модуль не вызывает — все проверки внутренние (БД, Redis, очередь, диск, cron), критерий матрицы «стоимость внешних API» не применим.

Модель данных

ТаблицаКлючевые поляПримечание
cms_health_check_resultsid, check_name, status, notification_message, meta (json), ended_atснимки последних результатов проверок
cms_health_historyid, check_name, status, checked_atистория статусов для таймлайна

Заметки: status — PHP Enum с касом 'string', не строка; meta — JSON с касом 'array'; индекс по (check_name, checked_at) в cms_health_history под таймлайн-запросы и BRIN по checked_at при росте истории. Записи cms_health_history старше health.history_retention_days удаляются штатной командой ретеншна по расписанию — журнал не подлежит ручной чистке из UI.

ПДн-паспорт: ПДн не храню — обе таблицы содержат только технические метрики проверок (имя чека, статус, meta диагностики вроде длины очереди или процента диска, временные метки); субъект персональных данных в них не идентифицируется — регистрировать обработчик в PrivacyRegistry (cms:privacy:export/forget, п. 15 ревизии ядра) для таблиц этого модуля незачем.

Входные и выходные данные

Входы

ИсточникДанные/поляЧем валидируется
Filament: «Перезапустить проверки» / POST /api/v1/admin/health/checks/runопционально check_namepermission health.manage; FormRequest whitelist — check_name должен быть зарегистрированным health-классом, иначе 422
GET /api/v1/system/healthзаголовок системного токенатокен сверяется с config('health.system_token'), не settings-store
Манифест модуля extra.cms.healthФQCN health-классапри установке модуля — класс обязан реализовывать интерфейс HealthCheck ядра, иначе модуль не проходит self-test enable
Health-классы модулей (внутренний вызов джобы)status, message, metaконтракт интерфейса HealthCheck; таймаут на каждый чек обязателен (см. крайние случаи)

Выходы

ПотребительДанныеФормат
Filament дашбордстатусы (зелёный/жёлтый/красный), таймлайн историиBlade через сервис модуля
/api/v1/system/healthагрегированный статус инсталляцииJSON, конверт data/meta
/api/v1/admin/health/checksдетализация по каждому чекуJSON, keyset-пагинация
cms/updates (гейт cms:upgrade)итог гейта pass/failсервис-вызов (requires, канал 4)
cms/notifications-bus (suggests)алерт об ухудшении статусасервис-вызов (канал 3, notification-channel)
cms_health_check_results/cms_health_historyснимки статусов проверокБД, append-only история

Настройки (группа health)

КлючТипДефолтaffectsPageCacheОписание
health.enabledbooltrueнетВключить проверки (обязателен в managed-парке)
health.check_interval_minutesint5нетПериодичность прогона проверок
health.check_timeout_secondsint10нетТаймаут одного health-класса — превышение помечает чек failed/timeout, не роняет прогон остальных
health.flap_debounce_checksint2нетЧисло подряд неудачных прогонов до признания статуса красным (гистерезис против флаппинга)
health.disk_threshold_percentint85нетПорог тревоги по заполнению диска
health.queue_backlog_thresholdint500нетПорог тревоги по длине очереди
health.public_endpoint_enabledbooltrueнетДоступность /api/v1/system/health
health.upgrade_gate_enabledbooltrueнетБлокировать cms:upgrade при красном статусе
health.history_retention_daysint90нетРетеншн cms_health_history — без него журнал растёт неограниченно
health.alert_window_minutesint15нетОкно группировки алертов в одно уведомление (защита от алерт-шторма)

Лимиты и квоты (матрица v2.2): history_retention_days, queue_backlog_threshold и disk_threshold_percent — явные настройки-лимиты с дефолтами, требуемые критерием «лимиты и квоты» матрицы; достижение queue_backlog_threshold/disk_threshold_percent красит соответствующий системный чек и уходит алертом, не молчаливо игнорируется, а history_retention_days ограничивает рост cms_health_history штатной командой ретеншна (см. «Модель данных»).

Kill-switch (матрица v2.2): health.upgrade_gate_enabled — реализация критерия «kill-switch»: аварийно отключает влияние health на cms:upgrade (гейт перестаёт блокировать апгрейд при красном статусе) без выключения самого модуля health — проверки и дашборд продолжают работать, отключается только их эффект на апгрейд.

API

МетодПутьДоступНазначение
GET/api/v1/system/healthтокен (system)Сводный статус для внешнего аптайм-монитора
GET/api/v1/admin/health/checksadmin (health.view)Детализация всех проверок
POST/api/v1/admin/health/checks/runadmin (health.manage)Принудительный перезапуск проверок

Детализация проверок отдаётся keyset-пагинацией (не OFFSET) при росте числа health-классов.

Компоненты

Виджеты: Filament-виджет «Статус системы». Filament: страница со списком проверок и историей. Команды: cms:health:check --json (прогон всех проверок вне расписания, вывод — статус каждого чека, duration_ms, причина при failed), cms:health:gate --json (используется cms:upgrade, возвращает булево pass/fail и список красных чеков-блокеров).

Демо-сидеры: не применимо — модуль не регистрирует блоков/виджетов в BlockRegistry (галерея блоков и playground его не касаются), демо-данных для затравки не требуется.

Системные проверки по умолчанию

ЧекЧто проверяетДефолтный порог
databaseподключение к БД доступно, простой запрос выполняетсятаймаут health.check_timeout_seconds (10 сек)
redisподключение к Redis доступнотаймаут health.check_timeout_seconds (10 сек)
queue-backlogдлина очереди Horizon не превышает порогhealth.queue_backlog_threshold (500)
disk-spaceдоля занятого места на дискеhealth.disk_threshold_percent (85%)
scheduler-heartbeatфакт выполнения запланированных задач cron за ожидаемый интервалпривязан к health.check_interval_minutes — отсутствие heartbeat дольше интервала = failed

Каждый модуль сверх этого списка добавляет свои health-классы через extra.cms.health (см. «Входные и выходные данные»).

Граница с ядром по диску (ревизия №2, п. 2): базовый чек free-space с порогами system.disk_warn_percent/disk_critical_percent встроен в health-агрегат ядра и работает без этого модуля. disk-space модуля — расширение (свой порог, история, алерты/эскалация); расширенные чеки (inode, прогноз роста) — тоже зона модуля. Пороги ядра и модуля независимы: ядро сигналит «до падения БД», модуль — «по политике эксплуатации».

События и обмен

СобытиеКогдаPayload
HealthCheckFailedпроверка перешла в статус failed/warning (после debounce)check_name, status, message, duration_ms
HealthCheckRecoveredпроверка вернулась в статус okcheck_name, previous_status

Оба события издаются после прохождения flap_debounce_checks — промежуточные колебания статуса в рамках гистерезиса не порождают события и не долетают до подписчиков (иначе cms/notifications-bus штормит на каждое дрожание).

Слушает: манифесты модулей (extra.cms.health) для регистрации дополнительных проверок. Provides: не декларирует контрактов ядра. Алерты об ухудшении публикуются в cms/notifications-bus как потребитель его сервис-вызова (по suggests).

Таблица взаимодействий

Сущность/модульКаналНаправлениеЧто происходит
Все модули (extra.cms.health)манифест-регистрация (декларативно, вне рантайм-каналов)inДополнительный health-класс регистрируется при установке модуля
cms/updatesrequires → сервис-вызов (канал 4), cms:health:gateoutИтог гейта блокирует/пропускает cms:upgrade
cms/notifications-bussuggests → сервис-вызов (канал 3, notification-channel)outАлерт об ухудшении статуса, сгруппированный по alert_window_minutes
cms/fleet-dashboardкосвенно, через телеметрию cms/updatesouthealth_status попадает в суточную телеметрию как часть чужого payload, не прямая связь
БД / Redis / очередь Horizon / дисквнутренний вызов драйвера (не модуль)inИсточник данных системных проверок

Эскалация в fleet-dashboard (точный путь): агрегированный health_status этого модуля не передаётся напрямую — он попадает в суточный POST-запрос телеметрии, который формирует cms/updates (см. его раздел «Настройки», updates.telemetry_enabled) и отправляет в cms/fleet-dashboard. Там красный/жёлтый статус отображается в карте парка сайта и, что важнее, участвует в гейте канареечной волны: cms/fleet-dashboard останавливает волну для конкретного сайта при провале его health-check (см. fleet-dashboard.md) — красный статус этого модуля может заблокировать раскатку обновления именно на этот сайт, не влияя на остальные сайты волны. Прямого канала «healthfleet-dashboard» нет: модуль health не знает о существовании fleet-dashboard, весь путь идёт через requires-канал с cms/updates.

Фоновая работа

Джобы прогона проверок в именованной очереди health, расписание — health.check_interval_minutes через ScheduleRegistrar ядра, без прямого Schedule:: в boot. Внешние HTTP-проверки (если health-класс модуля их требует) выполняются только из очереди.

Метрики и алерты (§15 стандарта): доля красных чеков за окно (red_checks_ratio), среднее время прогона одного чека (check_duration_ms, p50/p95), флап-рейт (число переключений статуса за сутки, сигнал нестабильного чека ещё до срабатывания алерта). Алерт HealthCheckFailed — единственный инцидент-триггер модуля, издаётся только после flap_debounce_checks, дальше группируется alert_window_minutes перед отправкой в cms/notifications-bus.

Мини-ранбук (симптом → что проверить → команда):

СимптомЧто проверитьКоманда
Дашборд красный, алерта нетне истёк ли flap_debounce_checks (флаппинг ещё копит подряд-неудачи)cms:health:check --json — смотреть duration_ms и текущий счётчик неудач по чеку
Прогон чека висит дольше обычногоhealth-класс модуля без таймаута на внешнем вызове (см. «чек сам роняет систему» ниже)cms:health:check --json, найти чек с status=timeout, проверить check_timeout_seconds
Шквал уведомлений в cms/notifications-busне сработала группировка alert_window_minutes (например, окно выставлено в 0)сверить health.alert_window_minutes в settings-store группы health
Дублирующиеся снимки статуса за один моментручной перезапуск совпал с расписанием, лок джобы не сработаллог очереди health на предмет двух активных джоб одновременно; cms:health:check --json для форс-прогона после починки
Алерты не долетают вообщеcms/notifications-bus выключен или не установлен (suggests, не requires)cms:doctor --json — проверить статус suggests-зависимости; статус при этом остаётся виден в Filament

Бэкап/рестор: cms_health_check_results/cms_health_history — журнальные, не критичны для бэкапа: после рестора инсталляции они пересоздаются следующими прогонами по расписанию (health.check_interval_minutes), пустая история сразу после рестора — ожидаемое состояние, не повод считать рестор неполным. Общий бэкап всех cms_* (включая эти таблицы) и обязательный cms:postupgrade после рестора — по ранбуку ядра.

Производительность и кеш

Объёмы: check_interval_minutes=5 → 288 прогонов/сутки на каждый health-класс; на managed-сайте с десятками модулей это системные ~5 проверок + по одной на модуль — сотни-тысячи строк в cms_health_history в сутки. Без history_retention_days журнал растёт неограниченно — BRIN по checked_at снижает стоимость записи, но не заменяет периодическую очистку по расписанию. Горячий путь — публичный /api/v1/system/health (дёргается внешними аптайм-мониторами раз в 1-5 минут): агрегированный статус читается из последнего снимка cms_health_check_results, не пересчитывается на лету; допустим короткий кеш агрегата (TTL ≤ check_interval_minutes) на этом эндпоинте, чтобы частые внешние опросы не били в БД при каждом запросе. Индекс (check_name, checked_at) в cms_health_history под таймлайн; дополнительно нужен индекс/частичный индекс по status для быстрой агрегации «все ли зелёные» без full scan. Собственных тегов page-cache не объявляет — статусы читаются из своих таблиц, не через кеш ядра.

Безопасность

Границы входа: публичный endpoint /api/v1/system/health защищён токеном (system), не открыт анонимно; принудительный перезапуск проверок — только под health.manage. Входных форм с пользовательскими данными нет, санитизация не требуется. Специфичные векторы: подбор/утечка system-токена даёт наблюдателю карту внутреннего состояния (очереди, диск, БД) — токен ротируется как секрет, не хранится в settings-store; health-класс стороннего модуля не должен получать доступ к чужим таблицам для диагностики — только через сервисы владельца (как любой модуль, §5 стандарта); чек с неограниченным временем выполнения — сам по себе вектор DoS на воркер очереди health, поэтому таймаут обязателен на уровне контракта HealthCheck, а не дисциплины отдельного модуля.

Права: health.view, health.manage.

Матрица ролей:

Рольhealth.view (просмотр статусов/истории)health.manage (принудительный перезапуск проверок, check_name)
Администратор сайта
Менеджер
Редактор контента❌ (модуль внутренний, редактору не нужен)
Studio (студия)✅ + эксклюзивное право disable модуля в managed-парке (см. «Зависимости и выключение»)

UX-требования

Админ: пустое состояние сразу после установки — «проверок ещё не было, первая через check_interval_minutes минут» вместо пустого графика; кнопка «перезапустить все проверки» — одно действие, не по одной на чек; предупреждение о времени ожидания, если среди чеков есть долгие внешние проверки; ошибки — на человеческом языке («Redis недоступен: соединение отклонено», не трассировка исключения); статус конкретного чека кликабелен и показывает историю именно по нему (не общий лог).

Посетитель: не применимо — модуль внутренний; единственная публичная поверхность (/api/v1/system/health) обязана отвечать 200 с полем status даже при частичной деградации (не 5xx), чтобы внешний мониторинг отличал «сайт лежит» от «часть проверок жёлтая».

Крайние случаи и типовые баги

  • флаппинг чека (быстрое переключение ok/failed) → flap_debounce_checks подряд неудачных прогонов до признания статуса красным; единичный сбой не поднимает алерт и не красит дашборд;
  • чек сам роняет систему (health-класс модуля делает тяжёлый запрос без таймаута) → таймаут (check_timeout_seconds) обязателен на уровне контракта HealthCheck: превышение помечает именно этот чек failed/timeout, не блокирует остальные и не валит джобу прогона целиком;
  • алерт-шторм (отказ Redis красит разом все зависящие чеки) → алерты группируются в одно уведомление за alert_window_minutes, а не рассылаются по одному на каждый упавший чек;
  • параллельный прогон проверок (ручной перезапуск совпал с расписанием) → идемпотентность через лок на уровне джобы: второй прогон либо ждёт первого, либо скипается с логом, не пишет дублирующие снимки статуса;
  • выключение модуля посреди прогона → уже запущенная джоба дорабатывает штатно, новые проверки не планируются; cms:upgrade при выключенном health пропускает гейт с предупреждением (описано выше), не падает;
  • отсутствие suggests-модуля cms/notifications-bus → алерты не отправляются, статус виден только в Filament-дашборде и логе — health-чеки продолжают работать;
  • сбой/таймаут health-класса стороннего модуля (внешний API, например интеграция) → статус unknown/warning, отдельный от явного failed: «не удалось проверить» — не то же самое, что «сервис сломан»;
  • пустые данные — свежая установка без истории → дашборд показывает «нет данных, первая проверка через X минут», не пустой график и не ошибку рендера;
  • огромный объём истории (годы работы инсталляции) → history_retention_days обязателен, штатная команда очистки по расписанию — без него таблица растёт неограниченно даже с BRIN-индексом;
  • измерения locale/city/site — health-чеки работают на уровне инсталляции целиком, измерения не применимы; на cms/multisite с несколькими сайтами на одной БД нужно явно зафиксировать, что статус общий на инсталляцию, не per-сайт — иначе красный чек одного сайта блокирует апгрейд для всех;
  • противоречивая комбинация настроек: health.upgrade_gate_enabled=true при health.enabled=false → гейта фактически нет, но конфигурация выглядит как будто он есть; cms:upgrade обязан явно сообщать «гейт включён, но health-чеки отключены — апгрейд идёт без проверки», а не молча пропускать как «зелёный»;
  • обязательность модуля в managed-парке и disable — зафиксировано ревизией 14.07.2026: disable инфра-модулей managed-парка (cms/health в их числе) недоступен клиентским ролям — доступен только studio-роли, с обязательным подтверждением и записью в аудит (см. «Зависимости и выключение» выше и §3 стандарта); на standalone-инсталляциях disable доступен клиентским ролям свободнее. Правило «выключение = деградация» при этом не отменяется: модуль всё же выключенный студией (managed) или клиентом (standalone) не роняет сайт — деградирует только наблюдаемость;

Донорский код

Донор: — (переиспользуются практики health-проверок из er (SEO health) и catalog, готовых путей не выдано)

Легаси-импорт (§16 стандарта): не применим. er/catalog — доноры кода (подходы к health-проверкам конкретных систем), не доноры данных: у клиентских legacy-платформ нет структурированной истории health-статусов, которую имело бы смысл переносить — свежая инсталляция стартует с чистой cms_health_history и накапливает её сама по расписанию. Команда cms:health:import-legacy не разрабатывается.

Тесты и приёмка

  • [ ] Контрактный тест: /api/v1/system/health отдаёт агрегированный статус под токеном
  • [ ] Health-чек самого модуля health отражает работоспособность планировщика проверок
  • [ ] Гейт cms:upgrade блокирует апгрейд при красном статусе и это покрыто тестом
  • [ ] При выключении модуля cms:upgrade продолжает работать с предупреждением в лог
  • [ ] Права health.view/health.manage разграничивают чтение и принудительный запуск
  • [ ] Алерты об ухудшении статуса уходят через cms/notifications-bus без дублей
  • [ ] Флаппинг чека не поднимает алерт до flap_debounce_checks подряд неудачных прогонов
  • [ ] Чек без ответа дольше check_timeout_seconds помечается timeout и не блокирует остальные проверки
  • [ ] Одновременный ручной и плановый прогон не создаёт дублирующих снимков статуса
  • [ ] Ретеншн cms_health_history применяется автоматически по history_retention_days
  • [ ] Контрактный набор cms-testing пройден, пакет протестирован в testbench-изоляции
  • [ ] Feature-тест на каждый роут модуля; тестовая БД только health_test, migrate:fresh запрещён
  • [ ] Disable модуля клиентской ролью в managed-парке заблокирован («модуль обязателен по условиям поддержки»); studio-роль выключает с подтверждением и записью в аудит
  • [ ] health_status доходит до cms/fleet-dashboard через суточную телеметрию cms/updates и красный статус останавливает канареечную волну для этого сайта
  • [ ] Состав системных чеков по умолчанию (database, redis, queue-backlog, disk-space, scheduler-heartbeat) присутствует сразу после установки модуля и каждый чек покрыт тестом на срабатывание своего дефолтного порога

Внутренняя база знаний студии