168 lines
17 KiB
Markdown
168 lines
17 KiB
Markdown
---
|
||
work_item: ORCH-100
|
||
stage: analysis
|
||
author_agent: analyst
|
||
status: ready-for-review
|
||
created_at: 2026-06-10
|
||
model_used: claude-opus-4-8
|
||
---
|
||
|
||
# 01 — BRD (бизнес-требования): ORCH-100 — FND/F1b: sidecar-watchdog (мозг мониторинга, отдельный контейнер)
|
||
|
||
Work Item: **ORCH-100** · Repo: **orchestrator** · Стадия: analysis
|
||
|
||
## 1. Бизнес-контекст и проблема
|
||
|
||
Задача — фундаментный кирпич **F1b** домена 0 «Фундамент» эпика автономного саморазвития
|
||
(`docs/epics/self-evolution.md`, §2, §«Архитектурные рамки наблюдаемости»). **F1a (ORCH-099)** уже
|
||
реализовал лёгкий read-only `GET /metrics` в самом орке — он отдаёт **только сырьё** (стадии,
|
||
очередь, agent-liveness, cost), без порогов/алертов/хранения. F1b — **вторая половина пары:** мозг
|
||
мониторинга, который это сырьё читает, дополняет внешними сигналами (хост, контейнеры, внешние
|
||
зависимости) и превращает в **алерты**.
|
||
|
||
**Боль, которую закрывает F1b.** Сегодня платформа слепа к собственному здоровью в реальном
|
||
времени. Инциденты 06–09.06 (диск хоста молча дорос до 100% и встал весь конвейер — ORCH-063;
|
||
фантом-merge, deploy-петли, флапп-статусы, зомби-jobs) обнаруживались **постфактум, человеком**.
|
||
Частичные стражи существуют, но они **живут ВНУТРИ процесса орка** (`disk_watchdog` ORCH-063,
|
||
`reaper` ORCH-065, `reconciler` ORCH-053): если орк завис/съел память/упал — стражи лягут **вместе
|
||
с ним**, и платформа слепа именно в критический момент.
|
||
|
||
**Архитектурная рамка — установленный факт заказчика (Слава, 09.06), не предмет переизобретения:**
|
||
- **C-1 / C-1б:** наблюдатель ОТДЕЛЁН от наблюдаемого. Sidecar-контейнер на том же хосте; КОД
|
||
sidecar — в репо орка (папка `watchdog/`), но рантайм — **ОТДЕЛЬНЫЙ контейнер** (свой Dockerfile +
|
||
сервис `orchestrator-watchdog` в `docker-compose.yml`). Изоляция — на уровне контейнера, не репо.
|
||
- **C-2:** без внешнего плеча (одна площадка; принятый риск — падёт весь хост → молчит и наблюдатель).
|
||
- **C-3:** тонкий стек — **НЕ Grafana/Prometheus**. Хост впритык: RAM 171Mi free / 7.7Gi, диск 92%.
|
||
- **Разделение ответственности:** орк отдаёт сырьё (`/metrics`), sidecar — мозг (пороги/алерты/свой
|
||
Telegram-канал, независимый от кода орка). Орк лёг → `/metrics` недоступен = **сам сигнал тревоги**.
|
||
|
||
**Критический инвариант наблюдаемости:** падение/зависание орка должно делать sidecar **громче**, а
|
||
не тише. Если орк не отвечает на `/metrics` — sidecar жив и обязан зарепортить это как тревогу
|
||
«орк не отвечает».
|
||
|
||
## 2. Объём (scope)
|
||
|
||
### В объёме
|
||
- Новая папка `watchdog/` в репо орка: тонкий код sidecar + собственный `Dockerfile`.
|
||
- Сервис `orchestrator-watchdog` в `docker-compose.yml` (отдельный контейнер, свой рестарт/память).
|
||
- **Сбор сигналов** (периодический тик): (a) `GET /metrics` орка по HTTP; (b) хост — диск %/inode,
|
||
память, CPU; (c) контейнеры — через `docker.sock` **read-only** (статусы Up/healthy/restarting/
|
||
exited/unhealthy); (d) пинг внешних зависимостей — Plane / Gitea / Anthropic.
|
||
- **Алертинг по порогам:** диск≥порог, память, agent-завис >N мин, job-failed, застрявшая стадия,
|
||
контейнер-down/unhealthy, внешняя зависимость недоступна, **орк-down (`/metrics` не отвечает)**.
|
||
- **Доставка:** Telegram через **СОБСТВЕННЫЙ канал sidecar** (свой токен/chat в `.env`), НЕ через
|
||
код/Telegram-функции орка.
|
||
- **Гигиена алертов:** дедупликация + throttle (один алерт на пересечение порога, не флапп) +
|
||
recovery-сообщение при возврате метрики в норму.
|
||
- **Управляемость:** kill-switch, конфигурируемые пороги, конфигурируемые интервалы.
|
||
- `.env.example`: токен/chat watchdog + пороги/интервалы (канон, без секретов).
|
||
- Документация (`07-infra-requirements.md` — разовое инфра-действие) + `CHANGELOG.md`; pytest зелёный.
|
||
|
||
### Вне объёма
|
||
- **Любая авто-ремедиация** (рестарт контейнеров, очистка диска, requeue jobs). F1b — **только
|
||
наблюдение + алерт** (L0 reactive, эпик §9). Авто-фиксы — домен D1 (отдельные задачи).
|
||
- **Grafana / Prometheus / TSDB / дашборд-UI / исторические графики** (C-3 — тонкий стек).
|
||
- **Изменение `/metrics` орка** (контракт F1a/ORCH-099 — данность; sidecar — потребитель). Если
|
||
обнаружится нехватка поля — это отдельная задача-расширение F1a, не часть F1b.
|
||
- **Изменение `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / схемы БД орка** — sidecar их не
|
||
касается (он вне процесса орка).
|
||
- **Журнал уроков (F2)** — отдельная задача; F1b не пишет в БД орка.
|
||
- **Второе внешнее плечо мониторинга (L2)** — сознательно отложено (C-2).
|
||
|
||
## 3. Заинтересованные стороны
|
||
- **Заказчик / приёмка:** Слава (зафиксировал архитектурные рамки 09.06).
|
||
- **Постановщик / ведение:** Стрим.
|
||
- **Затрагивает:** операторов платформы (получатели алертов), все проекты в общем прод-инстансе
|
||
(enduro-trails и пр.) — sidecar повышает наблюдаемость их общей инфраструктуры, **не вмешиваясь**.
|
||
- **Исполнители конвейера:** architect (стек, формат хранения порогов, владелец диск-алерта),
|
||
developer, reviewer, tester, deployer.
|
||
|
||
## 4. Бизнес-требования (BR)
|
||
|
||
- **BR-1 (отдельный контейнер).** Sidecar собирается в отдельный образ (`watchdog/Dockerfile`) и
|
||
работает как сервис `orchestrator-watchdog` в `docker-compose.yml` — отдельный процесс/память/
|
||
рестарт, **НЕ внутри процесса орка**.
|
||
- **BR-2 (сбор сырья орка).** На каждом тике sidecar делает `GET /metrics` орка по HTTP и
|
||
разбирает версионированный конверт (`schema_version`/`stages`/`queue`/`agents`/`cost`), **толерантно
|
||
к неизвестным/отсутствующим полям** (контракт F1a — additive, версия не растёт на добавление поля).
|
||
- **BR-3 (сбор хоста).** Sidecar измеряет хост: заполнение диска (% и, где доступно, inode), память,
|
||
CPU — по смонтированным хост-путям/интерфейсам, доступным контейнеру.
|
||
- **BR-4 (сбор контейнеров).** Sidecar читает состояние контейнеров через `docker.sock`
|
||
(**read-only mount**): различает Up / healthy / restarting / exited / unhealthy. Минимум — статус
|
||
ключевых контейнеров платформы (включая сам `orchestrator`).
|
||
- **BR-5 (пинг зависимостей).** Sidecar периодически проверяет доступность внешних зависимостей —
|
||
Plane, Gitea, Anthropic (лёгкий health/ping, короткий таймаут) — и алертит при недоступности.
|
||
- **BR-6 (пороговый алертинг).** При **пересечении порога** сигналом (диск≥порог, память,
|
||
agent-завис >N мин, job-failed, застрявшая стадия, контейнер-down/unhealthy, зависимость
|
||
недоступна) sidecar шлёт **ровно один** Telegram-алерт.
|
||
- **BR-7 (орк-down = тревога).** Если `GET /metrics` орка **не отвечает** (таймаут/connection
|
||
refused/5xx) — sidecar шлёт алерт «орк не отвечает». Это **главный** сценарий ценности:
|
||
наблюдатель жив, наблюдаемый лёг.
|
||
- **BR-8 (свой Telegram-канал).** Алерты идут через **независимый** транспорт sidecar — собственные
|
||
bot-токен и chat-id из `.env`, БЕЗ обращения к коду/функциям/токену орка (иначе падение орка
|
||
утянуло бы и алерт-канал — нарушение C-1).
|
||
- **BR-9 (дедуп / throttle / recovery).** Повторное нахождение метрики за порогом не флаппит: один
|
||
алерт на пересечение + анти-спам cooldown между повторами + **recovery-сообщение** при возврате
|
||
метрики в норму. Поведение — по образцу `disk_watchdog` (ORCH-063): чистая решающая функция
|
||
`(value, threshold, prev_state, now, cooldown) → alert | realert | recovery | none`.
|
||
- **BR-10 (нет дубля диск-алерта).** Диск уже алертит `disk_watchdog` ORCH-063 (порог 85%, через
|
||
Telegram орка). F1b **НЕ должен** порождать второй диск-алерт на то же событие. **Владельца
|
||
диск-алерта (sidecar vs внутренний `disk_watchdog`) выбирает архитектор** — BRD лишь фиксирует
|
||
требование «один диск-алерт на событие, без дублирования».
|
||
|
||
## 5. Нефункциональные требования (NFR)
|
||
|
||
- **NFR-1 (изоляция / резилентность).** Падение/зависание/рестарт орка **НЕ роняет** sidecar
|
||
(доказывается: орк down → sidecar продолжает тикать и шлёт алерт). Обратное тоже: sidecar — чисто
|
||
наблюдатель, его падение не влияет на конвейер.
|
||
- **NFR-2 (тонкость).** Контейнер лёгкий: предсказуемо малое потребление памяти (хост впритык —
|
||
171Mi free). Конкретный бюджет памяти и `mem_limit` — решение архитектора; BRD требует «в разумных
|
||
пределах, измеримо». **НЕ Grafana/Prometheus.**
|
||
- **NFR-3 (never-raise).** Любая ошибка сбора/парсинга/сети/отправки — best-effort: один битый
|
||
источник деградирует один сигнал, не роняет тик; ошибка тика не роняет демон. По образцу
|
||
`disk_watchdog` / `metrics` (три уровня never-raise: per-source, per-tick, per-send).
|
||
- **NFR-4 (безопасность self-hosting).** Sidecar **только читает и шлёт Telegram** — НИКОГДА не
|
||
трогает диск/контейнеры/прод, не рестартит, не пишет в `docker.sock` (mount **read-only**), не
|
||
пишет в БД орка, не пушит в `main`. Безопасен для общего инстанса (enduro-trails не затронут).
|
||
- **NFR-5 (управляемость / обратимость).** Kill-switch (выключить → sidecar инертен/не стартует,
|
||
нулевой эффект на орк). Пороги и интервалы конфигурируемы через `.env` (не хардкод).
|
||
- **NFR-6 (изоляция контракта).** Sidecar толерантен к версии `/metrics`: неизвестное поле
|
||
игнорируется, отсутствие опционального — не падение; рост `schema_version` логируется (предупреждение),
|
||
не крэшит.
|
||
- **NFR-7 (наблюдаемость самого sidecar).** Стартап/тик/решения логируются достаточно, чтобы по логам
|
||
контейнера понять, что sidecar жив и почему (не)сработал алерт.
|
||
|
||
## 6. Допущения и ограничения
|
||
|
||
- **Зависимость:** F1b **зависит от F1a (ORCH-099)** — читает `GET /metrics`. Контракт `/metrics`
|
||
(envelope `schema_version`/`generated_at`/`clk_tck`/`stages`/`queue`/`agents`/`cost`/`enabled`) —
|
||
установленный факт, sidecar его потребитель.
|
||
- **Сеть:** орк работает `network_mode: host` (порт 8500) → из host-network sidecar `/metrics`
|
||
достижим как `http://127.0.0.1:8500/metrics`. Точный сетевой режим sidecar — решение архитектора.
|
||
- **`docker.sock`** доступен на хосте `/var/run/docker.sock`; монтируется в sidecar **read-only**.
|
||
- **Разовое инфра-действие** (добавить сервис в compose + первый запуск + создать bot/chat watchdog)
|
||
выполняется человеком (Слава/Стрим) на хосте — фиксируется в `07-infra-requirements.md`. Дальше код
|
||
watchdog катится через конвейер (self-hosting).
|
||
- **Стек (Python/Go), формат хранения порогов, владелец диск-алерта** — **зона архитектора** в рамках
|
||
C-1…C-3; BRD их не предрешает.
|
||
- **Известный принятый риск (C-2):** падёт весь хост/Docker → молчит и sidecar (нет внешнего плеча).
|
||
- **Telegram 48ч** и прочие лимиты транспорта — как у орка (best-effort доставка).
|
||
|
||
## 7. Критерии успеха
|
||
|
||
Sidecar стартует отдельным контейнером, на каждом тике собирает сырьё орка + хост + контейнеры +
|
||
зависимости, при пересечении порога шлёт ровно один Telegram-алерт со своего канала (throttle +
|
||
recovery), при недоступности орка шлёт «орк не отвечает», и переживает падение орка не падая сам.
|
||
Тонкий, с kill-switch и конфигурируемыми порогами. Разовое инфра-действие задокументировано, pytest
|
||
зелёный, доки + CHANGELOG обновлены. Детальные PASS/FAIL — `03-acceptance-criteria.md`.
|
||
|
||
## 8. Риски
|
||
|
||
- **Дубль диск-алерта** с `disk_watchdog` ORCH-063 (BR-10) — нужно явное решение владельца (архитектор).
|
||
- **Шум алертов** (флапп на границе порога) при недостаточном throttle/recovery — закрывается BR-9.
|
||
- **Зависимость от `/metrics`:** ложный «орк-down» при сетевой икоте — нужен разумный таймаут/ретрай в
|
||
пороге, чтобы единичный transient не флаппил (детали — архитектор/developer).
|
||
- **Ресурсы хоста впритык** — sidecar обязан быть лёгким (NFR-2), иначе сам станет частью проблемы.
|
||
- **`docker.sock` доступ** — строго read-only; риск привилегий минимизируется mount-режимом (NFR-4).
|
||
- Детальный реестр и митигации — `10-tech-risks.md` (заполняет архитектор).
|