architect(ET): auto-commit from architect run_id=748
All checks were successful
CI / test (push) Successful in 1m8s
All checks were successful
CI / test (push) Successful in 1m8s
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
---
|
||||
work_item: ORCH-123
|
||||
stage: architecture
|
||||
author_agent: architect
|
||||
status: proposed
|
||||
created_at: 2026-06-16
|
||||
model_used: claude-opus-4-8
|
||||
---
|
||||
|
||||
# adr-0049: Граница исполнения docker — все docker-операции host-side, не изнутри app-контейнера
|
||||
|
||||
> **Сквозной (cross-cutting) ADR.** Кодифицирует инвариант **«docker-операции оркестратора
|
||||
> исполняются host-side через доверенный ssh-канал, никогда изнутри прод-контейнера»**, охватывающий
|
||||
> компоненты ORCH-036/058/115/123/101, и **амендит** execution-strategy-решение
|
||||
> [adr-0048](adr-0048-deterministic-staging-runner.md) (D3/D5). Поводом стала задача ORCH-123 (баг:
|
||||
> staging-runner отклонился от инварианта). Локальная детализация (D1–D9) —
|
||||
> `docs/work-items/ORCH-123/06-adr/ADR-001-host-side-staging-execution-and-env-classification.md`.
|
||||
|
||||
## Статус
|
||||
Proposed
|
||||
|
||||
## Контекст
|
||||
|
||||
Прод-контейнер `orchestrator` (8500) **не содержит docker CLI** (`Dockerfile:11`:
|
||||
`openssh-client git curl ca-certificates` + pinned gitleaks; `python:3.12-slim` docker не несёт).
|
||||
`/var/run/docker.sock` смонтирован rw + `group_add 999` (ORCH-040 «МИНА 1»), но **клиента, который
|
||||
бы им воспользовался, нет** — сознательно: добавление CLI/SDK активировало бы root-эквивалентный путь
|
||||
исполнения для всего, что бежит в контейнере (вкл. LLM-агентов). Поэтому в оркестраторе сложился
|
||||
**инвариант исполнения**, ранее не выделенный в отдельный ADR:
|
||||
|
||||
- **ORCH-036** (`self_deploy.build_deploy_command`, [adr-0007](adr-0007-executable-self-deploy.md)) —
|
||||
прод-деплой исполняется host-side через `ssh + setsid bash <hook> --deploy` на `127.0.0.1`.
|
||||
- **ORCH-058** (`image_freshness`, [adr-0008](adr-0008-staging-image-provenance.md)) — ребилд
|
||||
staging-образа (`ssh … bash <hook> --build-staging`) и инспекция revision
|
||||
(`image_revision(ssh_target=…)`) — host-side; модуль прямо документирует:
|
||||
*«docker lives on the HOST (the container ships only openssh-client git)»*.
|
||||
- **ORCH-101** ([adr-0036](adr-0036-replication-foundation-host-parametrization.md)) — host-параметры
|
||||
канала (`deploy_ssh_*`, `deploy_host_repo_path`, `repos_dir`/`host_repos_dir`) расхардкожены.
|
||||
|
||||
**ORCH-115** ([adr-0048](adr-0048-deterministic-staging-runner.md)), заменяя LLM-деплойера
|
||||
детерминированным `staging_runner`, **отклонился** от инварианта: зашил `docker exec` **изнутри**
|
||||
прод-контейнера через `proc_group → Popen` → `FileNotFoundError: docker` → постоянный
|
||||
environment-дефект, ложно маршрутизированный как транзиентная инфра → DEFER → fail-closed FAILED →
|
||||
**откат `deploy-staging → development`** (винит код задачи за дефект окружения раннера). Инцидент
|
||||
ORCH-116/ORCH-123.
|
||||
|
||||
## Решение
|
||||
|
||||
**Кодифицировать инвариант (нормативно):** docker-операции оркестратора (`docker`/`docker compose`/
|
||||
`docker exec`/`docker inspect`/`docker tag`) исполняются **host-side** через доверенный ssh-канал
|
||||
(`deploy_ssh_host=127.0.0.1`, ключ смонтирован, `openssh-client` в образе) — **никогда** изнутри
|
||||
прод-контейнера, который docker CLI не несёт. `/var/run/docker.sock` **не используется** изнутри
|
||||
контейнера; docker CLI/SDK в образ **не добавляется** (любое исключение — отдельный явный
|
||||
security-review: socket-из-контейнера = root-эквивалент на хосте, обслуживающем все проекты).
|
||||
|
||||
**ORCH-123 приводит `staging_runner` в соответствие** (амендит adr-0048 D3/D5):
|
||||
- **D3 (амендмент adr-0048):** `staging_runner.build_staging_command` теперь обёртывает
|
||||
`docker exec orchestrator-staging python3 staging_check.py …` в `ssh <user>@<host> '<…>'` (зеркало
|
||||
`image_freshness.image_revision(ssh_target=…)`). Внутренняя команда сюиты и exit-код-контракт — те
|
||||
же; меняется лишь **инициатор/канал**.
|
||||
- **D5 (амендмент adr-0048 двухуровневого исхода):** введён **третий** класс исхода `permanent-env`
|
||||
(зеркало `merge_gate.classify_retest_failure`, ORCH-110); корневой инвариант — **«сюита не
|
||||
исполнилась» (environment ИЛИ транзиентная инфра) НИКОГДА не оканчивается код-фейл-откатом и не жжёт
|
||||
developer-retry**; откат — только для реально исполнившейся сюиты с `exit≠0`. Терминал исчерпания
|
||||
DEFER изменён с fail-closed-FAILED+advance на **infra-HOLD + alert** (как ORCH-110 D3).
|
||||
|
||||
Кросс-каттинговые инварианты (сохранены **байт-в-байт**, как adr-0048):
|
||||
- `STAGE_TRANSITIONS` / реестр и имена `QG_CHECKS`/`check_staging_status`/`_parse_staging_status` /
|
||||
machine-verdict-ключи (`staging_status:`/`deploy_status:`/…) / **схема БД** — не тронуты (замена
|
||||
*стратегии исполнения продюсера*, не гейта/стадии).
|
||||
- Единственный транспорт LLM-консультации (`launcher._spawn`/S0, [adr-0047](adr-0047-llm-usage-policy-and-call-site-map.md))
|
||||
— соблюдён (раннер LLM не зовёт).
|
||||
- Сквозной бюджет времени ORCH-065/109/110 (`reaper_max_running_s` > Σ(работ на ребре) + grace) — не
|
||||
растёт (host-side ssh заменяет in-container call, окно ≤ `staging_runner_timeout_s`).
|
||||
- Граница transition-lease ORCH-114 — берётся внутри `advance_stage`; раннер не трогает.
|
||||
|
||||
Скоуп — **self-hosting only** (`staging_runner_repos=""` → `is_self_hosting_repo`); под флагами
|
||||
`staging_runner_enabled` (→ LLM-путь) и **новым** `staging_runner_exec_host_side` (дефолт `True` →
|
||||
фикс; `False` → прежний in-container call). never-raise во всех публичных функциях.
|
||||
|
||||
## Последствия
|
||||
|
||||
- **+** Инвариант «docker host-side» выделен и задокументирован → будущие компоненты не повторят
|
||||
отклонение ORCH-115; reviewer ловит in-container docker как регресс инварианта.
|
||||
- **+** staging-сюита реально исполняется в проде; инфра/environment ≠ код-фейл на staging-ребре
|
||||
(закрыт RCA-класс ORCH-110 на этом ребре полностью); анти-over-tolerance цел.
|
||||
- **+** Без расширения привилегий (нет docker CLI/SDK в контейнере, сокет не используется); согласовано
|
||||
с ORCH-036/058.
|
||||
- **−** Remote tree-kill ограничен локальным ssh-клиентом (как `image_freshness.rebuild_staging_image`);
|
||||
backstop — bounded таймаут внутри `staging_check.py`.
|
||||
- **−** Permanent-env/исчерпавшая-DEFER задача держится на `deploy-staging` (блокирует serial-gate репо
|
||||
до починки оператором) — принятый tradeoff (зеркало ORCH-110), self-hosting only.
|
||||
- **Откат:** `ORCH_STAGING_RUNNER_ENABLED=false` (→ LLM) или `ORCH_STAGING_RUNNER_EXEC_HOST_SIDE=false`
|
||||
(→ in-container call).
|
||||
|
||||
## Ссылки
|
||||
- Локальный ADR: `docs/work-items/ORCH-123/06-adr/ADR-001-host-side-staging-execution-and-env-classification.md`
|
||||
- Амендит: [adr-0048](adr-0048-deterministic-staging-runner.md) (D3/D5 ORCH-115)
|
||||
- Опирается на: [adr-0007](adr-0007-executable-self-deploy.md) (ORCH-036 self-deploy ssh),
|
||||
[adr-0008](adr-0008-staging-image-provenance.md) (ORCH-058 image-freshness host-side docker),
|
||||
[adr-0042](adr-0042-merge-gate-retest-infra-tolerance-and-tree-kill.md) (ORCH-110 proc_group +
|
||||
classify + infra-tolerance), [adr-0036](adr-0036-replication-foundation-host-parametrization.md)
|
||||
(ORCH-101 host-параметризация)
|
||||
- Сверено по коду: `src/staging_runner.py`, `src/self_deploy.py:220`, `src/image_freshness.py:185/246`,
|
||||
`scripts/orchestrator-deploy-hook.sh:166/197`, `Dockerfile:11`, `docker-compose.yml`
|
||||
Reference in New Issue
Block a user