87 lines
6.9 KiB
Markdown
87 lines
6.9 KiB
Markdown
---
|
||
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>
|