Round-3 review follow-up on c53d625 (P1/P2):
- P1: --build-staging now runs staging_check via parametrized
STAGING_CONTAINER / STAGING_CHECK_PATH / STAGING_CHECK_MODE (default
orchestrator-staging / bind-mount path / stub) instead of hardcoding
$TARGET_SERVICE + the script path. docker exec runs INSIDE the staging
container (ORCH-048 canonical: B6 registry isolation), after health,
before exit 0. Fail-closed: any non-zero -> exit 1. STAGING only (8501).
- P2a: rebuild_staging_image now passes the STAGING target EXPLICITLY
(TARGET_SERVICE/TARGET_PORT/COMPOSE_PROFILE/STAGING_CONTAINER) so the
self-rebuild can never drift onto prod 8500 if hook defaults change (AC-9).
- P2b: TC-09 caller<->hook contract tests assert the ssh command carries
GIT_SHA + BUILD_CONTEXT + the staging target and never the prod 8500 one;
no-ssh-host fails closed.
- P3: consolidated the three duplicate README footers into one.
- Docs (golden source): DEPLOY_HOOK.md step 4 + env rows, README footer,
CHANGELOG, Dockerfile ARG GIT_SHA="" comment, .env.example freshness block.
Validates exactly the artefact later BUILD-ONCE retagged to prod (AC-4,
ADR-001 step 3). 632 tests pass, ruff clean, bash -n OK.
Refs: ORCH-058
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
Orchestrator Deploy Hook
scripts/orchestrator-deploy-hook.sh — хост-скрипт деплоя orchestrator с health-чеком и авто-rollback.
Как работает
Режим --deploy (по умолчанию)
- Захват текущего образа — до рестарта записывает ID образа работающего контейнера в
$PREV_IMAGE_FILE(best-effort, не падает если сервис не запущен). - git pull — обновляет код репозитория.
2b. Build-once retag (ORCH-036, BR-6) — если задан
$SOURCE_IMAGE, хук ретегает его на$TARGET_IMAGE(docker tag $SOURCE_IMAGE $TARGET_IMAGE) и поднимает контейнер на этом образе черезup -d --no-build. Это деплой РОВНО того образа, что прошёл staging, безdocker build. Если$SOURCE_IMAGEне задан (дефолт) — шаг пропускается (обратная совместимость).- Fail-closed провенанс-guard (ORCH-058, Strategy B) — ПЕРЕД
docker tag, если задан$EXPECTED_REVISION, хук сверяет OCI-лейблorg.opencontainers.image.revisionу$SOURCE_IMAGEс$EXPECTED_REVISION. Несовпадение / пустой лейбл (<no value>) / ошибка inspect → лог +exit 1(FAILED → авто-rollback), прод не трогается. Не задан$EXPECTED_REVISION(дефолт) → проверка пропускается (обратная совместимость для не-self репозиториев).
- Fail-closed провенанс-guard (ORCH-058, Strategy B) — ПЕРЕД
- Рестарт контейнера —
docker compose --profile $COMPOSE_PROFILE up -d --no-build $TARGET_SERVICE. - Health-цикл — 10 попыток × 6с = до 60с. Критерий: HTTP 200 + тело содержит
"status":"ok".- Успех →
exit 0, лог "Deploy SUCCESS". - Провал → авто-rollback (шаг 5).
- Успех →
- Авто-rollback — восстанавливает образ из
$PREV_IMAGE_FILE, рестарт, повторный health 5×3с.- Если восстановился →
exit 1(деплой провалился, откат успешен). - Если и откат не помог →
exit 2(критично).
- Если восстановился →
Режим --build-staging (ORCH-058, Strategy A)
Пересобирает staging-образ из провалидированного коммита и пересоздаёт 8501, чтобы артефакт, который мы валидируем, был РОВНО тем, что позже build-once ретегается в прод (инвариант INV-FRESH). Собирает/пересоздаёт только staging (8501) — никогда прод (8500).
docker build --build-arg GIT_SHA=$GIT_SHA -t $TARGET_IMAGE $BUILD_CONTEXT— пересборка из host-worktree валидированного коммита;GIT_SHAштампуется в OCI-лейблorg.opencontainers.image.revision.docker compose [--profile $COMPOSE_PROFILE] up -d --no-build $TARGET_SERVICE— пересоздание staging на свежем образе.- Health-цикл 10×6с. Провал сборки/health →
exit 1. staging_checkпротив СВЕЖЕГО образа (Strategy A, шаг 3 — ADR-001, AC-4) — после health хук запускаетdocker exec $STAGING_CONTAINER python3 $STAGING_CHECK_PATH --base-url http://localhost:$TARGET_PORT --mode $STAGING_CHECK_MODE(дефолт--mode stub, без LLM-трат). Запуск внутри staging-контейнера канонический (ORCH-048): suite читает реестр из собственного env контейнера, аstaging_check.pyберётся из bind-mount (/repos/orchestrator/scripts/..., не из образа). Это ровно тот артефакт, что позже build-once ретегается в прод → валидируем то, что промоутим (AC-4). PASS →exit 0; любой не-ноль (FAIL чека или safety-abortORCH_STAGING≠true) →exit 1.
Запускается оркестратором на ребре deploy-staging → deploy (QG-под-чек check_staging_image_fresh → rebuild_staging_image пробрасывает явный staging-таргет, см. INFRA.md). Тот же контракт кодов выхода (0 = здоров и staging_check PASS).
Режим --rollback
Вручную откатывает сервис на предыдущий образ из $PREV_IMAGE_FILE.
Переменные окружения
| Переменная | Дефолт | Описание |
|---|---|---|
TARGET_SERVICE |
orchestrator-staging |
Имя docker-compose сервиса |
TARGET_PORT |
8501 |
Порт health-check |
TARGET_IMAGE |
orchestrator-orchestrator-staging |
Имя образа для retag при rollback |
COMPOSE_PROFILE |
staging |
Docker compose profile (пусто = без профиля) |
PREV_IMAGE_FILE |
$REPO/.deploy-prev-image-staging |
Файл для сохранения предыдущего образа |
SOURCE_IMAGE |
(unset) | Build-once (ORCH-036): провалидированный образ для retag на $TARGET_IMAGE перед рестартом (без rebuild). Не задан → шаг пропущен. |
EXPECTED_REVISION |
(unset) | Build-once (ORCH-058, Strategy B): ожидаемый git-SHA $SOURCE_IMAGE (лейбл org.opencontainers.image.revision). Задан → fail-closed guard перед docker tag. Не задан → проверка пропущена. |
GIT_SHA |
(unset) | --build-staging (ORCH-058, Strategy A): коммит, штампуемый в OCI-лейбл revision при пересборке staging-образа. |
BUILD_CONTEXT |
$REPO |
--build-staging: docker build context (host-worktree валидированного коммита). |
STAGING_CONTAINER |
$TARGET_SERVICE (orchestrator-staging) |
--build-staging (ORCH-058): контейнер, внутри которого docker exec запускает staging_check. |
STAGING_CHECK_PATH |
/repos/orchestrator/scripts/staging_check.py |
--build-staging (ORCH-058): путь к staging_check.py внутри контейнера (bind-mount, не образ). |
STAGING_CHECK_MODE |
stub |
--build-staging (ORCH-058): режим staging_check (stub — быстро, без LLM; full-real — дожидается аналитика). |
LOG |
/var/log/orchestrator/deploy-hook.log |
Лог-файл (fallback: $REPO/deploy-hook.log) |
⚠️ Дефолт — всегда STAGING. Прод активируется только явным переопределением env.
Примеры запуска
Staging (дефолт, безопасно)
cd /home/slin/repos/orchestrator
bash scripts/orchestrator-deploy-hook.sh --deploy
# или просто:
bash scripts/orchestrator-deploy-hook.sh
Прод (осознанный шаг, Этап 5)
TARGET_SERVICE=orchestrator \
TARGET_PORT=8500 \
TARGET_IMAGE=orchestrator-orchestrator \
COMPOSE_PROFILE="" \
PREV_IMAGE_FILE=/home/slin/repos/orchestrator/.deploy-prev-image-prod \
bash scripts/orchestrator-deploy-hook.sh --deploy
Прод build-once (ORCH-036) — ретег staging-образа, без rebuild
Так прод-деплой запускается автоматически исполняемым самодеплоем (Фаза B: ssh + setsid, см. INFRA.md). Ключевое отличие — SOURCE_IMAGE указывает на провалидированный staging-образ, который ретегается на прод-тег:
SOURCE_IMAGE=orchestrator-orchestrator-staging \
TARGET_SERVICE=orchestrator \
TARGET_PORT=8500 \
TARGET_IMAGE=orchestrator-orchestrator \
COMPOSE_PROFILE="" \
PREV_IMAGE_FILE=/home/slin/repos/orchestrator/.deploy-prev-image-prod \
bash scripts/orchestrator-deploy-hook.sh --deploy
Ручной rollback staging
bash scripts/orchestrator-deploy-hook.sh --rollback
Коды выхода
| Код | Значение |
|---|---|
0 |
Деплой успешен, сервис здоров |
1 |
Деплой провалился; откат выполнен (или пропущен) |
2 |
Деплой провалился И откат тоже провалился (критично) |
Логи
/var/log/orchestrator/deploy-hook.log
Каждая строка с UTC-таймстампом в формате [2026-06-05T06:30:00Z].
Разница с enduro-deploy-hook.sh
| Функция | enduro-deploy-hook.sh | orchestrator-deploy-hook.sh |
|---|---|---|
| Захват PREV_IMG | ✅ | ✅ |
| git pull | ✅ | ✅ |
| Рестарт | ✅ | ✅ |
| Health-цикл (60с) | ❌ | ✅ 10×6с |
| Авто-rollback | ❌ | ✅ |
| Параметризация (env) | ❌ хардкод | ✅ дефолт=staging |
| Compose profile | ❌ | ✅ --profile staging |