Files
orchestrator/docs/architecture/adr/adr-0033-sidecar-watchdog.md

7.7 KiB
Raw Blame History

work_item, stage, author_agent, status, created_at, model_used
work_item stage author_agent status created_at model_used
ORCH-100 architecture architect proposed 2026-06-10 claude-opus-4-8

adr-0033: Sidecar-watchdog F1b — мозг мониторинга в отдельном контейнере

  • Статус: proposed
  • Дата: 2026-06-10
  • Задача: ORCH-100 (FND/F1b)
  • Детальный ADR: docs/work-items/ORCH-100/06-adr/ADR-001-sidecar-watchdog.md
  • Парный ADR: adr-0030 (F1a /metrics — источник сырья)

Контекст

Домен 0 «Фундамент» эпика автономного саморазвития, рамка наблюдаемости заказчика: наблюдатель отделён от наблюдаемого. F1a (adr-0030) отдаёт read-only GET /metricsтолько сырьё. F1b — мозг: читает сырьё, дополняет внешними сигналами (хост/контейнеры/зависимости), решает по порогам, алертит. Частичные стражи (disk_watchdog/reaper/reconciler) живут ВНУТРИ процесса орка — орк завис/упал ⇒ они мертвы, платформа слепа в критический момент. Рамки: C-1 (отдельный контейнер, код в watchdog/), C-2 (без внешнего плеча — принятый риск), C-3 (тонкий стек, НЕ Grafana/Prometheus; хост впритык). Критический инвариант: орк лёг ⇒ /metrics недоступен = сам сигнал тревоги.

Решение

Новая папка watchdog/тонкий Python-3.12-stdlib демон (без сторонних зависимостей), отдельный образ watchdog/Dockerfile + сервис orchestrator-watchdog в docker-compose.yml (network_mode: host, read-only docker.sock, mem_limit: 128m, restart: unless-stopped). Тик: (1) GET /metrics; (2) хост (диск/inode/память/CPU, stdlib); (3) статусы контейнеров через read-only docker.sock (GET-only — без docker SDK); (4) пинг Plane/Gitea/Anthropic. Сигналы проходят через обобщённую чистую decide(signal_active, prev, now, cooldown) -> alert|realert|recovery|none (генерализация disk_watchdog.decide_action; per-signal in-memory AlertState). Алерт — в собственный Telegram- канал sidecar (свои WATCHDOG_TG_*; НЕ импорт src/notifications.py). Особый сигнал — /metrics не отвечает → orch_down. Всё never-raise (per-source/per-tick/per-send), под kill-switch WATCHDOG_ENABLED, строго read-only к наблюдаемому. src/**/STAGE_TRANSITIONS/QG_CHECKS/ check_*/схема БД орка — не тронуты (F1b вне процесса орка и вне конвейера QG).

  • Стек — Python stdlib (urllib, socket+http.client для docker.sock, shutil.disk_usage, /proc/meminfo); pytest на чистые функции. Отвергнуты Go / docker SDK / Prometheus (C-3).
  • Реестр сигналовorch_down (K подряд неудачных опросов), host_mem/host_disk_crit, agent_hungcpu_ticks/clk_tckgenerated_at < floor при растущем runtime_s; нужно 2 опроса — sidecar stateful-арбитр), stage_stuck (age_in_stage_s), job_failed (edge), queue_depth, container_down (per name), dep_down (per name). Пороги/интервалы/URL — из env.
  • Владелец диск-алерта (BR-10) — штатные 85% остаются за внутренним disk_watchdog (ORCH-063, канал орка) ⇒ нулевой дубль по построению; sidecar покрывает провал «орк+disk_watchdog мертвы» через orch_down, плюс opt-in (default off) независимый критический потолок host_disk_crit (97%) — другое событие/канал, не повтор 85%.
  • Толерантность контракта — неизвестные ключи /metrics игнорируются, отсутствие опционального не ошибка, рост schema_version → warning (зеркало аддитивной политики adr-0030).
  • Kill-switch WATCHDOG_ENABLED=false → демон инертен (idle-loop, не exit) ⇒ нулевой эффект.

Альтернативы

  • Go / docker SDK / requests — отклонено: вес/вторая цепочка против C-3 и консистентности с disk_watchdog.
  • Prometheus/Grafana/TSDB — отклонено: прямой запрет C-3.
  • Sidecar — единственный владелец диска — отклонено: потеря покрытия, когда сам sidecar/Docker недоступен; выбрана связка primary disk_watchdog + opt-in ceiling.
  • Push из орка в sidecar — отклонено: зависший орк не пушит; pull падает = сам сигнал orch_down.
  • bridge + host.docker.internal — отклонено: на Linux ненадёжно; network_mode: host проще.
  • Своя БД/файл порогов — отклонено: C-3; in-memory best-effort достаточно (как disk_watchdog).

Последствия

  • Внешний мозг мониторинга переживает падение орка; orch_down делает наблюдателя громче в инцидент.
  • Строго read-only + независимый канал + never-raise ⇒ self-hosting-безопасно (enduro не затронут); падение sidecar не влияет на конвейер.
  • Аддитивно/обратимо: src/**/гейты/схема байт-в-байт; kill-switch → нулевая регрессия; дубль диска исключён структурно.
  • Плата: новый контейнер на впритык-хосте (mem_limit: 128m + замер RSS на staging обязательны); C-2 (падёт хост → молчит и sidecar); новая поверхность совместимости /metrics↔F1b (толерантный парсинг + единый репо контракта); CPU-liveness Linux-специфичен.
  • Топология меняется (новый контейнер) → 07-infra-requirements.md; схема БД не меняется → 08 = N/A. Новый компонент + контейнер + канал → arch:major-change; прод-выкат через staging-гейт (8501), деплой sidecar НЕ рестартит прод-контейнер.
  • Откат: не запускать сервис / WATCHDOG_ENABLED=false (мгновенный) или удаление watchdog/ + сервиса + env — без следов в БД/схеме.

Связи

adr-0030 (F1a /metrics — парный источник сырья; контракт cpu_ticks/clk_tck/generated_at/ schema_version), adr-0024 (disk_watchdog — образец решающей функции/never-raise + владелец диск-алерта), adr-0025 (build-cache-pruner — паттерн «вторая половина»), adr-0017 (serial_gate — leaf snapshot()/never-raise), adr-0011 (job-reaper — pid/liveness-семантика). Прямой источник — F1a (GET /metrics); F1b — его потребитель.