Бэкап, из которого нельзя восстановиться — не бэкап. Мы внедрили ежемесячные учения по восстановлению (DR Drill) и автоматическую еженедельную проверку.
Автоматический тест восстановления (еженедельно):
#!/bin/bash
# /opt/backup/scripts/restore_test.sh
# Автоматическое тестовое восстановление PostgreSQL бэкапа
set -euo pipefail
TEST_DIR="/tmp/restore_test_$(date +%s)"
TEST_PORT=5433 # Отдельный порт, чтобы не конфликтовать с production
LOG="/var/log/backup/restore_test.log"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG"; }
cleanup() {
pg_ctl -D "$TEST_DIR" stop 2>/dev/null || true
rm -rf "$TEST_DIR"
}
trap cleanup EXIT
# Находим последний бэкап
LATEST=$(ls -td /backup/postgresql/*/ | head -1)
log "Testing restore from: $LATEST"
# Распаковываем бэкап
mkdir -p "$TEST_DIR"
tar xzf "$LATEST/base.tar.gz" -C "$TEST_DIR"
tar xzf "$LATEST/pg_wal.tar.gz" -C "$TEST_DIR/pg_wal/"
# Запускаем тестовый PostgreSQL на отдельном порту
cat >> "$TEST_DIR/postgresql.conf" << EOF
port = $TEST_PORT
unix_socket_directories = '/tmp'
log_destination = 'stderr'
logging_collector = off
EOF
# Создаём recovery signal
touch "$TEST_DIR/recovery.signal"
cat >> "$TEST_DIR/postgresql.conf" << EOF
restore_command = 'cp /backup/postgresql/wal_archive/%f %p || true'
recovery_target = 'immediate'
recovery_target_action = 'promote'
EOF
# Стартуем и ждём восстановления
pg_ctl -D "$TEST_DIR" -l "$TEST_DIR/startup.log" start
sleep 10
# Проверяем, что база поднялась и данные читаемы
TEST_RESULT=$(psql -h /tmp -p $TEST_PORT -U postgres -d medplus \
-c "SELECT COUNT(*) FROM patients" -t 2>&1)
if [[ "$TEST_RESULT" =~ ^[0-9]+$ ]] && [ "$TEST_RESULT" -gt 0 ]; then
log "RESTORE TEST PASSED: $TEST_RESULT patients found"
# Дополнительные проверки
TABLES=$(psql -h /tmp -p $TEST_PORT -U postgres -d medplus \
-c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public'" -t)
log "Tables count: $TABLES"
# Сравниваем количество записей с production
PROD_COUNT=$(psql -h /var/run/postgresql -U postgres -d medplus \
-c "SELECT COUNT(*) FROM patients" -t)
DIFF=$((PROD_COUNT - TEST_RESULT))
log "Records difference from production: $DIFF"
echo '{"status":"ok","timestamp":'$(date +%s)',"patients":'$TEST_RESULT',"diff":'$DIFF'}' \
> /opt/backup/status/restore_test.json
else
log "RESTORE TEST FAILED: $TEST_RESULT"
echo '{"status":"failed","timestamp":'$(date +%s)'}' \
> /opt/backup/status/restore_test.json
# Алерт в Telegram
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d chat_id="$CHAT_ID" \
-d text="RESTORE TEST FAILED on $(hostname)! Check $LOG"
fi
Ежемесячные ручные учения: раз в месяц мы проводим полноценный DR Drill — восстановление на чистой виртуальной машине с нуля. Процедура задокументирована в runbook, и каждый раз её выполняет другой инженер клиента (чтобы навык не зависел от одного человека).
Результаты учений фиксируются в журнале:
# /opt/backup/drills/drill_log.csv
# date,engineer,type,rto_target,rto_actual,data_verified,notes
2026-02-01,Иванов,full_restore,30min,22min,yes,"Штатное восстановление"
2026-03-01,Петрова,full_restore,30min,45min,yes,"S3 был медленный, увеличили bandwidth"
2026-04-01,Сидоров,full_restore,30min,18min,yes,"Оптимизировали скрипт, параллельный rsync"
Оставить комментарий