ci: убирать buildx-билдер в конце сборочных job'ов (#2869) #2870

Merged
bot-backend merged 3 commits from fix/2869-buildx-cleanup into main 2026-08-13 12:48:29 +00:00
Collaborator

Зачем

Сегодня деплой упал так:

#15 ERROR: mkdir /var/lib/buildkit/...: no space left on device

Диск VPS был на 94% (145G / 136G занято / 9.0G свободно). Тем же дефицитом
покрасились ещё две проверки на других PR — один дефицит, три «независимых» симптома.

Корень

docker/setup-buildx-action@v3 создаёт билдер docker-container на каждый прогон,
а его post-step под Forgejo act_runner не срабатывает. На хосте накопилось
20 контейнеров buildx_buildkit_builder-* возрастом до двух месяцев:

buildx_buildkit_builder-*_state × 10   19.3 GB
tradein-postgres-data                  21.6 GB   ← данные
gendesign_postgres_data                15.2 GB   ← данные

Почему рост незаметен до отказа:

  • тома не dangling (контейнеры существуют) → docker volume prune их не видит;
  • docker system df показывает Build Cache 0B, потому что кэш живёт внутри
    контейнеров-билдеров, а не у хостового демона.

Инструмент, слепой по построению: смотришь на «0B» и делаешь вывод, что чистить нечего.

Что делает PR

Шесть сборочных job'ов (три в deploy.yml, три в deploy-tradein.yml) получают
id: buildx у шага setup и шаг уборки в конце:

      - name: Убрать buildx-билдер (#2869 — иначе копятся по одному на прогон)
        if: always()
        run: |
          name="${{ steps.buildx.outputs.name }}"
          if [ -z "$name" ]; then
            echo "buildx: имя билдера не пришло из outputs — уборка НЕ сработала (см. #2869)"
            exit 0
          fi
          echo "buildx: убираю билдер $name"
          docker buildx rm --force "$name" || echo "buildx: не удалось убрать $name (не фатально)"
  • if: always() — убирает и после упавшей сборки, иначе именно при падениях билдеры и копятся.
  • Ошибка уборки не роняет прогон.
  • Шаг говорит вслух, когда не сработал. Под act не гарантировано, что
    steps.buildx.outputs.name заполнится; если он пуст, в логе будет явная строка,
    а не молчаливый no-op, который читался бы как «почищено».

Проверка

  • yaml.safe_load обоих файлов — валидны, состав job'ов не изменился
    (deploy.yml: changes/build-backend/build-worker/build-frontend/deploy;
    deploy-tradein.yml: changes/test/build-backend/build-frontend/build-browser/deploy)
  • шаг уборки — последний во всех шести сборочных job'ах (проверено разбором YAML,
    а не глазами)
  • pre-commit check yaml — passed
  • боевая проверка: после мержа посчитать docker ps | grep -c buildx_buildkit
    до и после следующего деплоя — число не должно расти

Что этот PR НЕ делает

  • Не сносит 20 уже накопившихся билдеров — это ручное действие на проде, решение владельца
    (пункт 1 в #2869). Кэш в них я сегодня вычистил (buildctl prune --all, только
    регенерируемый кэш): 132G → 117G занято, свободно 13G → 28G.
  • Не добавляет сторож на заполнение диска (пункт 3 в #2869) — отдельной задачей.

Refs #2869

## Зачем Сегодня деплой упал так: ``` #15 ERROR: mkdir /var/lib/buildkit/...: no space left on device ``` Диск VPS был на **94%** (145G / 136G занято / 9.0G свободно). Тем же дефицитом покрасились ещё две проверки на других PR — один дефицит, три «независимых» симптома. ## Корень `docker/setup-buildx-action@v3` создаёт билдер `docker-container` **на каждый прогон**, а его post-step под Forgejo `act_runner` не срабатывает. На хосте накопилось **20 контейнеров** `buildx_buildkit_builder-*` возрастом до двух месяцев: ``` buildx_buildkit_builder-*_state × 10 19.3 GB tradein-postgres-data 21.6 GB ← данные gendesign_postgres_data 15.2 GB ← данные ``` Почему рост незаметен до отказа: - тома **не** dangling (контейнеры существуют) → `docker volume prune` их не видит; - `docker system df` показывает `Build Cache 0B`, потому что кэш живёт **внутри** контейнеров-билдеров, а не у хостового демона. Инструмент, слепой по построению: смотришь на «0B» и делаешь вывод, что чистить нечего. ## Что делает PR Шесть сборочных job'ов (три в `deploy.yml`, три в `deploy-tradein.yml`) получают `id: buildx` у шага setup и шаг уборки в конце: ```yaml - name: Убрать buildx-билдер (#2869 — иначе копятся по одному на прогон) if: always() run: | name="${{ steps.buildx.outputs.name }}" if [ -z "$name" ]; then echo "buildx: имя билдера не пришло из outputs — уборка НЕ сработала (см. #2869)" exit 0 fi echo "buildx: убираю билдер $name" docker buildx rm --force "$name" || echo "buildx: не удалось убрать $name (не фатально)" ``` - `if: always()` — убирает и после упавшей сборки, иначе именно при падениях билдеры и копятся. - Ошибка уборки **не роняет** прогон. - Шаг **говорит вслух**, когда не сработал. Под `act` не гарантировано, что `steps.buildx.outputs.name` заполнится; если он пуст, в логе будет явная строка, а не молчаливый no-op, который читался бы как «почищено». ## Проверка - [x] `yaml.safe_load` обоих файлов — валидны, состав job'ов не изменился (`deploy.yml`: changes/build-backend/build-worker/build-frontend/deploy; `deploy-tradein.yml`: changes/test/build-backend/build-frontend/build-browser/deploy) - [x] шаг уборки — **последний** во всех шести сборочных job'ах (проверено разбором YAML, а не глазами) - [x] pre-commit `check yaml` — passed - [ ] боевая проверка: после мержа посчитать `docker ps | grep -c buildx_buildkit` до и после следующего деплоя — число не должно расти ## Что этот PR НЕ делает - Не сносит 20 уже накопившихся билдеров — это ручное действие на проде, решение владельца (пункт 1 в #2869). Кэш в них я сегодня вычистил (`buildctl prune --all`, только регенерируемый кэш): **132G → 117G занято, свободно 13G → 28G**. - Не добавляет сторож на заполнение диска (пункт 3 в #2869) — отдельной задачей. Refs #2869
bot-backend added 1 commit 2026-08-13 12:42:02 +00:00
ci: убирать buildx-билдер в конце сборочных job'ов
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 11s
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
4f5d61f0e8
setup-buildx-action создаёт билдер `docker-container` на КАЖДЫЙ прогон, а его
post-step под Forgejo act_runner не срабатывает. К 13.08 на хосте накопилось
20 контейнеров buildx_buildkit_builder-* возрастом до двух месяцев, ~19 ГБ в
их `_state`-томах: диск ушёл на 94%, деплой упал с `no space left on device`,
и тем же дефицитом покрасились ещё две проверки на других PR.

Тома не dangling (контейнеры существуют), поэтому `docker volume prune` их не
видит; `docker system df` показывает `Build Cache 0B`, потому что кэш живёт
внутри контейнеров, а не у хостового демона — рост незаметен до отказа.

Шесть сборочных job'ов (три в deploy.yml, три в deploy-tradein.yml) получают
`id: buildx` и шаг уборки в конце. `if: always()` — чтобы убирал и после
падения сборки; ошибка уборки не роняет прогон.

Шаг ГОВОРИТ, когда не сработал: если `steps.buildx.outputs.name` пуст (а под
act это возможно), в лог уходит явная строка, а не молчаливый no-op.

Refs #2869
Light1YT added 1 commit 2026-08-13 12:43:50 +00:00
ci: подбирать протёкшие buildx-билдеры на входе сборочных job'ов
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
9a43f00a5e
Поправка к первому коммиту: там я написал, что post-step у setup-buildx-action
«не срабатывает». Замер это опроверг.

Даты создания 20 висящих билдеров: 17.05, 30.05, 31.05, 13.06, 17.06, 20.06,
28.06, 05.07 — и НИ ОДНОГО за пять недель между 05.07 и 13.08. Два последних
созданы 13.08 в 11:57:43, ровно тем прогоном, что упал с
`no space left on device`. То есть штатная уборка работает, а билдеры текут,
когда job умирает АВАРИЙНО (ENOSPC, OOM, отмена concurrency-группой) — тогда
контейнер job'а мёртв и завершающие шаги не выполняются вообще.

Из этого следует, что завершающий шаг из первого коммита сам по себе почти
бесполезен: он не отработает ровно в тех случаях, которые и создают мусор.
Поэтому добавлен шаг НА ВХОДЕ: подбирает чужие протёкшие билдеры старше
6 часов (самый долгий job — ~17 минут, живой прогон под порог не попадает)
вместе с их `_state`-томами и печатает, сколько убрал и сколько места на диске.

Завершающий шаг оставлен — он дешёвый и закрывает обычные падения.

Refs #2869
Author
Collaborator

Поправка к описанию выше — механизм оказался другой, и первая версия PR была почти бесполезной

В первом коммите я написал, что post-step у setup-buildx-action «не срабатывает под
act_runner». Замер это опроверг. Даты создания всех 20 висящих билдеров:

17.05 ×2   30.05 ×2   31.05 ×3   13.06 ×1   17.06 ×1
20.06 ×4   28.06 ×1   05.07 ×4   13.08 ×2

Между 05.07 и 13.08 — пять недель без единой протечки, хотя деплоев за это время были
десятки. А два сегодняшних созданы в 11:57:43 — ровно тем прогоном, который упал с
no space left on device.

Значит штатная уборка работает, а билдеры текут, когда job умирает аварийно
(ENOSPC, OOM, отмена concurrency-группой): контейнер job'а уже мёртв, и никакие
завершающие шаги — включая мой if: always() — не выполняются. То есть первая версия
этого PR не сработала бы ровно в тех случаях, которые и создают мусор: защита, которая
не защищает.

Что теперь

Добавлен шаг на входе каждого сборочного job'а: подбирает ЧУЖИЕ протёкшие билдеры
старше 6 часов вместе с их _state-томами.

  • Порог 6 часов: самый долгий job здесь ~17 минут, поэтому под порог не может попасть
    живой параллельный прогон.
  • Шаг печатает, сколько убрал, сколько оставил свежих, и df -h / — чтобы заполнение
    диска было видно в логе КАЖДОГО деплоя, а не всплывало красным один раз в три месяца.
  • Завершающий шаг оставлен: он дёшев и закрывает обычные падения.

Внимание — это удаляет контейнеры и тома на хосте раннера. Именно поэтому пишу явно:
после мержа первый же деплой уберёт 18 исторических билдеров (17.05–05.07). Кэш из них
я уже вычистил вручную, так что по месту выигрыш будет небольшой, но дальше мусор
перестанет накапливаться. Если такое авто-удаление нежелательно — скажи, переделаю на
«только отчитываться, не удалять».

Проверка после мержа

ssh gendesign 'docker ps --format "{{.Names}}" | grep -c buildx_buildkit'

До мержа: 20. После первого деплоя ожидаю ≤ 3 (только свежие этого прогона),
и строку buildx: убрано протёкших N в логе job'а.

## Поправка к описанию выше — механизм оказался другой, и первая версия PR была почти бесполезной В первом коммите я написал, что post-step у `setup-buildx-action` «не срабатывает под act_runner». **Замер это опроверг.** Даты создания всех 20 висящих билдеров: ``` 17.05 ×2 30.05 ×2 31.05 ×3 13.06 ×1 17.06 ×1 20.06 ×4 28.06 ×1 05.07 ×4 13.08 ×2 ``` Между **05.07 и 13.08 — пять недель без единой протечки**, хотя деплоев за это время были десятки. А два сегодняшних созданы в **11:57:43** — ровно тем прогоном, который упал с `no space left on device`. Значит штатная уборка работает, а билдеры текут, когда job умирает **аварийно** (ENOSPC, OOM, отмена concurrency-группой): контейнер job'а уже мёртв, и никакие завершающие шаги — включая мой `if: always()` — не выполняются. То есть первая версия этого PR не сработала бы ровно в тех случаях, которые и создают мусор: защита, которая не защищает. ## Что теперь Добавлен шаг **на входе** каждого сборочного job'а: подбирает ЧУЖИЕ протёкшие билдеры старше 6 часов вместе с их `_state`-томами. - Порог 6 часов: самый долгий job здесь ~17 минут, поэтому под порог не может попасть живой параллельный прогон. - Шаг печатает, сколько убрал, сколько оставил свежих, и `df -h /` — чтобы заполнение диска было видно в логе КАЖДОГО деплоя, а не всплывало красным один раз в три месяца. - Завершающий шаг оставлен: он дёшев и закрывает обычные падения. **Внимание — это удаляет контейнеры и тома на хосте раннера.** Именно поэтому пишу явно: после мержа первый же деплой уберёт 18 исторических билдеров (17.05–05.07). Кэш из них я уже вычистил вручную, так что по месту выигрыш будет небольшой, но дальше мусор перестанет накапливаться. Если такое авто-удаление нежелательно — скажи, переделаю на «только отчитываться, не удалять». ## Проверка после мержа ```bash ssh gendesign 'docker ps --format "{{.Names}}" | grep -c buildx_buildkit' ``` До мержа: **20**. После первого деплоя ожидаю **≤ 3** (только свежие этого прогона), и строку `buildx: убрано протёкших N` в логе job'а.
Light1YT added 1 commit 2026-08-13 12:45:39 +00:00
ci: подбор билдеров не должен падать под bash -e
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
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
b58adbbe3d
`docker rm -f ... && reaped=$((reaped+1))` отдельным оператором: при неудаче
удаления вся конструкция возвращает ненулевой код, и под `bash -e` (режим
`run:` в Actions) это уронило бы шаг — то есть уборка мусора могла бы уронить
деплой. Переписано на if/else с явным сообщением в лог.

Логика порога проверена на РЕАЛЬНЫХ датах с прода (20 контейнеров):
убрало бы 18, оставило 2 (сегодняшние, возрастом ~1ч).

Refs #2869
bot-backend merged commit 92593404fa into main 2026-08-13 12:48:29 +00:00
bot-backend deleted branch fix/2869-buildx-cleanup 2026-08-13 12:48:29 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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#2870
No description provided.