architect(ET): auto-commit from architect run_id=491
This commit is contained in:
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