chore(ci): не проглатывать падение сборки + починить самолечение buildcache #2890

Merged
lekss361 merged 2 commits from chore/ci-gates-runner-and-deploy into main 2026-08-15 16:20:20 +00:00
Owner

Проблема

Из аудита прод-состояния 15.08. Зелёная галка деплоя не отличима от пропущенного деплоя: если сборка падает из-за битого blob в кеше, шаг деплоя пропускается, а прогон в целом выглядит успешным (#2841).

Плюс раннеры работают в сети хоста (NetworkMode=host у всех трёх), а ss -ltnp показывает LISTEN 127.0.0.1:5432 — боевой gendesign-postgres. Job с сервис-контейнером на 5432 попал бы в прод-базу.

Что сделано

1. Проверка того, что образ реально собран. После каждого из шести retry-шагов — безусловный docker buildx imagetools inspect <image>:<sha>, без continue-on-error.

Это принципиально: первая версия правки держалась на steps.<id>.outcome, а поддержка этого поля у Forgejo act_runner не доказана. Если бы раннер его не заполнял, retry молча не побежал бы, падение ушло бы в continue-on-error, и job вернул успех — то есть правка про «честный статус» дала бы ровно обратное. Новый шаг движок ни о чём не спрашивает, он смотрит фактическое состояние registry.

2. cache-to возвращён в retry-шаги. Заявленное самолечение битого кеша не работало никогда: cache-to стоял только на основном шаге, а retry всегда собирался без него — значит испорченный манифест не перезаписывался и падение воспроизводилось бы вечно. Теперь успешный ретрай сам чинит кеш. cache-from в retry остаётся убран, он и есть источник падения.

3. Health-check в главном deploy.yml теперь может упасть. Было:

for i in $(seq 1 30); do curl -fsS .../health && break; sleep 1; done

Под set -e это завершается кодом 0, даже если curl не отдал 200 ни разу: он не последняя команда &&-списка, и POSIX такие команды от проверки освобождает. Переписано по образцу deploy-tradein.yml (#2214) — явный флаг и exit 1 после цикла.

Про docker rm -f

В deploy*.yml вызовы docker rm -f без -v не тронуты — они применяются к боевым контейнерам, и флаг снёс бы тома с данными прода. В ci*.yml -v стоит и это правильно (#2887). Дифф строго аддитивный, ни одной удалённой строки.

Ревью

Два круга. В первом — три замечания, включая то самое про steps.outcome. Во втором ревьюер проверил закрытие сам: yaml-парсом подтвердил 6 из 6 verify-шагов без continue-on-error и без if. Вердикт APPROVE.

Остаток, осознанно принятый: сам imagetools inspect вживую на act_runner не гонялся (косвенно — в тех же job'ах уже работают docker login и docker buildx шеллом). Проверится первым же деплоем.

Test plan

  • check yaml в pre-commit
  • первый деплой после мержа: verify-шаг зелёный, время сборки не выросло
## Проблема Из аудита прод-состояния 15.08. Зелёная галка деплоя не отличима от пропущенного деплоя: если сборка падает из-за битого blob в кеше, шаг деплоя пропускается, а прогон в целом выглядит успешным (#2841). Плюс раннеры работают в сети хоста (`NetworkMode=host` у всех трёх), а `ss -ltnp` показывает `LISTEN 127.0.0.1:5432` — боевой `gendesign-postgres`. Job с сервис-контейнером на 5432 попал бы в прод-базу. ## Что сделано **1. Проверка того, что образ реально собран.** После каждого из шести retry-шагов — безусловный `docker buildx imagetools inspect <image>:<sha>`, без `continue-on-error`. Это принципиально: первая версия правки держалась на `steps.<id>.outcome`, а поддержка этого поля у Forgejo act_runner не доказана. Если бы раннер его не заполнял, retry молча не побежал бы, падение ушло бы в `continue-on-error`, и job вернул успех — то есть правка про «честный статус» дала бы ровно обратное. Новый шаг движок ни о чём не спрашивает, он смотрит фактическое состояние registry. **2. `cache-to` возвращён в retry-шаги.** Заявленное самолечение битого кеша не работало никогда: `cache-to` стоял только на основном шаге, а retry всегда собирался без него — значит испорченный манифест не перезаписывался и падение воспроизводилось бы вечно. Теперь успешный ретрай сам чинит кеш. `cache-from` в retry остаётся убран, он и есть источник падения. **3. Health-check в главном `deploy.yml` теперь может упасть.** Было: ```bash for i in $(seq 1 30); do curl -fsS .../health && break; sleep 1; done ``` Под `set -e` это завершается кодом 0, даже если `curl` не отдал 200 ни разу: он не последняя команда `&&`-списка, и POSIX такие команды от проверки освобождает. Переписано по образцу `deploy-tradein.yml` (#2214) — явный флаг и `exit 1` после цикла. ## Про `docker rm -f` В `deploy*.yml` вызовы `docker rm -f` **без** `-v` не тронуты — они применяются к боевым контейнерам, и флаг снёс бы тома с данными прода. В `ci*.yml` `-v` стоит и это правильно (#2887). Дифф строго аддитивный, ни одной удалённой строки. ## Ревью Два круга. В первом — три замечания, включая то самое про `steps.outcome`. Во втором ревьюер проверил закрытие сам: yaml-парсом подтвердил 6 из 6 verify-шагов без `continue-on-error` и без `if`. Вердикт **APPROVE**. Остаток, осознанно принятый: сам `imagetools inspect` вживую на act_runner не гонялся (косвенно — в тех же job'ах уже работают `docker login` и `docker buildx` шеллом). Проверится первым же деплоем. ## Test plan - [x] `check yaml` в pre-commit - [ ] первый деплой после мержа: verify-шаг зелёный, время сборки не выросло
lekss361 added 2 commits 2026-08-15 16:01:26 +00:00
Зелёная галка прогона не отличима от пропущенного деплоя: если build падает
из-за битого blob в удалённом buildcache, шаг deploy молча пропускается
(if-условие даёт result=skipped), а прогон в целом не подсвечен как FAILED.

- deploy-status: новая job в конце deploy.yml и deploy-tradein.yml, всегда
  бежит (if: always() && !cancelled()) и падает явно, если deploy.result !=
  success — неважно, пропущен он (upstream build/test упал) или упал сам.
- cache-from нефатален: каждый build-push-action-шаг получил id + continue-
  on-error, и ретрай без cache-from/cache-to при steps.build.outcome ==
  'failure'. Битый remote-кеш больше не роняет саму сборку; следующий
  успешный прогон с кешем перезаписывает buildcache-тег целиком (mode=max)
  и самолечит порчу. Реальные ошибки сборки (не кеш) по-прежнему валят job
  на ретрае — deploy-status их тоже поймает.

Гейт против публикации services-портов на VPS (та же задача, проблема 1)
уже покрыт scripts/check-workflow-ports.py + шагом в ci.yml (#2757/#2759,
слит ранее) — сканирует все .forgejo/workflows/*.yml, включая эти два файла;
новых правок не потребовалось.

docker rm -f БЕЗ -v в SSH-скриптах деплоя не тронут — эти вызовы намеренно
без -v (боевые тома), правка их не касается.
fix(ci): fail-safe registry verification + real cache self-heal + honest health-check (#2841 R2)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
8bce8cf5ae
Ревью R2 нашёл, что вся безопасность предыдущего фикса держалась на
недоказанной поддержке act_runner'ом steps.<id>.outcome: если раннер его
не заполняет, retry-шаг молча не бежит, continue-on-error проглатывает
падение сборки, job зелёный — а деплой тянет старый :latest на прод.

- Добавлен engine-agnostic verify-шаг после каждого retry (6 мест,
  deploy.yml + deploy-tradein.yml): `docker buildx imagetools inspect
  <image>:<sha>` без continue-on-error. Не зависит от того, поддерживает
  ли раннер outcome — проверяет реальное состояние registry напрямую.
  Если ни build, ни retry реально не запушили образ — шаг падает и job
  честно FAILURE независимо от семантики outcome.
- Вернул `cache-to` в retry-шаги (6 мест): без него битый buildcache-тег
  никогда не перезаписывался — retry всегда собирал без cache-to, значит
  cache-to не выполнялся НИКОГДА, и каждый следующий прогон снова падал
  на том же cache-from. Заявленное самолечение не работало ни разу.
- Health-check в deploy.yml (main-стек) под `set -e` не мог упасть:
  `curl ... && break` — curl не последняя команда &&-списка, POSIX
  освобождает такие команды от errexit, цикл дохаживал до sleep (exit 0)
  даже если curl ни разу не отдал 200. Приведено к паттерну
  deploy-tradein.yml: явный флаг healthy + `exit 1` после цикла.
  Подтверждено локальным bash-репро (mock curl, всегда failure): старая
  версия — exit 0, новая — exit 1; позитивный сценарий не сломан.

docker rm -f без -v в SSH-скриптах деплоя не тронут.
lekss361 merged commit e9abf1cd31 into main 2026-08-15 16:20:20 +00:00
lekss361 deleted branch chore/ci-gates-runner-and-deploy 2026-08-15 16:20:20 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2890
No description provided.