156 lines
14 KiB
Markdown
156 lines
14 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
|
||
---
|
||
|
||
# 02 — ТЗ (TRZ): ORCH-100 — FND/F1b: sidecar-watchdog (мозг мониторинга, отдельный контейнер)
|
||
|
||
Work Item: **ORCH-100** · Repo: **orchestrator** · Стадия: analysis
|
||
|
||
> ТЗ описывает **конкретные изменения к реализации**, выведенные из BRD (`01-brd.md`) и фактического
|
||
> кода. Архитектурное обоснование/решения (выбор стека Python/Go, формат хранения порогов, владелец
|
||
> диск-алерта, точная топология сети sidecar, бюджет памяти/`mem_limit`) — **зона архитектора**
|
||
> (`06-adr/`). ТЗ фиксирует ТРЕБОВАНИЯ и ограничения, не способ реализации.
|
||
|
||
## 1. Сводка изменения
|
||
|
||
Добавить **отдельный sidecar-контейнер** `orchestrator-watchdog`, код которого лежит в новой папке
|
||
`watchdog/` репозитория орка, а рантайм — изолированный контейнер (свой `watchdog/Dockerfile` + сервис
|
||
в `docker-compose.yml`). Sidecar периодически (тик): (1) тянет `GET /metrics` орка; (2) меряет хост
|
||
(диск/inode/память/CPU); (3) читает статусы контейнеров через read-only `docker.sock`; (4) пингует
|
||
Plane/Gitea/Anthropic. По набору **конфигурируемых порогов** через **чистую решающую функцию**
|
||
(образец `disk_watchdog.decide`) принимает решение `alert | realert | recovery | none` с дедупом/
|
||
throttle, и шлёт алерт в **собственный** Telegram-канал (свой токен/chat, независимо от кода орка).
|
||
Особый сигнал: `/metrics` не отвечает → алерт «орк не отвечает». Всё — never-raise, под kill-switch,
|
||
строго read-only к наблюдаемому (self-hosting-безопасно).
|
||
|
||
**Орк-сторона (`src/**`) не меняется**: F1b — потребитель уже существующего `GET /metrics` (F1a,
|
||
ORCH-099). `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / схема БД орка — **не тронуты**.
|
||
|
||
## 2. Задействованные модули / пути
|
||
|
||
| Путь | Действие |
|
||
|------|----------|
|
||
| `watchdog/` | **создать** — корень кода sidecar (новая папка в репо орка) |
|
||
| `watchdog/Dockerfile` | **создать** — отдельный тонкий образ sidecar (стек — выбор архитектора) |
|
||
| `watchdog/<entrypoint>` | **создать** — демон/цикл сбора+решения+отправки (имя/структура — архитектор) |
|
||
| `watchdog/<collectors>` | **создать** — сбор: `/metrics` орка (HTTP), хост (диск/inode/память/CPU), контейнеры (`docker.sock` ro), пинг Plane/Gitea/Anthropic |
|
||
| `watchdog/<decision>` | **создать** — **чистая** решающая функция порога `(value, threshold, prev_state, now, cooldown) → alert\|realert\|recovery\|none` (образец `src/disk_watchdog.py::decide`) |
|
||
| `watchdog/<notify>` | **создать** — независимый Telegram-транспорт sidecar (свой токен/chat; НЕ импорт `src/notifications.py`) |
|
||
| `watchdog/<config>` | **создать** — чтение порогов/интервалов/токенов/kill-switch из env |
|
||
| `watchdog/tests/` (или `tests/watchdog/`) | **создать** — pytest на чистые функции (решение/парсинг/детект орк-down); размещение — архитектор |
|
||
| `docker-compose.yml` | **изменить** — добавить сервис `orchestrator-watchdog` (build `watchdog/`, restart-policy, read-only `docker.sock`, `mem_limit`, env, kill-switch) |
|
||
| `.env.example` | **изменить** — канон: токен/chat watchdog + пороги + интервалы + kill-switch (без секретов) |
|
||
| `CHANGELOG.md` | **изменить** — запись о F1b |
|
||
| `docs/work-items/ORCH-100/07-infra-requirements.md` | **создать (architect)** — разовое инфра-действие: добавить сервис в compose, создать bot/chat watchdog, первый запуск на хосте |
|
||
|
||
> **`src/**` НЕ редактируется.** Если в ходе разработки выяснится нехватка поля в `/metrics` — это
|
||
> отдельная задача-расширение F1a (ORCH-099), а не правка в рамках F1b (см. BRD §«Вне объёма»).
|
||
|
||
## 3. Функциональные требования
|
||
|
||
### FR-1 — Отдельный контейнер sidecar (BR-1, NFR-1)
|
||
Sidecar собирается из `watchdog/Dockerfile` в отдельный образ и поднимается сервисом
|
||
`orchestrator-watchdog` в `docker-compose.yml`: отдельный процесс/память/рестарт-политика, **НЕ**
|
||
внутри процесса орка. `restart: unless-stopped` (или эквивалент) — sidecar самовосстанавливается.
|
||
|
||
### FR-2 — Сбор сырья орка (BR-2, NFR-6)
|
||
На каждом тике `GET <orch-metrics-url>` (дефолт-достижимость `http://127.0.0.1:8500/metrics` при
|
||
host-network; URL конфигурируем). Тело — версионированный конверт F1a:
|
||
`{schema_version, generated_at, clk_tck, stages[], queue, agents[], cost, enabled}`. Парсинг
|
||
**толерантен**: неизвестные поля игнорируются, отсутствие опционального — не ошибка, рост
|
||
`schema_version` логируется (warning), не крэшит. Из конверта извлекаются сигналы для порогов:
|
||
agent-liveness (cpu_ticks/runtime → «завис»), застрявшая стадия, job-failed, длина очереди.
|
||
|
||
### FR-3 — Детект «орк не отвечает» (BR-7) — главный сигнал
|
||
Если `GET /metrics` завершается таймаутом / connection refused / 5xx / нечитаемым телом — это
|
||
**отдельный сигнал тревоги** `orchestrator_down`. Проходит через ту же машину порога/дедупа/recovery
|
||
(BR-9): один алерт «орк не отвечает», recovery при восстановлении. Единичный transient не должен
|
||
немедленно флаппить — порог/таймаут/ретрай подбираются так, чтобы алерт был осмысленным (детали —
|
||
архитектор/developer; требование: «не флаппить на одиночной сетевой икоте»).
|
||
|
||
### FR-4 — Сбор хоста (BR-3)
|
||
Измерять заполнение диска (% и, где доступно, inode), память, CPU по доступным контейнеру
|
||
хост-путям/интерфейсам (стдлиб-средствами выбранного стека; **без** тяжёлых агентов). Пути/пороги —
|
||
конфигурируемы. **Диск:** см. FR-9 (анти-дубль с ORCH-063).
|
||
|
||
### FR-5 — Сбор контейнеров (BR-4, NFR-4)
|
||
Через `docker.sock`, смонтированный **read-only**, читать состояния контейнеров платформы:
|
||
различать Up / healthy / restarting / exited / unhealthy. Минимум — статус `orchestrator` (и других
|
||
ключевых сервисов). **Только чтение** Docker API (list/inspect) — никаких start/stop/restart/exec.
|
||
|
||
### FR-6 — Пинг внешних зависимостей (BR-5)
|
||
Периодически проверять доступность Plane / Gitea / Anthropic лёгким запросом (health/ping, короткий
|
||
таймаут, never-raise). Недоступность → сигнал для порога. Эндпоинты/таймауты — конфигурируемы.
|
||
|
||
### FR-7 — Пороговый алертинг (BR-6, BR-9)
|
||
Каждый сигнал проходит через **чистую решающую функцию** (образец `disk_watchdog.decide`):
|
||
вход `(value/state, threshold, prev_state, now, cooldown)`, выход `alert | realert | recovery | none`.
|
||
Семантика:
|
||
- не-alerting & за порогом → **ALERT** (один на пересечение);
|
||
- alerting & за порогом & cooldown истёк → **REALERT**;
|
||
- alerting & за порогом & в cooldown → **NONE** (анти-спам);
|
||
- alerting & вернулось в норму → **RECOVERY**;
|
||
- не-alerting & в норме → **NONE**.
|
||
Состояние порога (alerting/last_alert_at) — per-signal, in-memory (best-effort; рестарт sidecar
|
||
сбрасывает → корректно повторно алертит ещё стоящую проблему, как `disk_watchdog`). Хранилище
|
||
состояния/порогов (in-memory vs файл/иное) — **решение архитектора**.
|
||
|
||
### FR-8 — Независимый Telegram-транспорт (BR-8, NFR-4)
|
||
Отправка через собственный код sidecar (свой `<notify>`), читающий **свои** `bot_token`/`chat_id`
|
||
из env. **Запрещено** импортировать/вызывать `src/notifications.py` или использовать токен/функции
|
||
орка (иначе падение орка утянет алерт-канал). `disable_web_page_preview`/`parse_mode` — по
|
||
усмотрению; сообщение содержит суть алерта (сигнал, значение, порог, хост/контейнер).
|
||
|
||
### FR-9 — Анти-дубль диск-алерта (BR-10)
|
||
Диск уже алертит `disk_watchdog` (ORCH-063, порог 85%, Telegram орка). F1b **не должен** слать
|
||
второй диск-алерт на то же событие. **Владельца диск-алерта выбирает архитектор** (варианты:
|
||
sidecar становится единственным владельцем и внутренний `disk_watchdog` остаётся как fallback на
|
||
случай down-канала орка; ИЛИ sidecar не дублирует диск, оставляя его за ORCH-063). ТЗ фиксирует
|
||
инвариант: **на одно событие переполнения диска — не более одного алерта**, решение и его обоснование —
|
||
в `06-adr/`.
|
||
|
||
### FR-10 — Управляемость (NFR-5)
|
||
Kill-switch (env): выключен → sidecar не стартует / инертен, нулевой эффект на орк и конвейер.
|
||
Пороги (диск, память, agent-завис N мин, длина очереди, и т.п.), интервал тика, таймауты, cooldown —
|
||
из env (`.env.example` — канон).
|
||
|
||
### FR-11 — never-raise (NFR-3)
|
||
Три уровня: per-source (битый источник деградирует один сигнал, прочие собираются), per-tick (внешний
|
||
try/except цикла), per-send (обёрнутая отправка). Демон не падает от ошибки сбора/сети/парсинга.
|
||
|
||
## 4. Изменения API
|
||
|
||
**Нет** изменений API орка. Sidecar — **клиент** существующего `GET /metrics` (F1a, ORCH-099). Орк
|
||
новых эндпоинтов не получает. Sidecar собственного входящего HTTP-API не обязан иметь (опциональный
|
||
liveness-эндпоинт самого sidecar — на усмотрение архитектора, вне обязательного объёма).
|
||
|
||
## 5. Изменения схемы БД
|
||
|
||
**Нет.** Sidecar **не пишет** в БД орка (NFR-4) и не имеет своей БД (тонкий стек, C-3). Состояние
|
||
порогов — in-memory best-effort (FR-7). Журнал уроков (F2, БД орка) — отдельная задача, не F1b.
|
||
|
||
## 6. Требования к новым/изменённым QG checks
|
||
|
||
**Нет.** F1b живёт **вне** процесса орка и **вне** конвейера Quality Gate. `QG_CHECKS` / `check_*` /
|
||
`STAGE_TRANSITIONS` — **не тронуты** (по образцу operational-демонов `disk_watchdog`/`reaper`/
|
||
`reconciler`, которые тоже не являются Quality Gate). Sidecar — операционный наблюдатель, не гейт.
|
||
|
||
## 7. Совместимость / регресс
|
||
|
||
- **Обратная совместимость:** изменения **аддитивны** — новая папка `watchdog/`, новый сервис в
|
||
compose, новые ключи в `.env.example`. Существующий орк-контейнер и его поведение — без изменений.
|
||
- **Kill-switch:** выключенный sidecar = нулевой эффект (не стартует), полная обратимость (NFR-5).
|
||
- **Область раската:** только инфраструктура наблюдения; конвейер всех проектов не затронут
|
||
(self-hosting-безопасно, NFR-4).
|
||
- **Регресс:** существующий `pytest tests/` остаётся зелёным; новые тесты sidecar добавляются
|
||
изолированно (FR — чистые функции тестируемы без контейнера/таймера, образец
|
||
`tests/` для `disk_watchdog.decide`).
|
||
- **Разовое инфра-предусловие** (не код): добавить сервис в compose + создать bot/chat watchdog +
|
||
первый запуск на хосте (Слава/Стрим). Зафиксировать в `07-infra-requirements.md`. Отсутствие
|
||
bot/chat watchdog = sidecar не шлёт (fail-safe, логирует), но не падает.
|