architect(ET): auto-commit from architect run_id=491
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
- **Job-reaper** (`src/job_reaper.py`, ORCH-065 — [adr-0011](adr/adr-0011-job-reaper-lease-reclaim.md)) — фоновый daemon-поток (каркас `reconciler`), стартует/останавливается в `main.lifespan` (после `reconciler.start()` / перед `worker.stop()`). Детектирует «мёртвый» `running`-job **без рестарта** процесса (Tier-1 мёртвый `jobs.pid` после `reaper_dead_ticks` тиков; Tier-2 `agent_runs.exit_code` записан, а job ещё `running`; Tier-3 backstop `reaper_max_running_s`) и приводит строку к корректному статусу через те же контракты (`_try_advance_stage`/`_finalize_job`, gate-driven; exit≠0/неизвестно → `attempts<max`→`queued`, иначе `failed`+Telegram). Атомарный reap-claim (guard `status='running'`) совместим со стартовым `requeue_running_jobs`. Тот же поток периодически делает проактивный реклейм stale/dead merge-lease (см. ниже). never-raise; kill-switch `ORCH_REAPER_ENABLED`; снимок в `GET /queue` (блок `reaper`).
|
||||
- **Reconciler** (`src/reconciler.py`, ORCH-053 — реализовано, [adr-0007](adr/adr-0007-reconciler.md)) — фоновый daemon-поток (паттерн `queue_worker`), стартует/останавливается в `main.lifespan` (после `worker.start()` / перед `worker.stop()`). Реконсилирует рассинхрон «источник истины ≠ стадия задачи» при потерянном webhook. F-1 gate-side (продвигает застрявшую стадию по локальной БД через штатный `advance_stage(..., finished_agent=None)`), F-2 plane-side (опрос Plane API → `handle_*` из `plane.py`), F-3 (БД-fallback `sha→branch` в `handle_ci_status`). Источник истины — гейт/Plane, не событие; идемпотентность (active-job guard + atomic-claim + grace); kill-switch `ORCH_RECONCILE_ENABLED`. `analysis` F-1 не трогает (человеческий гейт). F-1 также пропускает escalated (retry≥лимита) и Blocked/Needs-Input задачи (ORCH-060). Наблюдаемость — блок `reconcile` в `GET /queue`.
|
||||
- **Disk-watchdog** (`src/disk_watchdog.py`, ORCH-063 — [adr-0024](adr/adr-0024-disk-watchdog.md)) — фоновый daemon-поток (каркас `reconciler`/`job_reaper`), стартует/останавливается в `main.lifespan` (старт последним — после `reaper.start()`; стоп первым в reverse-порядке; гард `disk_monitor_enabled`). Каждые `disk_monitor_interval_s` (дефолт 300с) меряет заполнение **хост-ФС** по смонтированным bind-путям (`/repos`, `/app/data`) через stdlib `shutil.disk_usage` (не overlay `/` контейнера, не субпроцесс `df`; дедуп путей по `st_dev`). Решение об алерте — pure-функция `decide_action(used_pct, threshold, prev_state, now, realert_s)`: алерт на пересечении порога (дефолт **85%**), cooldown-повтор `disk_monitor_realert_s` (анти-спам, не на каждом тике), однократный recovery при возврате ниже порога. Алерт — `send_telegram` (notifying, best-effort). Состояние анти-спама — in-memory (без миграции БД). never-raise (per-path/per-tick/per-send); только читает и уведомляет — не трогает диск/контейнер, не рестартит прод (self-hosting безопасность). Kill-switch `ORCH_DISK_MONITOR_ENABLED`; снимок — блок `disk_monitor` в `GET /queue` (`enabled`/`threshold_pct`/`interval_s`/`realert_s`/`paths`[`used_pct`/`free_gb`/`alerting`/`last_alert_at`]). `STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД — не тронуты. Детали — `docs/work-items/ORCH-063/06-adr/ADR-001-disk-watchdog.md`.
|
||||
- **Build-cache-pruner** (`src/build_cache_pruner.py`, ORCH-062 — [adr-0025](adr/adr-0025-build-cache-pruner.md)) — фоновый daemon-поток (каркас `disk_watchdog`), стартует/останавливается в `main.lifespan` (старт последним — после `disk_watchdog.start()`; стоп первым в reverse; гард `build_cache_prune_enabled`). «Вторая половина» disk-watchdog: **watchdog сигналит — pruner убирает**. Каждые `build_cache_prune_interval_s` (дефолт 21600с = 6ч) выполняет **строго `docker builder prune -f --filter until=<until>`** (BuildKit GC; дефолт `until=24h` — удаляет build cache старше суток, тёплый кэш сохраняет; `-a` опционально, только в паре с фильтром). Затрагивает **только** build cache — НЕ образы/контейнеры; рестарт docker daemon/прода не выполняется (self-hosting безопасность). В контейнере нет `docker` CLI (`Dockerfile:11`), поэтому уборка идёт **на хосте через ssh** каналом `deploy_ssh_user@deploy_ssh_host` (как `image_freshness`/`self_deploy`); пустой `deploy_ssh_host` → тик no-op (скоуп на self-host). never-raise (per-команда/per-tick); учёт результата in-memory (без миграции БД). Kill-switch `ORCH_BUILD_CACHE_PRUNE_ENABLED`; снимок — блок `build_cache_prune` в `GET /queue` (`enabled`/`interval_s`/`until`/`last_run_ts`/`last_reclaimed`/`last_error`). `STAGE_TRANSITIONS`/`QG_CHECKS`/схема БД — не тронуты. Детали — `docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md`.
|
||||
- **Notifications / Live-tracker** (`src/notifications.py`, ORCH-042/ORCH-067) — ОДНА live-карточка на задачу (`update_task_tracker`), обновляется на каждом переходе. Режим `ORCH_TRACKER_MODE` (дефолт `bump` с ORCH-067: delete+silent send+repoint внизу чата; `edit` — правка на месте). Карточка несёт строку Plane-статуса `📍 …` (оффлайн-ядро `plane_status_label` + best-effort live-overlay `_live_plane_branch_override`, kill-switch `ORCH_TRACKER_LIVE_STATUS`) и кликабельный номер задачи (`plane_issue_link`/`link_for` → ссылка в Plane, fail-safe на сырой номер). **ORCH-080:** оба низкоуровневых примитива (`send_telegram`/`edit_telegram`) шлют payload с `disable_web_page_preview: True` — Telegram больше не разворачивает баннер link-preview Plane под карточкой/уведомлениями; `parse_mode: HTML` сохранён (ссылка остаётся кликабельной), безусловно без kill-switch. Все алерты, упоминающие `work_item_id`, делают номер кликабельным. **ORCH-087:** bump ведёт авторитетный леджер всех созданных карточек (`tracker_messages`, `deleted_at IS NULL` = жива) и на каждом обновлении зачищает ВСЕ незакрытые mid (а не только скаляр `tracker_message_id`) → класс «замёрзшая сирота» устранён; строка стадии несёт фактический эффорт рядом с моделью (`· {model} · {effort}`, колонка `agent_runs.effort`, стамп в `launcher._spawn`); done-строка времени переписана на три подписанных метрики `⏱️ Агенты · твоё{~cap} · общее с ожиданием` (кап `ORCH_TRACKER_BRD_REVIEW_CAP_S`); deploy-цикл дополнен overlay-ключом `confirm_deploy`. Контракт всего компонента — never raises; карточка всегда silent. Детали — [internals.md](internals.md) §7 и [ADR-001](../work-items/ORCH-087/06-adr/ADR-001-tracker-orphan-cleanup.md).
|
||||
- **Project Registry** (`src/projects.py`, ORCH-6) — Plane project id → repo + prefix; фильтрация вебхуков по проекту.
|
||||
- **Plane Sync** (`src/plane_sync.py`) — синхронизация статусов/комментариев в Plane. Резолв статусов проекта `get_project_states` (ORCH-10) кэширует `{logical_key→uuid}` per-project; **ORCH-068** добавляет в кэш-запись `{uuid→group}` (для терминал-исключения F-2) и **TTL** `ORCH_PLANE_STATES_TTL_S` (дефолт 300с; `0` → прежний lifetime-кэш) — устаревший набор статусов самозалечивается без рестарта процесса через существующий `reload_project_states()` (баг кэша после появления нового Plane-статуса). Форма возврата `get_project_states` неизменна (обратная совместимость).
|
||||
|
||||
@@ -27,6 +27,10 @@ Per-work-item решения живут в `docs/work-items/<id>/06-adr/ADR-NNN-
|
||||
| adr-0019 | Стандарт документов конвейера (PIPELINE_DOCS, слой 1) | accepted | 2026-06-09 | ORCH-075 |
|
||||
| adr-0020 | Единый frontmatter-контракт + спека handoff (reader/writer/валидатор) | accepted | 2026-06-09 | ORCH-076 |
|
||||
| adr-0021 | Канон Anthropic для агент-промптов + эмиссия frontmatter-схемы 52c | proposed | 2026-06-09 | ORCH-077 |
|
||||
| adr-0022 | Стандарт трассировочных маркеров `ORCH-NNN` | accepted | 2026-06-09 | ORCH-078 |
|
||||
| adr-0023 | Обзорная ось reviewer + закрытие эпика 52 | accepted | 2026-06-09 | ORCH-079 |
|
||||
| adr-0024 | Disk-watchdog — heartbeat-сигнал заполнения хост-ФС | proposed | 2026-06-09 | ORCH-063 |
|
||||
| adr-0025 | Build-cache-pruner — авто-prune docker build cache на хосте | proposed | 2026-06-09 | ORCH-062 |
|
||||
|
||||
> ⚠️ Историческая коллизия: номер `0007` занят двумя файлами —
|
||||
> `adr-0007-reconciler.md` (ORCH-053) и `adr-0007-executable-self-deploy.md`
|
||||
@@ -36,6 +40,8 @@ Per-work-item решения живут в `docs/work-items/<id>/06-adr/ADR-NNN-
|
||||
> adr-0016 **amends** adr-0013/0014 (гарантирует открытый код-PR перед merge_pr, ORCH-082).
|
||||
> adr-0020 реализует машинный слой к adr-0019 (ORCH-52b→52c).
|
||||
> adr-0021 реализует слой промптов к adr-0019/0020 (ORCH-52d — замыкает эпик 52).
|
||||
> adr-0025 **комплементарен** adr-0024 (watchdog сигналит о росте диска — pruner убирает
|
||||
> доминирующего «пожирателя», docker build cache).
|
||||
|
||||
## Формат
|
||||
**Контекст → Решение → Альтернативы → Последствия → Связи.** Статус: proposed / accepted / superseded.
|
||||
|
||||
86
docs/architecture/adr/adr-0025-build-cache-pruner.md
Normal file
86
docs/architecture/adr/adr-0025-build-cache-pruner.md
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
work_item: ORCH-062
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# adr-0025: Build-cache-pruner — фоновый heartbeat-демон авто-уборки docker build cache на хосте
|
||||
|
||||
> Сквозной (cross-cutting) ADR: вводит **новый фоновый компонент** оркестратора в ряду
|
||||
> `reconciler` (adr-0007), `job_reaper` (adr-0011) и `disk_watchdog` (adr-0024). Детальное
|
||||
> решение задачи — `docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md`.
|
||||
|
||||
## Статус
|
||||
Proposed (ORCH-062)
|
||||
|
||||
## Контекст
|
||||
|
||||
07.06.2026 диск хоста mva154 тихо дорос до 100% и положил **весь self-hosting-конвейер всех
|
||||
проектов** (один прод-инстанс `orchestrator` на общей БД/очереди). Доминирующий «пожиратель» —
|
||||
**docker build cache** (≈11 ГБ от частых пересборок прод/staging-образов). `disk_watchdog`
|
||||
(adr-0024, ORCH-063) ввёл **сигнал** о заполнении (Telegram ≥85%) и явно отложил авто-очистку в
|
||||
отдельную задачу. ORCH-062 — эта задача: **автоматическое освобождение build cache**, чтобы
|
||||
инцидент не повторялся без оператора.
|
||||
|
||||
Сверено по коду: контейнер `orchestrator` **не содержит docker CLI** (`Dockerfile:11` — только
|
||||
`openssh-client git curl`); host-docker-операции приложение уже делает **через ssh на хост**
|
||||
(`image_freshness.image_revision`, `self_deploy` Phase B), канал `deploy_ssh_user@deploy_ssh_host`
|
||||
настроен. У оркестратора три проверенных фоновых daemon-потока с единым каркасом.
|
||||
|
||||
## Решение
|
||||
|
||||
Вводится четвёртый фоновый компонент **build-cache-pruner** (`src/build_cache_pruner.py`):
|
||||
- **Калька каркаса** `disk_watchdog`/`reconciler`/`reaper`: daemon-поток, чистый стоп через
|
||||
`_stop.wait(interval)`, контракт `start()`/`stop(timeout)`/`status()`, старт/стоп в
|
||||
`main.lifespan` (старт последним — после `disk_watchdog.start()`; стоп первым в reverse),
|
||||
наблюдаемость — аддитивный блок `build_cache_prune` в `GET /queue`. Leaf-модуль (без обратных
|
||||
зависимостей на `stage_engine`/`stages`/`qg`).
|
||||
- **Уборка — строго `docker builder prune -f --filter until=<until>`** (BuildKit GC, дефолт
|
||||
`until=24h`): удаляется только старый build cache, тёплый ≤24ч сохраняется. `-a` — опционально и
|
||||
только в паре с возрастным фильтром. **Запрещены** `docker image prune`/`system prune`/удаление
|
||||
образов запущенных сервисов/остановка-рестарт контейнеров.
|
||||
- **Исполнение на хосте через ssh** (CLI в контейнере нет): `ssh deploy_ssh_user@deploy_ssh_host
|
||||
"docker builder prune …"`, bounded таймаутом. **Нет ssh-таргета → тик no-op** → фича
|
||||
естественно скоупится на self-hosting-прод.
|
||||
- **Конфиг/kill-switch** (`ORCH_BUILD_CACHE_PRUNE_*`, дефолты безопасные): `enabled` (дефолт
|
||||
`true`), `interval_s` (6ч), `until` (`24h`), `all` (`false`), `timeout_s`, `notify_min_gb`.
|
||||
Валидаторы по образцу `disk_monitor_*` (невалид → лог + дефолт).
|
||||
- **Сигнал + лечение как пара:** disk_watchdog сигналит о росте диска, build-cache-pruner убирает
|
||||
доминирующего «пожирателя» — две половины одной операционной защиты.
|
||||
|
||||
**Инварианты:** `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, `src/stage_engine.py`, схема БД
|
||||
— **не меняются** (pruner — эксплуатационный демон, не Quality Gate, как watchdog/reaper). Без
|
||||
миграции БД (учёт результата in-memory, best-effort). never-raise per-команда/per-tick. Уборка
|
||||
**никогда** не рестартит docker daemon/прод-контейнер (self-hosting безопасность; рестарт-путь —
|
||||
отвергнутый Вариант B). При выключенном kill-switch — поведение 1:1 как сейчас (нулевая регрессия
|
||||
для enduro-trails).
|
||||
|
||||
## Альтернативы
|
||||
- **host `daemon.json builder.gc.defaultKeepStorage`** — отвергнуто: требует рестарта docker
|
||||
daemon (останавливает ВСЕ контейнеры хоста = групповой self-hosting риск); политика по объёму,
|
||||
не по возрасту; не наблюдаемо в `GET /queue`.
|
||||
- **host-cron** — отвергнуто как основное (оставлено ручным fallback): off-git невидимая инфра,
|
||||
без `/queue`-наблюдаемости, без config-kill-switch, не тестируется.
|
||||
- **raw-HTTP по docker.sock / docker CLI в образе** — отвергнуто: лишний код / раздувание образа
|
||||
против уже существующего ssh-канала.
|
||||
|
||||
## Последствия
|
||||
- **+** Корень инцидента 07.06 устраняется автоматически; тёплый кэш сохранён; без новых
|
||||
зависимостей и без рестарта docker/прода (принцип «всё в Docker, минимум зависимостей»).
|
||||
- **+** Знакомый паттерн фонового демона → низкий риск, наблюдаемость, обратимость, тестируемость.
|
||||
- **−** Зависимость от ssh на хост (как `image_freshness`/`self_deploy`); нет таргета → no-op
|
||||
(наблюдаемо), фича не работает, но ничего не ломает.
|
||||
- **Откат:** `ORCH_BUILD_CACHE_PRUNE_ENABLED=false`; миграций БД нет.
|
||||
|
||||
## Ссылки
|
||||
- Задачный ADR: `docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md`
|
||||
- Инфра/риски: `docs/work-items/ORCH-062/07-infra-requirements.md`,
|
||||
`docs/work-items/ORCH-062/10-tech-risks.md`
|
||||
- Комплемент: [adr-0024-disk-watchdog.md](adr-0024-disk-watchdog.md) (ORCH-063 — сигнал)
|
||||
- Родственные компоненты: [adr-0007-reconciler.md](adr-0007-reconciler.md),
|
||||
[adr-0011-job-reaper-lease-reclaim.md](adr-0011-job-reaper-lease-reclaim.md)
|
||||
- Топология host / env-карта: `docs/operations/INFRA.md`
|
||||
</content>
|
||||
@@ -74,10 +74,30 @@ ADR `docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md` и гл
|
||||
`disk_monitor.enabled=false`; поведение 1:1 как сейчас). Наблюдаемость — блок `disk_monitor` в
|
||||
`GET /queue` (последний замер: `used_pct`/`free_gb`/`alerting`/`last_alert_at` по каждому пути).
|
||||
- **Что делать при алерте:** watchdog **только сигнализирует** — он не трогает диск/контейнер и не
|
||||
рестартит прод (self-hosting безопасность). Освобождение места — **ручная** операция оператора:
|
||||
типовые «пожиратели» — старые worktree-каталоги `/home/slin/repos/_wt/*` завершённых задач,
|
||||
логи, dangling Docker-образы/слои (`docker image prune`, `docker builder prune`). Авто-очистка —
|
||||
вне объёма ORCH-063 (отдельная задача).
|
||||
рестартит прод (self-hosting безопасность). Освобождение **docker build cache** автоматизировано
|
||||
отдельным демоном (ORCH-062, см. ниже); прочие «пожиратели» — старые worktree-каталоги
|
||||
`/home/slin/repos/_wt/*` завершённых задач, логи, dangling-образы (`docker image prune`) —
|
||||
по-прежнему **ручная** операция оператора (авто-уборка этих категорий — вне объёма ORCH-062/063).
|
||||
|
||||
### Build-cache-pruner: авто-prune docker build cache на mva154 (ORCH-062)
|
||||
Доминирующий «пожиратель» в инциденте 07.06.2026 — **docker build cache** (≈11 ГБ от частых
|
||||
пересборок прод/staging-образов). Чтобы он не мог снова заполнить диск **без оператора**, работает
|
||||
фоновый daemon-поток `src/build_cache_pruner.py` (каркас `disk_watchdog`) — «вторая половина»
|
||||
watchdog'а: **watchdog сигналит, pruner убирает**.
|
||||
- **Что делает:** каждые `ORCH_BUILD_CACHE_PRUNE_INTERVAL_S` (дефолт 21600с = 6ч) выполняет
|
||||
**строго `docker builder prune -f --filter until=<until>`** (BuildKit GC; дефолт `until=24h` —
|
||||
удаляется build cache старше суток, тёплый свежий кэш сохраняется). Команда затрагивает **только
|
||||
build cache** — НЕ образы/контейнеры запущенных сервисов; рестарт docker daemon/прода НЕ
|
||||
выполняется (self-hosting безопасность).
|
||||
- **Как исполняется:** в контейнере нет `docker` CLI (образ несёт только `openssh-client git`),
|
||||
поэтому уборка идёт **на хосте через ssh** тем же каналом `ORCH_DEPLOY_SSH_USER@_HOST`, что
|
||||
деплой/`image_freshness`. **Пустой `ORCH_DEPLOY_SSH_HOST` → тик no-op** (фича активна только на
|
||||
self-host, где ssh настроен).
|
||||
- **Как отключить:** `ORCH_BUILD_CACHE_PRUNE_ENABLED=false` (демон не стартует; поведение 1:1 как
|
||||
до ORCH-062). Наблюдаемость — блок `build_cache_prune` в `GET /queue` (`enabled`/`interval_s`/
|
||||
`until`/`last_run_ts`/`last_reclaimed`/`last_error`); never-raise; in-memory учёт (без миграции).
|
||||
- **Ручной fallback** (если ssh-канал недоступен) — host-cron на mva154:
|
||||
`0 */6 * * * docker builder prune -f --filter until=24h` (off-git, процедура Owner).
|
||||
|
||||
## Переменные окружения (карта; значения — в `.env`)
|
||||
|
||||
@@ -117,6 +137,12 @@ ADR `docs/work-items/ORCH-040/06-adr/ADR-001-run-agents-as-host-uid.md` и гл
|
||||
| `ORCH_DISK_MONITOR_THRESHOLD_PCT` | порог заполнения для алерта, %; дефолт `85` (валидация 1..100, иначе → дефолт) |
|
||||
| `ORCH_DISK_MONITOR_REALERT_S` | cooldown повторного алерта, пока выше порога, сек; дефолт `21600` (~6 ч) |
|
||||
| `ORCH_DISK_MONITOR_PATHS` | CSV отслеживаемых **хост**-bind-путей; пусто → `/repos,/app/data` |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_ENABLED` | kill-switch build-cache-pruner (ORCH-062); дефолт `true`. `false` → демон не стартует, поведение 1:1 как до задачи |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_INTERVAL_S` | период тика авто-prune, сек; дефолт `21600` (~6 ч); валидация >0, иначе → дефолт |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_UNTIL` | возраст удержания тёплого кэша (`docker builder prune --filter until=`); дефолт `24h`; валидация `^\d+[smhdw]?$`, иначе → `24h` |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_ALL` | добавить `-a` к prune (только в паре с `until`); дефолт `false` |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_TIMEOUT_S` | таймаут ssh-команды prune, сек; дефолт `120` |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_NOTIFY_MIN_GB` | Telegram при освобождении ≥ N ГБ; дефолт `0` (тихо) |
|
||||
| `DEPLOY_SSH_USER` / `_HOST` / `DEPLOY_HOOK_SCRIPT` | параметры деплой-хука |
|
||||
|
||||
**Секреты — только в `.env` / `.env.staging` на хосте, в гит НЕ коммитятся.** Канон — `.env.example`, `.env.staging.example`.
|
||||
|
||||
206
docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md
Normal file
206
docs/work-items/ORCH-062/06-adr/ADR-001-build-cache-pruner.md
Normal file
@@ -0,0 +1,206 @@
|
||||
---
|
||||
work_item: ORCH-062
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# ADR-001: Авто-prune docker build cache — фоновый heartbeat-демон, выполняющий `docker builder prune` на хосте через ssh
|
||||
|
||||
Work Item: **ORCH-062** — INFRA: авто-prune docker build cache на mva154
|
||||
Стадия: **architecture**
|
||||
Сквозная регистрация: **`docs/architecture/adr/adr-0025-build-cache-pruner.md`** (кросс-каттинг —
|
||||
вводит новый фоновый компонент в ряду `reconciler`/`job_reaper`/`disk_watchdog`).
|
||||
|
||||
## Статус
|
||||
Proposed
|
||||
|
||||
## Контекст
|
||||
|
||||
07.06.2026 хост-диск mva154 тихо дорос до 100% и положил **весь self-hosting-конвейер всех
|
||||
проектов** (один прод-инстанс `orchestrator` на общей БД/очереди обслуживает и `enduro-trails`, и
|
||||
`orchestrator`). Доминирующий «пожиратель» — **docker build cache** (≈11 ГБ), накопленный частыми
|
||||
пересборками (`docker compose up -d --build` при прод-деплое; пересборка staging-образа
|
||||
`--profile staging`; build-once retag за `check_staging_image_fresh`, ORCH-058). ORCH-063 ввёл
|
||||
disk-watchdog, который **только сигнализирует** (Telegram-алерт ≥85%) и явно отложил авто-очистку в
|
||||
отдельную задачу. **ORCH-062 — эта задача.**
|
||||
|
||||
BRD/ТЗ ставят развилку реализации (`06-adr` решает):
|
||||
- **A** — heartbeat-демон в приложении (`src/build_cache_pruner.py`), 1:1 на `src/disk_watchdog.py`.
|
||||
- **B** — host `daemon.json builder.gc.defaultKeepStorage` (BuildKit GC, инфра-процедура Owner).
|
||||
- **C** — host-cron `docker builder prune -af --filter until=24h` (инфра-процедура Owner).
|
||||
|
||||
**Факты, сверенные с кодом (важно для выбора):**
|
||||
- **Контейнер `orchestrator` НЕ содержит `docker` CLI.** `Dockerfile:11` ставит только
|
||||
`openssh-client git curl ca-certificates`. `src/image_freshness.py::image_revision` прямо
|
||||
фиксирует: *«`docker` lives on the HOST (the container ships only `openssh-client git`), so when
|
||||
`ssh_target` is given the inspect runs over ssh»*. → Любая docker-операция приложения над хостом
|
||||
идёт **через ssh на хост** (`ssh deploy_ssh_user@deploy_ssh_host docker …`), как уже делают
|
||||
`image_freshness` и `self_deploy` (Phase B). Допущение BRD A-1 («docker.sock смонтирован →
|
||||
приложение может вызвать `docker builder prune`») верно на уровне сокета, но **не** даёт готового
|
||||
CLI; raw-HTTP-over-UDS — лишний код против существующего ssh-канала.
|
||||
- В оркестраторе уже три проверенных фоновых daemon-потока с единым каркасом
|
||||
(`threading.Thread(daemon=True)` + `threading.Event`, `start()/stop(timeout)/status()`,
|
||||
per-tick never-raise, kill-switch, снимок в `GET /queue`): `reconciler` (ORCH-053),
|
||||
`job_reaper` (ORCH-065), `disk_watchdog` (ORCH-063, `src/disk_watchdog.py`).
|
||||
- ssh-канал на хост сконфигурирован и доступен: `settings.deploy_ssh_user` (дефолт `slin`),
|
||||
`settings.deploy_ssh_host`; ключи проброшены ro (`~/.orchestrator-ssh → /home/slin/.ssh`,
|
||||
ORCH-040); `slin` — в группе docker (деплой-хук запускает `docker compose` на хосте).
|
||||
- `docker builder prune` по контракту BuildKit затрагивает **только build cache**, не
|
||||
останавливает контейнеры и не удаляет образы запущенных сервисов (основа BR-3).
|
||||
|
||||
## Решение
|
||||
|
||||
### Сводка
|
||||
|
||||
Выбран **Вариант A — фоновый heartbeat-демон `src/build_cache_pruner.py`**, смоделированный
|
||||
**1:1 на `src/disk_watchdog.py`** (тот же каркас, контракт, kill-switch, never-raise, блок в
|
||||
`GET /queue`), который **периодически выполняет `docker builder prune` на ХОСТЕ через ssh** —
|
||||
тем же каналом `deploy_ssh_user@deploy_ssh_host`, что уже используют `image_freshness` и
|
||||
`self_deploy`. Это «вторая половина» disk-watchdog: **watchdog сигналит — pruner убирает**.
|
||||
|
||||
Варианты B и C отклонены (см. «Альтернативы»). Вариант C сохраняется как
|
||||
**задокументированный ручной fallback** в `07-infra-requirements.md` на случай, если ssh-канал
|
||||
недоступен.
|
||||
|
||||
### D1 — Механизм: фоновый демон приложения (A), не host-инфра (B/C) — BR-1/FR-1
|
||||
|
||||
Новый **leaf**-модуль `src/build_cache_pruner.py` (без обратных зависимостей на
|
||||
`stage_engine`/`stages`/`qg`, как `disk_watchdog`/`serial_gate`/`task_deps`). Класс
|
||||
`BuildCachePruner` с каркасом `disk_watchdog`: daemon-поток, чистый стоп через
|
||||
`_stop.wait(interval)`, контракт `start()/stop(timeout)/status()`, модульный singleton
|
||||
`build_cache_pruner`. Каждые `build_cache_prune_interval_s` (дефолт **21600с = 6ч**, NFR-4
|
||||
«порядка часов») один тик выполняет уборку. Выбор A над B/C даёт: наблюдаемость в `GET /queue`,
|
||||
kill-switch из конфига, golden-source-в-git, юнит-тесты, и **симметрию с disk-watchdog** (один
|
||||
паттерн на два смежных эксплуатационных демона) — это снижает стоимость сопровождения и
|
||||
когнитивную нагрузку следующего агента.
|
||||
|
||||
### D2 — Команда и политика удержания: строго BuildKit GC с возрастным фильтром — BR-2/BR-3/FR-2/FR-3
|
||||
|
||||
- Команда уборки — **строго `docker builder prune -f --filter until=<until>`** (BuildKit GC).
|
||||
Дефолт `until=24h` (`build_cache_prune_until`, ориентир из бизнес-запроса): удаляется build
|
||||
cache **старше 24ч**, свежий тёплый кэш недавних сборок сохраняется (BR-2/AC-2).
|
||||
- Флаг `-a/--all` — **только** опционально (`build_cache_prune_all`, дефолт `False`) и **всегда в
|
||||
паре с возрастным фильтром**; «снести весь кэш» (`prune -af` без `until`) запрещён дефолтом.
|
||||
- **Жёстко запрещены** `docker image prune`, `docker system prune`, любое удаление образов
|
||||
запущенных сервисов, любая остановка/рестарт контейнеров. Затрагивается **только** build cache
|
||||
(BR-3/AC-3). Уборка **никогда** не рестартит/не роняет прод-контейнер `orchestrator`
|
||||
(групповой риск self-hosting).
|
||||
|
||||
### D3 — Канал исполнения: ssh на хост (CLI в контейнере нет) — BR-3/FR-3/NFR-1
|
||||
|
||||
- Уборка исполняется на хосте: `ssh -o StrictHostKeyChecking=no <deploy_ssh_user@deploy_ssh_host>
|
||||
"docker builder prune -f --filter until=<until>"`, по образцу `image_freshness.image_revision`
|
||||
(`ssh_target`-ветка). Это где **физически** живёт build cache (host docker daemon).
|
||||
- **Нет ssh-таргета (`deploy_ssh_host` пуст) → тик no-op** (лог + `status()` отражает причину).
|
||||
Это естественно **скоупит** фичу на self-hosting-прод (где ssh настроен) и делает дефолт
|
||||
безопасным для любого окружения без host-доступа — параллель тому, как `self_deploy`/
|
||||
`image_freshness` деградируют без `_ssh_target()`.
|
||||
- Вызов **bounded** таймаутом (`build_cache_prune_timeout_s`, дефлот 120с) и **неблокирующий**
|
||||
конвейер (отдельный daemon-поток). Любой сбой — ниже D6.
|
||||
|
||||
### D4 — Конфиг, kill-switch, дефолты — BR-5/BR-6/FR-5/NFR-3
|
||||
|
||||
Новый блок флагов в `src/config.py` рядом с `disk_monitor_*` (env-префикс `ORCH_BUILD_CACHE_PRUNE_*`):
|
||||
|
||||
| Поле (`settings.*`) | env | Дефолт | Назначение |
|
||||
|---|---|---|---|
|
||||
| `build_cache_prune_enabled` | `ORCH_BUILD_CACHE_PRUNE_ENABLED` | `True` | kill-switch; `False` → демон не стартует, поведение 1:1 как до задачи (NFR-3) |
|
||||
| `build_cache_prune_interval_s` | `ORCH_BUILD_CACHE_PRUNE_INTERVAL_S` | `21600` (6ч) | период тика, сек |
|
||||
| `build_cache_prune_until` | `ORCH_BUILD_CACHE_PRUNE_UNTIL` | `24h` | возраст удержания (`--filter until=`) |
|
||||
| `build_cache_prune_all` | `ORCH_BUILD_CACHE_PRUNE_ALL` | `False` | добавить `-a` (только в паре с `until`) |
|
||||
| `build_cache_prune_timeout_s` | `ORCH_BUILD_CACHE_PRUNE_TIMEOUT_S` | `120` | таймаут ssh-команды, сек |
|
||||
| `build_cache_prune_notify_min_gb` | `ORCH_BUILD_CACHE_PRUNE_NOTIFY_MIN_GB` | `0` | Telegram при освобождении ≥ N ГБ; `0` → тихо (без нотификаций) |
|
||||
|
||||
**Дефолт `enabled=True` (обоснование, не самоочевидно):** (а) бизнес-цель BR-1 — авто-предотвращение
|
||||
инцидента *без ручного вмешательства*; дефолт `False` означал бы, что оператор обязан вспомнить и
|
||||
включить флаг, что подрывает саму задачу; (б) операция документированно-безопасна (только build
|
||||
cache, never images/containers/restart — D2/A-2); (в) при отсутствии ssh-таргета тик no-op (D3) →
|
||||
фича безопасна-по-построению в любом окружении без host-доступа; (г) полностью обратима kill-switch.
|
||||
Это сознательный, явно зафиксированный компромисс «безопасный дефолт vs авто-цель» в пользу
|
||||
авто-цели, при сохранённой обратимости. Параллель: `disk_monitor_enabled` тоже дефолт `True`.
|
||||
|
||||
**Валидаторы** (паттерн `_disk_positive_int`/`_disk_threshold_pct` из `config.py`): невалидный
|
||||
`interval_s`/`timeout_s` (не-int / ≤0) → лог-warning + дефолт; невалидный `until` (не матчит
|
||||
`^\d+[smhdw]?$`) → лог-warning + `24h`. Невалидное значение **никогда** не роняет старт (AC-6).
|
||||
|
||||
### D5 — Наблюдаемость — BR-4/FR-4
|
||||
|
||||
Аддитивный read-only блок `build_cache_prune` в `GET /queue` (как `disk_monitor`):
|
||||
`enabled`, `interval_s`, `until`, `all`, `last_run_ts`, `last_reclaimed` (распарсенное
|
||||
`Total reclaimed space: …` из вывода `docker builder prune`, best-effort), `last_error`
|
||||
(строка причины последнего сбоя/no-op, или `null`). `status()` — never-raise (минимум
|
||||
`{"enabled": …}` при ошибке). Опционально — `send_telegram` при освобождении
|
||||
≥ `notify_min_gb` (по образцу recovery-сообщения watchdog'а; дефолт выключено).
|
||||
|
||||
### D6 — Инварианты и never-raise — NFR-1/NFR-2/NFR-5/FR-6, AC-4/AC-8
|
||||
|
||||
- `STAGE_TRANSITIONS`, реестр `QG_CHECKS`, `check_*`, `_parse_*`, `src/stage_engine.py`, схема БД
|
||||
(`src/db.py`) — **не изменяются**. Pruner — эксплуатационный демон, не Quality Gate (категория
|
||||
`reconciler`/`job_reaper`/`disk_watchdog`).
|
||||
- **Без миграции БД**: учёт «когда убирали в последний раз»/последний результат — **in-memory**,
|
||||
best-effort; сброс при рестарте безопасен (максимум одна лишняя безопасная уборка, NFR-5).
|
||||
- **never-raise на двух уровнях:** per-команда (ненулевой rc / таймаут / `OSError` /
|
||||
недоступность ssh / parsing-ошибка вывода → лог + проглот, тик жив) и per-tick (внешний
|
||||
`try/except` в `_run`, как `disk_watchdog._run`). Фоновый цикл и конвейер не падают.
|
||||
- **Self-hosting:** ssh выполняет `docker builder prune` на хосте под `slin` (в группе docker);
|
||||
команда не трогает образы/контейнеры запущенных сервисов; прод не рестартится. Обслуживание
|
||||
`enduro-trails` в общем инстансе не затронуто.
|
||||
|
||||
### D7 — Жизненный цикл (`main.lifespan`)
|
||||
|
||||
Старт демона — **последним**, сразу после `disk_watchdog.start()` (строки ~113–114 `main.py`);
|
||||
стоп — **первым** в reverse-порядке, перед `disk_watchdog.stop()`. `start()` чтит kill-switch
|
||||
(no-op при `enabled=False`), как `DiskWatchdog.start()`.
|
||||
|
||||
## Альтернативы
|
||||
|
||||
- **Вариант B — host `daemon.json builder.gc.defaultKeepStorage`** — **отвергнуто:** применение
|
||||
конфигурации требует **рестарта docker daemon** на mva154, что останавливает **ВСЕ** контейнеры
|
||||
хоста (прод `orchestrator` + всё остальное) → катастрофический self-hosting blast radius (BRD
|
||||
C-1/R-3). Дополнительно: политика BuildKit GC — по **объёму** (`defaultKeepStorage`), а не по
|
||||
возрасту (BR-2 хочет `until=24h`); состояние не наблюдаемо в `GET /queue` (только хостовый
|
||||
`docker system df`); конфигурация — off-git host-артефакт.
|
||||
- **Вариант C — host-cron** `docker builder prune -af --filter until=24h` — **отвергнуто как
|
||||
основное** (сохранено как ручной fallback в `07`): off-git невидимая инфра (следующий
|
||||
оператор/агент её не видит), **нет** наблюдаемости в `GET /queue`, **нет** kill-switch из
|
||||
конфига, **не** покрывается `tests/` — ломает принцип self-contained/reproducible/observable,
|
||||
которому следуют остальные демоны.
|
||||
- **A через raw-HTTP по docker.sock (без ssh)** — **отвергнуто:** требует ручного HTTP-over-UDS
|
||||
клиента (chunked-ответы, версионирование API) — лишний код против уже существующего,
|
||||
проверенного ssh-канала `image_freshness`/`self_deploy`.
|
||||
- **A через `docker` CLI, вкомпилированный в образ** — **отвергнуто:** раздувает образ и требует
|
||||
пересборки/рестарта прода ради уборки; ssh-канал на хост уже есть и не трогает образ.
|
||||
|
||||
## Последствия
|
||||
|
||||
- **+** Корень инцидента 07.06 (build cache → 100% диска) устраняется **автоматически**, без
|
||||
ручного вмешательства; тёплый кэш ≤24ч сохранён → штатные пересборки не «холодные».
|
||||
- **+** Знакомый паттерн фонового демона (калька `disk_watchdog`) → низкий риск, наблюдаемость в
|
||||
`GET /queue`, обратимость одним флагом, юнит-тестируемость, golden-source-в-git.
|
||||
- **+** Без новых внешних зависимостей и без рестарта docker daemon/прода (принцип «всё в Docker
|
||||
на одном сервере, минимум зависимостей»); ssh-канал переиспользован.
|
||||
- **−** Зависимость от ssh-доступа на хост (как у `image_freshness`/`self_deploy`); при
|
||||
отсутствии — тик no-op (наблюдаемо в `status().last_error`), фича просто не работает, но ничего
|
||||
не ломает. Митигейшн: документированный host-prerequisite + fallback-cron (`07`).
|
||||
- **−** In-memory учёт результата (без миграции) — допустим для эксплуатационного демона (не SLA).
|
||||
- **Откат:** `ORCH_BUILD_CACHE_PRUNE_ENABLED=false` → демон не стартует, поведение 1:1 как до
|
||||
задачи; миграций БД нет, удалять нечего.
|
||||
|
||||
## Ссылки
|
||||
- BRD: `docs/work-items/ORCH-062/01-brd.md`
|
||||
- TRZ: `docs/work-items/ORCH-062/02-trz.md`
|
||||
- Acceptance: `docs/work-items/ORCH-062/03-acceptance-criteria.md`
|
||||
- Инфра-требования: `docs/work-items/ORCH-062/07-infra-requirements.md`
|
||||
- Тех-риски: `docs/work-items/ORCH-062/10-tech-risks.md`
|
||||
- Сквозной ADR: `docs/architecture/adr/adr-0025-build-cache-pruner.md`
|
||||
- Сверено по коду: `src/disk_watchdog.py` (каркас-образец), `src/image_freshness.py`
|
||||
(`image_revision`/`_ssh_target` — ssh-канал к host docker), `src/config.py`
|
||||
(`disk_monitor_*` + валидаторы, `deploy_ssh_user/host`), `src/main.py`
|
||||
(`lifespan` старт/стоп демонов, `GET /queue`), `Dockerfile:11` (нет docker CLI в образе).
|
||||
- Родственные компоненты: `docs/architecture/adr/adr-0024-disk-watchdog.md` (ORCH-063),
|
||||
`adr-0007-reconciler.md`, `adr-0011-job-reaper-lease-reclaim.md`.
|
||||
</content>
|
||||
</invoke>
|
||||
76
docs/work-items/ORCH-062/07-infra-requirements.md
Normal file
76
docs/work-items/ORCH-062/07-infra-requirements.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
work_item: ORCH-062
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 07 — Инфра-требования: ORCH-062 — авто-prune docker build cache на mva154
|
||||
|
||||
Work Item: **ORCH-062** · Repo: **orchestrator** · Стадия: architecture
|
||||
|
||||
> Решение: **Вариант A** (фоновый демон приложения, `docker builder prune` на хосте через ssh) —
|
||||
> см. `06-adr/ADR-001-build-cache-pruner.md`. Этот файл фиксирует host-prerequisites выбранного
|
||||
> пути и задокументированный ручной fallback (Вариант C, host-cron).
|
||||
|
||||
## I-1. Топология / окружения
|
||||
|
||||
- Без изменений топологии: **новый внутренний фоновый daemon-поток** в существующем прод-контейнере
|
||||
`orchestrator` (8500), наравне с `reconciler`/`job_reaper`/`disk_watchdog`. Новых контейнеров,
|
||||
портов, сетей, томов — **нет**.
|
||||
- Уборка исполняется **на хосте mva154** (host docker daemon — там физически живёт build cache)
|
||||
через уже существующий ssh-канал `deploy_ssh_user@deploy_ssh_host`
|
||||
(по образцу `image_freshness`/`self_deploy` Phase B). В контейнере `docker` CLI **нет**
|
||||
(`Dockerfile:11` — только `openssh-client git curl`), поэтому raw-вызов CLI в контейнере
|
||||
невозможен — только ssh на хост.
|
||||
|
||||
## I-2. Переменные окружения / секреты
|
||||
|
||||
Новые env (дефолты безопасны; полная карта — `docs/operations/INFRA.md`; канон — `.env.example`):
|
||||
|
||||
| env | Дефолт | Назначение |
|
||||
|-----|--------|------------|
|
||||
| `ORCH_BUILD_CACHE_PRUNE_ENABLED` | `true` | kill-switch; `false` → демон не стартует, 1:1 как до задачи |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_INTERVAL_S` | `21600` (6ч) | период тика, сек (валидация >0, иначе → дефолт) |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_UNTIL` | `24h` | возраст удержания тёплого кэша (`--filter until=`); валидация `^\d+[smhdw]?$`, иначе → `24h` |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_ALL` | `false` | добавить `-a` (только в паре с `until`) |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_TIMEOUT_S` | `120` | таймаут ssh-команды, сек |
|
||||
| `ORCH_BUILD_CACHE_PRUNE_NOTIFY_MIN_GB` | `0` | Telegram при освобождении ≥ N ГБ; `0` → тихо |
|
||||
|
||||
- Переиспользуются существующие `ORCH_DEPLOY_SSH_USER` (дефолт `slin`) / `ORCH_DEPLOY_SSH_HOST` как
|
||||
ssh-таргет. **Пустой `ORCH_DEPLOY_SSH_HOST` → тик no-op** (фича не активна вне self-host).
|
||||
- Секретов не добавляет. ssh-ключи уже проброшены ro (`~/.orchestrator-ssh → /home/slin/.ssh`,
|
||||
ORCH-040); в git не коммитятся.
|
||||
|
||||
## I-3. Деплой / рестарт
|
||||
|
||||
- **Рестарт docker daemon — НЕ требуется** (ключевое отличие от отклонённого Варианта B). Уборка —
|
||||
это `docker builder prune` (BuildKit GC), без правки `daemon.json`.
|
||||
- **Рестарт прод-контейнера ради уборки — категорически НЕ требуется и запрещён** (self-hosting
|
||||
групповой риск). Сам код демона активируется штатным конвейерным деплоем оркестратора
|
||||
(staging 8501 → Confirm Deploy → prod), не отдельной операцией.
|
||||
- Host-prerequisites выбранного пути A (процедура Owner, в git не коммитятся — как P-1…P-4 в
|
||||
INFRA.md):
|
||||
1. На хосте установлен `docker` и пользователь `slin` — в группе `docker` (уже выполняется:
|
||||
деплой-хук запускает `docker compose` на хосте).
|
||||
2. ssh с контейнера на хост под `slin` работает без пароля (уже настроено для Phase B деплоя).
|
||||
Иные действия Owner не требуются — фича включена дефолтом и активна при наличии ssh-таргета.
|
||||
|
||||
### Ручной fallback (Вариант C, host-cron) — если ssh-канал недоступен
|
||||
|
||||
Если по какой-то причине ssh-канал на хост закрыт, эквивалентную защиту можно временно обеспечить
|
||||
host-cron на mva154 (процедура Owner, off-git):
|
||||
```cron
|
||||
# каждые 6 часов: удалить build cache старше 24ч (только build cache, не образы/контейнеры)
|
||||
0 */6 * * * docker builder prune -f --filter until=24h >> /var/log/orch-build-cache-prune.log 2>&1
|
||||
```
|
||||
Это fallback, не основной путь: cron не наблюдаем в `GET /queue` и не имеет config-kill-switch.
|
||||
|
||||
## I-4. CI/CD
|
||||
|
||||
- `.gitea/workflows/` — **без изменений**. Добавляется юнит-тест `tests/test_build_cache_pruner.py`
|
||||
(путь A), исполняется существующим `pytest tests/ -q`; docker/ssh в тестах мокируются (как
|
||||
`image_freshness`-тесты не требуют реального docker).
|
||||
</content>
|
||||
43
docs/work-items/ORCH-062/10-tech-risks.md
Normal file
43
docs/work-items/ORCH-062/10-tech-risks.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
work_item: ORCH-062
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-09
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# 10 — Технические риски: ORCH-062 — авто-prune docker build cache на mva154
|
||||
|
||||
Work Item: **ORCH-062** · Repo: **orchestrator** · Стадия: architecture
|
||||
|
||||
> Информационный (гейтом не парсится). Детализация R-1…R-4 из BRD + риски, выявленные при
|
||||
> архитектурном решении (Вариант A, ssh-на-хост). Решение — `06-adr/ADR-001-build-cache-pruner.md`.
|
||||
|
||||
## Реестр рисков
|
||||
|
||||
| ID | Риск | Вер. | Влия. | Митигейшн |
|
||||
|----|------|------|-------|-----------|
|
||||
| TR-1 | **Слишком агрессивная политика** (`-a` без возрастного фильтра / малый `until`) убивает тёплый кэш → каждая сборка «холодная», медленная (BRD R-1) | Низ. | Сред. | Дефолт `docker builder prune -f --filter until=24h` **без** `-a`; `-a` — только опционально и всегда в паре с `until` (D2/AC-2). Параметр удержания конфигурируем |
|
||||
| TR-2 | **Гонка уборки с активной сборкой** staging/прод-образа (`check_staging_image_fresh`, build-once retag) — теоретическое удаление кэша во время сборки (BRD R-2) | Низ. | Низ. | `docker builder prune --filter until=24h` по контракту BuildKit не трогает кэш, занятый/использованный активной сборкой (свежий < 24ч); период тика — порядка часов (6ч), не конкурирует за ресурсы (NFR-4) |
|
||||
| TR-3 | **Контейнер не имеет docker CLI** (`Dockerfile:11`) → наивный `subprocess.run(["docker",…])` упал бы FileNotFoundError | — (закрыт решением) | — | Решено архитектурно: уборка идёт через **ssh на хост** (`image_freshness`-канал), не CLI-в-контейнере. Не риск реализации, а зафиксированный инвариант D3 |
|
||||
| TR-4 | **ssh-канал недоступен** (нет `deploy_ssh_host` / закрыт ssh) → уборка не выполняется | Низ. | Сред. | Тик no-op + причина в `status().last_error` (наблюдаемо в `GET /queue`); never-raise — конвейер не страдает; документированный host-cron fallback (`07` I-3); disk-watchdog продолжает сигналить о росте диска |
|
||||
| TR-5 | **Расширение скоупа** на `docker image prune` / `system prune` → удаление образов запущенных контейнеров (BRD R-4) | Низ. | Выс. | Жёстко исключено D2/FR-3/AC-3: команда строго `docker builder prune`; reviewer проверяет отсутствие `image prune`/`system prune`/рестарта в коде и процедуре |
|
||||
| TR-6 | **Рестарт прода/докера ради уборки** (групповой self-hosting риск) | — (исключён) | Выс. | Вариант B (рестарт docker daemon) отвергнут именно по этой причине; Вариант A не рестартит ни прод, ни docker daemon (D3/I-3) |
|
||||
| TR-7 | **Сбой docker-команды/таймаут** на хосте всплывает в фоновый поток → останавливает цикл/конвейер | Низ. | Сред. | never-raise per-команда и per-tick (D6/FR-6/AC-4), как `disk_watchdog._run`/`tick`; ненулевой rc/таймаут/`OSError` логируются и проглатываются |
|
||||
| TR-8 | **Telegram-шум** при каждом тике | Низ. | Низ. | Нотификация только при освобождении ≥ `notify_min_gb`; дефолт `0` → тихо (D4/D5) |
|
||||
|
||||
## Сводный вывод
|
||||
|
||||
Доминирующий класс — **операционная безопасность self-hosting** (уборка на проде, обслуживающем
|
||||
все проекты). Все высоко-влиятельные риски (TR-5/TR-6) **структурно исключены** выбором узкой
|
||||
команды `docker builder prune` и отказом от рестарта docker daemon/прода (отклонён Вариант B).
|
||||
Остаточные риски — низкой вероятности и нейтрализуются never-raise + наблюдаемостью в `GET /queue`
|
||||
+ обратимостью kill-switch.
|
||||
|
||||
**Эскалация:** вводится **новый фоновый компонент** (leaf-демон) — формально подпадает под
|
||||
`arch:major-change`. Однако это калька уже принятого паттерна `disk_watchdog`/`reconciler`/
|
||||
`job_reaper` **без** изменения `STAGE_TRANSITIONS`/`QG_CHECKS`/схемы БД и **без** рестарта прода,
|
||||
поэтому остаточный риск для прод-конвейера — **низкий**; возврат в анализ не требуется (ТЗ
|
||||
реализуемо без нарушения принципов архитектуры).
|
||||
</content>
|
||||
Reference in New Issue
Block a user