Практикум DR — стенд и бэкап
На этом шаге вы поднимаете учебный PostgreSQL в Docker, наполняете базу тестовыми заказами, создаёте логический бэкап через pg_dump и копируете dump в offsite-хранилище. Offsite в lab — отдельная папка на другом диске, другая машина или bucket в облаке; главное — это не тот же Docker volume pgdata, где лежат живые данные. Параллельно вы начнёте runbook — документ, по которому в шаге 3 выполните restore drill.
1. PostgreSQL в Docker
Контейнер pg-dr-lab слушает порт 5433 на хосте (чтобы не конфликтовать с локальным Postgres на 5432). Данные persist в named volume pgdata.
docker run -d --name pg-dr-lab -e POSTGRES_PASSWORD=lab -e POSTGRES_DB=shop \
-v pgdata:/var/lib/postgresql/data -p 5433:5432 postgres:16-alpine
Проверка, что контейнер жив:
docker ps --filter name=pg-dr-lab
docker exec pg-dr-lab pg_isready -U postgres
Тестовые данные имитируют простой интернет-магазин — таблица orders с двумя строками. Время created_at пригодится позже для демонстрации RPO gap (заказы после dump не восстановятся).
docker exec -it pg-dr-lab psql -U postgres -d shop -c \
"CREATE TABLE orders(id serial PRIMARY KEY, item text, created_at timestamptz default now());
INSERT INTO orders(item) VALUES ('widget'), ('gadget');"
docker exec pg-dr-lab psql -U postgres -d shop -c "SELECT id, item, created_at FROM orders;"
Запишите в runbook версию образа (postgres:16-alpine), имя volume (pgdata) и пароль lab — в production пароль берут из секрет-хранилища, в lab допустим явный текст.
2. Логический бэкап и формат dump
pg_dump — утилита PostgreSQL для создания логической копии базы: схема, данные, индексы в переносимом файле. Флаг -Fc (custom format) даёт сжатый бинарный dump, который восстанавливают через pg_restore с опциями --clean, параллельным -j и выборочным restore объектов.
| Формат | Команда | Плюсы для DR |
|---|---|---|
Custom -Fc | pg_dump -Fc | Сжатие, pg_restore, гибкий restore |
| Plain SQL | pg_dump -Fp | Читаемость, медленнее, один большой SQL |
Directory -Fd | pg_dump -Fd | Параллельный dump/restore на больших базах |
Создайте две папки на хосте: backup-local (рабочая копия рядом с lab) и backup-offsite (симуляция удалённого хранилища).
mkdir -p ./backup-local ./backup-offsite
docker exec pg-dr-lab pg_dump -U postgres -Fc shop > ./backup-local/shop-$(date +%Y%m%d-%H%M).dump
cp ./backup-local/shop-*.dump ./backup-offsite/
Проверьте размер и целостность файла:
ls -lh ./backup-offsite/
file ./backup-offsite/shop-*.dump
Offsite simulation: папка backup-offsite на другом диске, sync в S3 через aws s3 cp, rclone copy на второй VPS или Yandex Object Storage — любой вариант, где копия переживёт удаление volume pgdata. Копия dump только в backup-local на том же SSD, что и Docker data root, правило 3-2-1 не выполняет.
3. Стратегия расписания бэкапов
Частота pg_dump напрямую задаёт RPO при отсутствии WAL. Таблица ориентиров для pet-проекта:
| Расписание | RPO (худший случай) | Нагрузка на CPU/IO |
|---|---|---|
| Раз в сутки 03:00 | До 24 часов | Минимальная |
| Каждые 6 часов | До 6 часов | Низкая |
| Каждый час | До 1 часа | Средняя на активной базе |
Для учебного магазина с двумя заказами достаточно cron каждые 6 часов. Для production с заказами каждую минуту RPO "15 минут" потребует либо hourly dump, либо архива WAL (8.11/10).
Пример строки cron на хосте (идея, пути подставьте свои):
# 0 */6 * * * pg_dump ... && aws s3 cp ... s3://my-dr-bucket/shop/
После каждого успешного dump фиксируйте timestamp в runbook или в метке имени файла shop-20250615-0300.dump.
4. Offsite в облаке и пример стоимости
Для pet-проекта offsite часто — object storage с lifecycle на cold tier. Пример monthly cost при 6 dump в сутки, размер одного dump 50 МБ после -Fc:
| Статья | Расчёт | Ориентир |
|---|---|---|
| Хранение 30 дней × 6 × 50 МБ ≈ 9 ГБ | S3 Standard ~$0.023/ГБ | ~$0.21/мес |
| Тот же объём | Glacier Instant Retrieval | ~$0.04/мес |
| PUT запросы 180/мес | копейки | менее $0.01 |
Egress при restore из bucket в инциденте — отдельная строка (первые гигабайты часто дешевле основного трафика). Сравнение tier и алерты — FinOps для pet-проекта. Смысл FinOps здесь — не переплачивать за Standard tier для файлов, которые читают раз в квартал на учении.
Команда загрузки в S3 (после локального dump):
aws s3 cp ./backup-local/shop-$(date +%Y%m%d-%H%M).dump \
s3://my-dr-bucket/shop/ --storage-class GLACIER_IR
Аналог для Yandex Cloud — yc storage s3 cp с bucket в другом folder или регионе.
5. Retention — сколько dump хранить
Хранить один последний файл рискованно: битый dump обнаружится только на restore. Минимум для pet-проекта — 7 daily + 4 weekly или правило "последние 30 файлов". Lifecycle в bucket автоматически удаляет объекты старше N дней и экономит storage.
6. Черновик runbook
Запишите в RUNBOOK-dr-shop.md или wiki следующие блоки.
Контекст. База shop, контейнер pg-dr-lab, порт 5433, offsite ./backup-offsite/ или s3://my-dr-bucket/shop/.
Команда restore (полная версия в шаге 3):
docker run -d --name pg-dr-lab-new -e POSTGRES_PASSWORD=lab -e POSTGRES_DB=shop \
-v pgdata-new:/var/lib/postgresql/data -p 5433:5432 postgres:16-alpine
DUMP=$(ls -t ./backup-offsite/*.dump | head -1)
docker exec -i pg-dr-lab-new pg_restore -U postgres -d shop --clean --if-exists < "$DUMP"
On-call — ваш Telegram или email pet-проекта.
Целевые метрики — RTO 1 час, RPO 6 часов (пример; подставьте свои из шага 1).
Last successful test restore — дата пустая до первого учения; после шага 3 впишите фактическую дату и RTO в минутах.
Runbook — документ, который читают в 3 ночи во время инцидента. Короткие команды, без лишней теории.
7. Скрипт backup pipeline (идея)
Объедините dump, checksum и upload в один shell-скрипт backup-shop.sh. Cron вызывает только его — меньше шансов забыть cp в offsite.
#!/bin/bash
set -euo pipefail
STAMP=$(date +%Y%m%d-%H%M)
LOCAL="./backup-local/shop-${STAMP}.dump"
OFFSITE="./backup-offsite/shop-${STAMP}.dump"
docker exec pg-dr-lab pg_dump -U postgres -Fc shop > "$LOCAL"
sha256sum "$LOCAL" > "${LOCAL}.sha256"
cp "$LOCAL" "${LOCAL}.sha256" ./backup-offsite/
# aws s3 cp "$LOCAL" "s3://my-dr-bucket/shop/${STAMP}.dump"
echo "OK ${STAMP}" >> ./backup-offsite/backup.log
После upload проверьте sha256sum -c на копии в offsite. Контрольная сумма ловит обрезанные файлы при нестабильной сети.
8. Мониторинг успеха бэкапа
Cron молча падает, если диск полон. Минимальный мониторинг для pet-проекта:
| Сигнал | Как проверить | Действие |
|---|---|---|
| Файл свежий | find backup-offsite -mtime -1 | Алерт если старше 7 h при cron 6 h |
| Размер dump | не 0 байт | Алерт в Telegram bot |
| Строка в log | tail backup.log | Нет OK за сутки — разбор |
Интеграция с Prometheus practicum — следующий уровень; для lab достаточно email от cron MAILTO=.
9. Логический и физический бэкап — когда переходить
| Критерий | Остаёмся на pg_dump | Добавляем physical + WAL |
|---|---|---|
| Размер БД | до ~50 ГБ | сотни ГБ и выше |
| RPO | часы | минуты |
| Downtime restore | минуты–часы приемлемы | нужен PITR |
| Команда | один человек | DBA / SRE |
Practicum закрывает левую колонку. При росте проекта план миграции стратегии фиксируют в runbook: "при 100k заказов — включить WAL archive по 8.11/10".
10. Troubleshooting pg_dump
| Симптом | Причина | Решение |
|---|---|---|
pg_dump: error: connection refused | Контейнер down | docker start pg-dr-lab |
| Dump 0 байт | Редирект до готовности БД | pg_isready перед dump |
disk full | Local backup без rotation | Retention + lifecycle |
| Долгий dump | Большая таблица без vacuum | VACUUM ANALYZE, -Fd parallel |
12. Docker Compose для lab (альтернатива docker run)
Для повторяемого стенда сохраните docker-compose.dr-lab.yml:
services:
pg-dr-lab:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: lab
POSTGRES_DB: shop
ports:
- "5433:5432"
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Команды docker compose up -d и docker compose down -v (осторожно: -v удаляет volume — только в lab). Runbook фиксирует, используете ли raw docker run или compose.
13. Проверка после pg_dump
docker exec pg-dr-lab psql -U postgres -d shop -c "SELECT count(*) FROM orders;"
pg_restore --list ./backup-offsite/shop-*.dump | grep -c TABLE
Число строк orders на primary и наличие TABLE DATA в dump — быстрая sanity check перед offsite copy.
14. Wal-G и облако (обзор)
Wal-G пушит physical backup и WAL в S3/GCS — следующий уровень после practicum. Для pet-проекта с RPO в минутах изучите 8.11/10. Пока RPO в часах, pg_dump + offsite проще и дешевле в поддержке.
| Инструмент | Тип | Offsite | Типичный pet |
|---|---|---|---|
| pg_dump | logical | manual/S3 cp | да |
| Wal-G | physical+WAL | S3 native | при росте |
| pg_probackup | physical | catalog | enterprise РФ |
15. Сеть и firewall для restore
При restore на новом VPS откройте порт 5433 только для app и admin IP. Dump скачивают по HTTPS из bucket; Postgres наружу без TLS в lab допустим локально, в production — через VPN или SSL.
11. Чек перед переходом к катастрофе
Убедитесь, что в backup-offsite лежит свежий .dump, pg_restore --list на файле не падает с ошибкой (опционально), runbook сохранён. Тогда переходите к учебной катастрофе и restore.
16. Документирование в git
Коммитьте RUNBOOK-dr-shop.md, backup-shop.sh и docker-compose.dr-lab.yml в репозиторий infra. История git показывает, когда менялись команды restore — полезно при post-incident. Секреты production в git не кладут; используйте .env.example с placeholder.
17. RTO budget по шагам runbook
| Шаг runbook | Budget (lab) | Комментарий |
|---|---|---|
| T0 fix | 0 min | date |
| Destroy volume | 1 min | docker rm |
| Pull image | 0–5 min | cache |
| pg_restore | 1–10 min | size dump |
| App smoke | 2 min | curl health |
| Total | 5–20 min | цель 1 h |
Если сумма шагов > целевого RTO — автоматизируйте script из шага 3.
Полный walkthrough шага 2
# 1. Каталоги
mkdir -p ./backup-local ./backup-offsite
cd ~/dr-lab
# 2. Postgres
docker rm -f pg-dr-lab 2>/dev/null || true
docker volume rm pgdata 2>/dev/null || true
docker run -d --name pg-dr-lab -e POSTGRES_PASSWORD=lab -e POSTGRES_DB=shop \
-v pgdata:/var/lib/postgresql/data -p 5433:5432 postgres:16-alpine
sleep 5
docker exec pg-dr-lab pg_isready -U postgres
# 3. Данные
docker exec pg-dr-lab psql -U postgres -d shop -c \
"CREATE TABLE IF NOT EXISTS orders(id serial PRIMARY KEY, item text, created_at timestamptz default now());
INSERT INTO orders(item) VALUES ('widget'), ('gadget') ON CONFLICT DO NOTHING;"
docker exec pg-dr-lab psql -U postgres -d shop -c "SELECT * FROM orders;"
# 4. Dump
STAMP=$(date +%Y%m%d-%H%M)
docker exec pg-dr-lab pg_dump -U postgres -Fc shop > "./backup-local/shop-${STAMP}.dump"
cp "./backup-local/shop-${STAMP}.dump" "./backup-offsite/"
sha256sum "./backup-offsite/shop-${STAMP}.dump" > "./backup-offsite/shop-${STAMP}.dump.sha256"
# 5. Verify
ls -lh ./backup-offsite/
pg_restore --list "./backup-offsite/shop-${STAMP}.dump" | grep -c "TABLE DATA"
echo "OK ${STAMP}" >> ./backup-offsite/backup.log
Ожидаемый SELECT — 2 строки widget, gadget. pg_restore --list — count > 0.
Скрипт backup-shop.sh — полная версия
#!/bin/bash
set -euo pipefail
STAMP=$(date +%Y%m%d-%H%M)
LOCAL="./backup-local/shop-${STAMP}.dump"
OFFSITE="./backup-offsite/shop-${STAMP}.dump"
CONTAINER="${PG_CONTAINER:-pg-dr-lab}"
docker exec "$CONTAINER" pg_isready -U postgres
docker exec "$CONTAINER" pg_dump -U postgres -Fc shop > "$LOCAL"
test -s "$LOCAL" || { echo "FAIL: empty dump"; exit 1; }
sha256sum "$LOCAL" > "${LOCAL}.sha256"
cp "$LOCAL" "${LOCAL}.sha256" ./backup-offsite/
# aws s3 cp "$LOCAL" "s3://my-dr-bucket/shop/${STAMP}.dump" --storage-class GLACIER_IR
echo "OK ${STAMP}" >> ./backup-offsite/backup.log
pg_restore --list "$OFFSITE" >/dev/null
echo "Backup OK: $OFFSITE ($(du -h "$OFFSITE" | cut -f1))"
Запуск:
chmod +x backup-shop.sh
./backup-shop.sh
Ожидаемый вывод — Backup OK: ./backup-offsite/shop-....dump (NNK).
Cron setup
# crontab -e
# 0 */6 * * * cd /home/user/dr-lab && ./backup-shop.sh >> ./backup-offsite/cron.log 2>&1
Проверка после 6 часов:
tail -3 ./backup-offsite/backup.log
find ./backup-offsite -name '*.dump' -mtime -1
S3 offsite walkthrough
aws s3 cp ./backup-offsite/shop-latest.dump s3://my-dr-bucket/shop/ --storage-class GLACIER_IR
aws s3 ls s3://my-dr-bucket/shop/
# restore drill:
# aws s3 cp s3://my-dr-bucket/shop/shop-20250615-0300.dump ./backup-offsite/
Расширенное устранение неполадок
| Симптом | Ожидаемое | Решение |
|---|---|---|
| connection refused | pg_isready accepting | docker start pg-dr-lab |
| dump 0 bytes | file size > 0 | pg_isready перед dump |
| disk full | df -h OK | retention + delete old |
| corrupt archive | --list OK | новый dump |
| permission denied offsite | cp OK | chmod папки backup-offsite |
Чек-лист завершения шага 2
| # | Критерий | Команда | Ожидание |
|---|---|---|---|
| 1 | Container Up | docker ps | pg-dr-lab |
| 2 | Orders exist | SELECT | 2+ rows |
| 3 | Dump in offsite | ls backup-offsite | .dump файл |
| 4 | Checksum | sha256sum -c | OK |
| 5 | --list OK | pg_restore --list | без corrupted |
| 6 | Runbook v0.1 | файл существует | путь offsite |
Дальше — катастрофа и restore.
Предупреждения безопасности
- Пароль
labне в public git —.env.exampleс placeholder. - S3 bucket — block public access, encryption SSE.
- Dump содержит данные — IAM least privilege.
- backup.log не логируйте connection strings.
docker volume rmтолько в lab.