feat(ops): DR-тест восстановления бэкапа PG + runbook HA/DR
Закрыта слепая зона «бэкапы есть, но восстановление не проверялось». - scripts/ops/pg_restore_verify.sh — берёт последний дамп из MinIO backups/pg/, restore в ЭФЕМЕРНЫЙ postgres:16, проверяет ключевые таблицы (users/documents/ tasks). Прод не трогает, всё в одноразовом контейнере. Под cron раз в неделю. - docs/DR-HA.md — runbook: бэкапы, проверка восстановления, потоковая репликация PG, Redis-реплика+Sentinel, таблица SPOF со статусом митигаций. Провижн реплик PG/Redis — на Proxmox (нужен новый LXC), это работа на железе, не в репозитории; runbook даёт конкретные шаги. Векторный SPOF уже снимается Qdrant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
71
scripts/ops/pg_restore_verify.sh
Executable file
71
scripts/ops/pg_restore_verify.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
# DR-тест: проверка ВОССТАНОВИМОСТИ бэкапа PostgreSQL.
|
||||
#
|
||||
# Берёт последний дамп из MinIO (backups/pg/), поднимает ЭФЕМЕРНЫЙ контейнер
|
||||
# postgres:16, восстанавливает в него дамп и проверяет, что ключевые таблицы не
|
||||
# пусты. Прод не трогается — всё в одноразовом контейнере/сети, удаляются на выходе.
|
||||
#
|
||||
# Зачем: бэкапы делаются (cron на 1.38), но восстановление раньше никто не проверял —
|
||||
# «непроверенный бэкап = отсутствие бэкапа». Запускать по cron раз в неделю.
|
||||
#
|
||||
# Требуется docker + доступ до MinIO. Креды берутся из .env (или окружения):
|
||||
# MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY,
|
||||
# MINIO_BUCKET_BACKUPS (=backups), POSTGRES_DB, POSTGRES_USER.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
if [ -f "$ROOT/.env" ]; then set -a; . "$ROOT/.env"; set +a; fi
|
||||
|
||||
: "${MINIO_ENDPOINT:?нужен MINIO_ENDPOINT}"
|
||||
: "${MINIO_ACCESS_KEY:?нужен MINIO_ACCESS_KEY}"
|
||||
: "${MINIO_SECRET_KEY:?нужен MINIO_SECRET_KEY}"
|
||||
BUCKET="${MINIO_BUCKET_BACKUPS:-backups}"
|
||||
PREFIX="${PG_BACKUP_PREFIX:-pg/}"
|
||||
PGDB="${POSTGRES_DB:-antiplagiator}"
|
||||
PGUSER="${POSTGRES_USER:-antiplagiator}"
|
||||
|
||||
SUFFIX="$$-$RANDOM"
|
||||
PG="drtest-pg-$SUFFIX"
|
||||
WORK="$(mktemp -d)"
|
||||
MC_HOST="http://${MINIO_ACCESS_KEY}:${MINIO_SECRET_KEY}@${MINIO_ENDPOINT}"
|
||||
|
||||
cleanup() { docker rm -f "$PG" >/dev/null 2>&1 || true; rm -rf "$WORK"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "▶ Поиск последнего дампа в minio://$BUCKET/$PREFIX"
|
||||
LATEST="$(docker run --rm -e MC_HOST_dr="$MC_HOST" minio/mc:latest \
|
||||
sh -c "mc ls --recursive dr/$BUCKET/$PREFIX | awk '{print \$NF}' | grep -E '\.(sql\.)?gz$' | sort | tail -1")"
|
||||
[ -n "$LATEST" ] || { echo "❌ дампы не найдены"; exit 1; }
|
||||
FNAME="$(basename "$LATEST")"
|
||||
echo " последний: $FNAME"
|
||||
|
||||
echo "▶ Скачивание дампа"
|
||||
docker run --rm -e MC_HOST_dr="$MC_HOST" -v "$WORK":/w minio/mc:latest \
|
||||
sh -c "mc cp 'dr/$BUCKET/$PREFIX$FNAME' /w/dump.gz"
|
||||
|
||||
echo "▶ Эфемерный postgres:16"
|
||||
docker run -d --name "$PG" \
|
||||
-e POSTGRES_PASSWORD=drtest -e POSTGRES_DB="$PGDB" -e POSTGRES_USER="$PGUSER" \
|
||||
postgres:16 >/dev/null
|
||||
for _ in $(seq 1 30); do
|
||||
docker exec "$PG" pg_isready -U "$PGUSER" -d "$PGDB" >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "▶ Восстановление"
|
||||
gunzip -c "$WORK/dump.gz" | docker exec -i "$PG" psql -v ON_ERROR_STOP=0 -U "$PGUSER" -d "$PGDB" >/dev/null 2>&1
|
||||
|
||||
echo "▶ Проверка ключевых таблиц"
|
||||
rc=0
|
||||
for tbl in users documents tasks; do
|
||||
n="$(docker exec "$PG" psql -U "$PGUSER" -d "$PGDB" -tAc "SELECT count(*) FROM $tbl" 2>/dev/null | tr -d '[:space:]' || echo ERR)"
|
||||
echo " $tbl: ${n:-ERR}"
|
||||
{ [ -z "$n" ] || [ "$n" = "ERR" ]; } && rc=1
|
||||
done
|
||||
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
echo "✅ Бэкап восстанавливается, ключевые таблицы на месте ($FNAME)"
|
||||
else
|
||||
echo "❌ Восстановление не прошло проверку — разобраться СРОЧНО"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user