docs(history): LESSONS self-deploy bootstrap — каскад 4 инфра-багов (passwd/env/log-perms/stale-staging-image)
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
78
docs/history/LESSONS_ORCH-036-selfdeploy.md
Normal file
78
docs/history/LESSONS_ORCH-036-selfdeploy.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Lessons Learned — 2026-06-07 (утро): ORCH-36 self-deploy bootstrap — каскад неготовности инфры
|
||||
|
||||
## Итог
|
||||
ORCH-36 (исполняемый самодеплой стадии `deploy`) **замкнулась в проде** — конвейер
|
||||
впервые задеплоил сам себя по полному циклу Phase A→B→C (approve → детачед ssh-хук →
|
||||
finalizer). Но путь до Done вскрыл **четыре слоя неготовности инфраструктуры**, каждый из
|
||||
которых требовал ручного bootstrap-разрыва: задача про автодеплой не может задеплоить
|
||||
сама себя, пока её же механизм + инфра не в проде.
|
||||
|
||||
Эпик ORCH-54: **4/6 в проде** (ORCH-40 права, ORCH-43 merge-gate, ORCH-36 самодеплой,
|
||||
ORCH-53 reconciler). Конвейер автономен: мержит → катит в прод → чинит застрявшее.
|
||||
|
||||
---
|
||||
|
||||
## Каскад из 4 инфра-багов (все вскрылись только при РЕАЛЬНОМ деплое)
|
||||
|
||||
### 1. 🔴 uid 1000 без записи в `/etc/passwd` → ssh/whoami падают
|
||||
**Симптом:** `self-deploy initiate failed: ssh launch failed (rc=255): No user exists for
|
||||
uid 1000`. **Корень:** регрессия ORCH-40 — compose запускает контейнер под `1000:1000`,
|
||||
но базовый образ `python:3.12-slim` не имеет passwd-записи для 1000. SSH-клиент (и
|
||||
`whoami`, `getpwuid()`) отказываются стартовать без валидного юзера.
|
||||
**Фикс:** в `Dockerfile` — `groupadd -g 1000 app && useradd -u 1000 -g 1000 -m -d
|
||||
/home/slin -s /bin/bash slin`. Rebuild + recreate. Коммит `64e031a`.
|
||||
**Урок:** при переводе контейнера на non-root uid (ORCH-40) ОБЯЗАТЕЛЬНО создавать passwd-
|
||||
запись в образе, иначе ssh/git/любой инструмент с getpwuid() ломается. Проверять
|
||||
`docker exec <c> whoami` после смены uid.
|
||||
|
||||
### 2. 🔴 env-префикс: `DEPLOY_*` vs `ORCH_DEPLOY_*` (pydantic не видит)
|
||||
**Симптом:** `ssh: Could not resolve hostname : No address associated with hostname` —
|
||||
host пустой, хотя в compose `DEPLOY_SSH_HOST=127.0.0.1` задан. **Корень:** `Settings`
|
||||
имеет `env_prefix = "ORCH_"` → читает ТОЛЬКО `ORCH_DEPLOY_SSH_HOST`. Старые
|
||||
`DEPLOY_*` (без префикса) предназначались легаси enduro-деплоеру (читает через
|
||||
`os.environ` напрямую) и pydantic их игнорирует → дефолт `host=""`. Доп: `DEPLOY_HOOK_SCRIPT`
|
||||
указывал на `enduro-deploy-hook.sh`, не на orchestrator-хук.
|
||||
**Фикс:** в `docker-compose.yml` добавлены `ORCH_DEPLOY_SSH_USER/HOST`,
|
||||
`ORCH_DEPLOY_HOOK_SCRIPT=scripts/orchestrator-deploy-hook.sh`,
|
||||
`ORCH_DEPLOY_HOST_REPO_PATH` (легаси `DEPLOY_*` оставлены для enduro). Коммит `115519e`.
|
||||
**Урок:** все настройки, читаемые через pydantic Settings, ДОЛЖНЫ иметь префикс `ORCH_`.
|
||||
Проверять резолв: `docker exec <c> python3 -c 'from src.config import settings; print(settings.deploy_ssh_host)'`.
|
||||
|
||||
### 3. 🔴 `/var/log/orchestrator` принадлежит root → хук падает на tee
|
||||
**Симптом:** `tee: /var/log/orchestrator/deploy-hook.log: Permission denied`, хук exit 1.
|
||||
**Корень:** лог-директория `root:root`, а хук бежит под `slin`. **Фикс:** `chown -R
|
||||
slin:slin /var/log/orchestrator` на хосте.
|
||||
**Урок:** все пути, в которые пишет хост-хук (логи, sentinel, prev-image), должны быть
|
||||
writable юзером, под которым ssh-сессия. Заложить создание/chown в provisioning хоста.
|
||||
|
||||
### 4. 🔴🔴 BUILD-ONCE retag берёт УСТАРЕВШИЙ staging-образ → катит регресс (ВАЖНО)
|
||||
**Симптом:** деплой «зелёный» (result=0, health ok), но прод откатился на код 2-дневной
|
||||
давности — пропал `deploy-finalizer` (`Unknown agent: deploy-finalizer`), задача не
|
||||
закрылась. **Корень:** хук делает `BUILD-ONCE: retag orchestrator-orchestrator-staging →
|
||||
orchestrator-orchestrator` (без rebuild, by design ORCH-36 BR-6). Дизайн предполагал
|
||||
«staging-образ = свежий, провалидированный». В РЕАЛЬНОСТИ `orchestrator-orchestrator-staging`
|
||||
никто не пересобрал из нового main → retag катил в прод СТАРЫЙ образ → бесконечная петля:
|
||||
каждый Phase B возвращал прод в прошлое, finalizer (новый код) исчезал, Phase C не мог
|
||||
закрыть задачу.
|
||||
**Фикс (ручной разрыв):** пересобрать `orchestrator-orchestrator-staging` из актуального
|
||||
main ПЕРЕД retag → тогда хук катит свежий код. После этого Phase C отработал: result=0 →
|
||||
SUCCESS → `deploy → done`.
|
||||
**Урок / ТЕХДОЛГ:** retag-стратегия BUILD-ONCE предполагает гарантию свежести staging-
|
||||
образа, которой НЕТ. Нужна отдельная задача: либо staging-деплой пересобирает образ из
|
||||
текущего main перед валидацией, либо deploy-хук проверяет, что staging-образ собран из
|
||||
HEAD main (по labels/sha), иначе fail-fast. Сейчас «зелёный» деплой может молча катить
|
||||
регресс. **Это самый опасный из четырёх — он не падает, а тихо откатывает прод.**
|
||||
|
||||
---
|
||||
|
||||
## Сквозной урок: bootstrap самохостинга
|
||||
Любая задача, меняющая deploy/merge-механику САМОГО оркестратора, упирается в парадокс:
|
||||
её механизм не работает, пока не в проде, а в прод его можно влить только старым
|
||||
механизмом. Каждый слой (код → права → env → образ) вскрывается ТОЛЬКО при первом
|
||||
реальном прогоне. Закладывать в план таких задач **ручной bootstrap-чеклист** и гонять
|
||||
**реальный** деплой в staging-петле до мержа, а не только бумажные гейты.
|
||||
|
||||
## Прод после (main `115519e`+, образ 2026-06-07 09:47)
|
||||
- self_deploy.py + reconciler.py в проде, finalizer зарегистрирован (grep=5)
|
||||
- uid 1000 = slin (passwd ok), ssh slin@127.0.0.1 работает, /var/log/orchestrator writable
|
||||
- ORCH-36 task 43 → done, Plane → Done
|
||||
Reference in New Issue
Block a user