chore(ops): чинить корень утечки docker-томов + еженедельная уборка #2887

Merged
lekss361 merged 1 commit from chore/docker-autoprune into main 2026-08-15 14:12:01 +00:00
Owner

Проблема

Диск на прод-VM был занят на 76% (110 из 145 ГБ). Разбор: 201 том-сирота на 12.6 ГБ.

Что Сколько Что внутри
Анонимные (64-символьный hex) 125 каталоги данных PostgreSQL 16
FORGEJO-ACTIONS-TASK-* 76 окружения задач CI
Именованные со смыслом 0

Прод-данных среди сирот нет ни одного — проверено поимённо перед удалением.

Корневая причина

ci.yml и ci-tradein.yml поднимают свой postgres на прогон и снимают его так:

docker rm -f "$CI_PG"     # без -v

Контейнер уходит, анонимный том с данными остаётся — по одному на каждый прогон CI. Отсюда ровно 125 постгресов.

Что сделано

1. Фикс корняdocker rm -f "$CI_PG"docker rm -fv в четырёх местах (ci.yml, ci-tradein.yml).

Важное разграничение: в deploy-*.yml тот же вызов применяется к боевым контейнерам при пересоздании. Туда -v добавлять нельзя — снесло бы тома с данными прода. Не тронуто, проверено отдельно.

2. ops/docker-prune.sh — страховка на то, что runner не убрал за собой.

Удаляет: остановленные контейнеры старше 24ч, висячие образы старше 7 суток, и тома-сироты только двух известных форм — hex и FORGEJO-ACTIONS-TASK-*.

Именованные тома не трогаются никогда, даже если в моменте оказались отцеплены. Голый docker volume prune такой разницы не делает — поэтому здесь он намеренно не используется: при пересоздании контейнера он мог бы снести боевой том.

Порядок операций важен: контейнеры → образы → тома. Обратный порядок оставляет тома, освободившиеся после удаления контейнеров — на этом я и споткнулся при ручной чистке, после первого прохода осталось ещё 78 сирот.

Проверено на проде

  • DRY_RUN=1 → поймал ошибку в самом скрипте (until не поддерживается в docker container ls, только в prune) — исправлено
  • боевой прогон → 78 томов удалено
  • повторный прогон → чистый no-op, «томов-сирот известных форм нет»
было стало
Диск 110 ГБ / 76% 97 ГБ / 67%
Томов 220 15, все используются
Освобождаемо 13.09 ГБ 0 B

Cron

Уже установлен на VM (воскресенье 04:00 UTC — свободный слот рядом с бэкапами в 03:30 и 04:30):

0 4 * * 0 bash /opt/gendesign/ops/docker-prune.sh >> /tmp/gendesign-docker-prune.log 2>&1

Скрипт попадёт в /opt/gendesign/ops/ автоматически при ближайшем деплое (git reset --hard origin/main). До этого момента cron будет писать в лог, что файла нет, — это ожидаемо и безвредно.

Скрипт логирует предупреждение, если после уборки диск всё равно занят ≥85% — уборки уже недостаточно, нужен взгляд глазами.

## Проблема Диск на прод-VM был занят на **76% (110 из 145 ГБ)**. Разбор: **201 том-сирота на 12.6 ГБ**. | Что | Сколько | Что внутри | |---|---|---| | Анонимные (64-символьный hex) | 125 | каталоги данных PostgreSQL 16 | | `FORGEJO-ACTIONS-TASK-*` | 76 | окружения задач CI | | Именованные со смыслом | **0** | — | Прод-данных среди сирот нет ни одного — проверено поимённо перед удалением. ## Корневая причина `ci.yml` и `ci-tradein.yml` поднимают свой postgres на прогон и снимают его так: ```bash docker rm -f "$CI_PG" # без -v ``` Контейнер уходит, **анонимный том с данными остаётся** — по одному на каждый прогон CI. Отсюда ровно 125 постгресов. ## Что сделано **1. Фикс корня** — `docker rm -f "$CI_PG"` → `docker rm -fv` в четырёх местах (`ci.yml`, `ci-tradein.yml`). Важное разграничение: в `deploy-*.yml` тот же вызов применяется к **боевым** контейнерам при пересоздании. Туда `-v` добавлять нельзя — снесло бы тома с данными прода. Не тронуто, проверено отдельно. **2. `ops/docker-prune.sh`** — страховка на то, что runner не убрал за собой. Удаляет: остановленные контейнеры старше 24ч, висячие образы старше 7 суток, и тома-сироты **только двух известных форм** — hex и `FORGEJO-ACTIONS-TASK-*`. Именованные тома не трогаются **никогда**, даже если в моменте оказались отцеплены. Голый `docker volume prune` такой разницы не делает — поэтому здесь он намеренно не используется: при пересоздании контейнера он мог бы снести боевой том. **Порядок операций важен**: контейнеры → образы → тома. Обратный порядок оставляет тома, освободившиеся после удаления контейнеров — на этом я и споткнулся при ручной чистке, после первого прохода осталось ещё 78 сирот. ## Проверено на проде - `DRY_RUN=1` → поймал ошибку в самом скрипте (`until` не поддерживается в `docker container ls`, только в `prune`) — исправлено - боевой прогон → 78 томов удалено - повторный прогон → **чистый no-op**, «томов-сирот известных форм нет» | | было | стало | |---|---|---| | Диск | 110 ГБ / 76% | **97 ГБ / 67%** | | Томов | 220 | **15**, все используются | | Освобождаемо | 13.09 ГБ | **0 B** | ## Cron Уже установлен на VM (воскресенье 04:00 UTC — свободный слот рядом с бэкапами в 03:30 и 04:30): ``` 0 4 * * 0 bash /opt/gendesign/ops/docker-prune.sh >> /tmp/gendesign-docker-prune.log 2>&1 ``` Скрипт попадёт в `/opt/gendesign/ops/` автоматически при ближайшем деплое (`git reset --hard origin/main`). До этого момента cron будет писать в лог, что файла нет, — это ожидаемо и безвредно. Скрипт логирует предупреждение, если после уборки диск всё равно занят ≥85% — уборки уже недостаточно, нужен взгляд глазами.
lekss361 added 1 commit 2026-08-15 13:53:54 +00:00
chore(ops): чинить корень утечки docker-томов + еженедельная уборка
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 59s
CI Trade-In / frontend-checks (pull_request) Successful in 2m8s
CI / frontend-tests (pull_request) Successful in 2m14s
CI / openapi-codegen-check (pull_request) Successful in 3m2s
CI Trade-In / backend-tests (pull_request) Successful in 5m27s
CI / backend-tests (pull_request) Successful in 17m38s
ddb76a137b
Диск был занят на 76% (110 из 145 ГБ). Разбор: 201 том-сирота на 12.6 ГБ —
125 анонимных (каталоги данных PostgreSQL от тестовых прогонов CI) и 76
окружений задач Forgejo Actions. Прод-данных среди них нет ни одного.

Корневая причина: ci.yml и ci-tradein.yml поднимают свой postgres и снимают
его через `docker rm -f` БЕЗ `-v`. Контейнер уходит, анонимный том с данными
остаётся сиротой — по одному на каждый прогон CI.

- ci.yml / ci-tradein.yml: `docker rm -f "$CI_PG"` → `docker rm -fv` (4 места).
  В deploy-*.yml тот же вызов применяется к БОЕВЫМ контейнерам — туда -v
  добавлять нельзя, снесло бы тома с данными прода. Не тронуто.

- ops/docker-prune.sh — страховка на то, что runner не убрал за собой.
  Удаляет: остановленные контейнеры старше 24ч, висячие образы старше 7 суток
  и тома-сироты ТОЛЬКО двух известных форм (64-символьный hex и
  FORGEJO-ACTIONS-TASK-*). Именованные тома не трогаются никогда — голый
  `docker volume prune` такой разницы не делает, поэтому здесь не используется.
  Порядок важен: контейнеры → образы → тома, иначе освободившиеся после
  контейнеров тома останутся до следующего запуска (на этом я и споткнулся
  при ручной чистке — после prune осталось ещё 78 сирот).

Проверено на проде: DRY_RUN, затем боевой прогон, затем повторный — no-op.
Тома 220 → 15, все используются, освобождать нечего. Диск 76% → 67%.
Cron поставлен: вс 04:00 UTC (свободный слот рядом с бэкапами).
lekss361 merged commit b5ea336c71 into main 2026-08-15 14:12:01 +00:00
lekss361 deleted branch chore/docker-autoprune 2026-08-15 14:12:01 +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#2887
No description provided.