Merge pull request 'chore(ops): чинить корень утечки docker-томов + еженедельная уборка' (#2887) from chore/docker-autoprune into main
This commit is contained in:
commit
b5ea336c71
3 changed files with 106 additions and 4 deletions
|
|
@ -116,7 +116,7 @@ jobs:
|
|||
# бы, а тесты всё равно скипались.
|
||||
run: |
|
||||
set -u
|
||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker rm -fv "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$CI_PG" \
|
||||
-e POSTGRES_DB=tradein -e POSTGRES_USER=tradein -e POSTGRES_PASSWORD=tradein \
|
||||
postgis/postgis:16-3.4
|
||||
|
|
@ -221,7 +221,7 @@ jobs:
|
|||
# отменён concurrency-группой. Иначе на раннере копятся мёртвые контейнеры.
|
||||
if: always()
|
||||
working-directory: .
|
||||
run: docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
run: docker rm -fv "$CI_PG" >/dev/null 2>&1 || true
|
||||
|
||||
# Тесты браузерного сайдкара (#2722). До этого job'а они не бежали НИГДЕ:
|
||||
# ci-tradein гейтил только backend/frontend, deploy-tradein — тоже, а каталог
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ jobs:
|
|||
# здесь не нужен вовсе, в отличие от tradein-лэйна.
|
||||
run: |
|
||||
set -u
|
||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker rm -fv "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker run -d --name "$CI_PG" \
|
||||
-e POSTGRES_DB=gendesign_ci -e POSTGRES_USER=gendesign -e POSTGRES_PASSWORD=gendesign \
|
||||
postgres:16
|
||||
|
|
@ -279,7 +279,7 @@ jobs:
|
|||
working-directory: .
|
||||
run: |
|
||||
echo "### шаг «Снести тестовый Postgres» начался (CI_PG=${CI_PG:-<пусто>})"
|
||||
docker rm -f "$CI_PG" >/dev/null 2>&1 || true
|
||||
docker rm -fv "$CI_PG" >/dev/null 2>&1 || true
|
||||
echo "### шаг «Снести тестовый Postgres» закончился успешно"
|
||||
|
||||
frontend-tests:
|
||||
|
|
|
|||
102
ops/docker-prune.sh
Normal file
102
ops/docker-prune.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env bash
|
||||
# Периодическая уборка docker-мусора на прод-VM.
|
||||
#
|
||||
# ЗАЧЕМ. 2026-08-15 диск был занят на 76% (110 из 145 ГБ). Разбор показал 201
|
||||
# том-сироту на 12.6 ГБ: 125 анонимных — каталоги данных PostgreSQL от тестовых
|
||||
# прогонов CI, 76 — окружения задач Forgejo Actions. Прод-данных среди них не
|
||||
# было ни одного.
|
||||
#
|
||||
# Корневая причина анонимных томов устранена отдельно: ci.yml и ci-tradein.yml
|
||||
# снимали свой postgres через `docker rm -f` БЕЗ `-v`, поэтому контейнер уходил,
|
||||
# а его том оставался. Теперь там `docker rm -fv`. Этот скрипт — страховка: он
|
||||
# подбирает то, что runner не убрал за собой, и то, что накопилось раньше.
|
||||
#
|
||||
# ЧТО ИМЕННО УДАЛЯЕТСЯ (осознанно консервативно):
|
||||
# - остановленные контейнеры старше 24ч;
|
||||
# - висячие (dangling) образы старше 7 суток;
|
||||
# - тома-сироты ТОЛЬКО двух известных форм: 64-символьный hex (анонимные) и
|
||||
# FORGEJO-ACTIONS-TASK-*. Именованные тома со смыслом (gendesign_postgres_data,
|
||||
# tradein-postgres-data, *_caddy_*, couchdb, redis и любые будущие) не трогаются
|
||||
# НИКОГДА — даже если в моменте оказались отцеплены. Голый `docker volume prune`
|
||||
# такой разницы не делает, поэтому здесь он намеренно не используется.
|
||||
#
|
||||
# Usage (cron на прод-VM; `bash <путь>`, а не голый путь — тогда снятый +x не ломает).
|
||||
# Лог в /tmp — как у соседних записей в том же crontab (backup.sh, backfill'ы):
|
||||
# 0 4 * * 0 bash /opt/gendesign/ops/docker-prune.sh >> /tmp/gendesign-docker-prune.log 2>&1
|
||||
#
|
||||
# Воскресенье 04:00 UTC — свободный слот: рядом 03:30 backup.sh, 04:30 backup
|
||||
# tradein, 05:00+ backfill'ы.
|
||||
#
|
||||
# Раз в неделю достаточно: после устранения корневой причины (docker rm -fv в CI)
|
||||
# копятся только тома runner'а. DRY_RUN=1 — показать, что удалится, не трогая.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DRY_RUN="${DRY_RUN:-0}"
|
||||
STOPPED_AGE="${STOPPED_AGE:-24h}"
|
||||
IMAGE_AGE="${IMAGE_AGE:-168h}"
|
||||
|
||||
log() { printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; }
|
||||
|
||||
disk_used_pct() { df --output=pcent / | tail -1 | tr -dc '0-9'; }
|
||||
|
||||
before_pct="$(disk_used_pct)"
|
||||
log "старт: диск занят ${before_pct}%"
|
||||
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
log "DRY_RUN=1 — только показываю"
|
||||
fi
|
||||
|
||||
# ── 1. остановленные контейнеры ───────────────────────────────────────────────
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
# `until` поддерживает только `prune`, у `ls` его нет («invalid filter 'until'»),
|
||||
# поэтому в dry-run считаем ВСЕ остановленные — это верхняя оценка.
|
||||
log "остановленных контейнеров всего (удалятся только старше ${STOPPED_AGE}): \
|
||||
$(docker container ls -aq --filter "status=exited" | wc -l)"
|
||||
else
|
||||
log "контейнеры: $(docker container prune -f --filter "until=${STOPPED_AGE}" \
|
||||
2>&1 | tail -1)"
|
||||
fi
|
||||
|
||||
# ── 2. висячие образы ─────────────────────────────────────────────────────────
|
||||
if [[ "$DRY_RUN" == "1" ]]; then
|
||||
log "висячих образов: $(docker image ls -qf dangling=true | wc -l)"
|
||||
else
|
||||
log "образы: $(docker image prune -f --filter "until=${IMAGE_AGE}" 2>&1 | tail -1)"
|
||||
fi
|
||||
|
||||
# ── 3. тома-сироты известных форм ─────────────────────────────────────────────
|
||||
# Отбираем ПОИМЁННО, а не через `docker volume prune`: тот снёс бы любой
|
||||
# отцепленный именованный том, включая боевой, если контейнер в моменте пересоздаётся.
|
||||
mapfile -t candidates < <(
|
||||
docker volume ls -qf dangling=true \
|
||||
| grep -E '^([0-9a-f]{64}|FORGEJO-ACTIONS-TASK-.*)$' || true
|
||||
)
|
||||
|
||||
skipped="$(docker volume ls -qf dangling=true \
|
||||
| grep -vE '^([0-9a-f]{64}|FORGEJO-ACTIONS-TASK-.*)$' || true)"
|
||||
if [[ -n "$skipped" ]]; then
|
||||
log "ПРОПУЩЕНЫ (именованные, руками): $(echo "$skipped" | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
if [[ "${#candidates[@]}" -eq 0 ]]; then
|
||||
log "томов-сирот известных форм нет"
|
||||
elif [[ "$DRY_RUN" == "1" ]]; then
|
||||
log "томов к удалению: ${#candidates[@]}"
|
||||
else
|
||||
removed=0
|
||||
for v in "${candidates[@]}"; do
|
||||
if docker volume rm "$v" >/dev/null 2>&1; then
|
||||
removed=$((removed + 1))
|
||||
fi
|
||||
done
|
||||
log "томов удалено: ${removed} из ${#candidates[@]}"
|
||||
fi
|
||||
|
||||
after_pct="$(disk_used_pct)"
|
||||
log "готово: диск занят ${after_pct}% (было ${before_pct}%)"
|
||||
|
||||
# Сигнал в лог, если места всё равно мало — повод посмотреть глазами.
|
||||
if [[ "$after_pct" -ge 85 ]]; then
|
||||
log "ВНИМАНИЕ: диск занят ${after_pct}% — уборки уже недостаточно"
|
||||
fi
|
||||
Loading…
Add table
Reference in a new issue