Стратегия бэкапов: как не потерять всё
Бэкапы — это та штука, о которой все знают, но настраивают единицы. А те, кто настраивают, редко проверяют восстановление. Я терял данные один раз — хватило, чтобы выстроить систему, которая работает без моего участия и которой я доверяю. Расскажу, как она устроена.
Правило 3-2-1
Начнём с фундамента. Правило 3-2-1 — минимальный стандарт надёжности:
- 3 копии данных (оригинал + 2 резервные)
- 2 разных типа носителей (локальный диск + облако, например)
- 1 копия offsite (физически в другом месте)
Звучит как паранойя? Подумайте вот о чём: диск может умереть (тип носителя), дата-центр может сгореть (offsite), ransomware может зашифровать всё, до чего дотянется (количество копий). Каждый элемент правила закрывает свой класс рисков.
Типы бэкапов
Full (полный) — копия всех данных целиком. Надёжно, но медленно и требует много места. Для сервера с 50 ГБ данных каждый full backup — это 50 ГБ.
Incremental (инкрементальный) — только изменения с последнего бэкапа (любого типа). Быстро, мало места, но для восстановления нужна вся цепочка: full + все incrementals.
Differential (дифференциальный) — изменения с последнего полного бэкапа. Компромисс: больше, чем incremental, но для восстановления нужен только full + последний differential.
На практике современные инструменты (restic, borg) используют дедупликацию и делают выбор за вас: каждый снимок выглядит как полный, но хранит только уникальные блоки данных. Это лучше всех трёх подходов вместе взятых.
Выбор инструмента: restic vs borg vs duplicati
restic — мой выбор. Быстрый, простой, шифрует по умолчанию, работает с S3/SFTP/local. Написан на Go, один бинарник без зависимостей. Дедупликация на уровне блоков.
borgbackup — мощнее restic в плане компрессии (zstd, lz4), но сложнее в настройке. Требует Python и borg на обоих концах (сервер и клиент). Отличный инструмент, если вам важна максимальная экономия места.
duplicati — GUI, расписание, шифрование. Хорош для десктопов и NAS, но для серверной автоматизации слишком громоздок.
Для серверов без раздумий: restic. Установка — один пакет, конфигурация — переменные окружения, восстановление — одна команда.
Практическая настройка с restic
Инициализация репозитория
# Локальный репозиторий
restic init --repo /backup/restic-repo
# S3-совместимое хранилище (Backblaze B2, MinIO)
export AWS_ACCESS_KEY_ID="your-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
restic init --repo s3:s3.eu-central-003.backblazeb2.com/my-backups Скрипт бэкапа
Я использую простой bash-скрипт, который запускается по расписанию:
#!/bin/bash
set -euo pipefail
# Конфигурация
export RESTIC_REPOSITORY="s3:s3.eu-central-003.backblazeb2.com/server-backups"
export RESTIC_PASSWORD_FILE="/root/.restic-password"
export AWS_ACCESS_KEY_ID="your-key-id"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
BACKUP_PATHS="/etc /home /var/www /opt/apps"
EXCLUDE_FILE="/etc/restic/excludes.txt"
LOG_FILE="/var/log/restic-backup.log"
HEALTHCHECK_URL="https://hc-ping.com/your-uuid-here"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting backup..."
# Бэкап баз данных (перед основным бэкапом)
log "Dumping PostgreSQL databases..."
pg_dumpall -U postgres | gzip > /backup/db/postgres-$(date +%Y%m%d).sql.gz
log "Dumping MySQL databases..."
mysqldump --all-databases --single-transaction | gzip > /backup/db/mysql-$(date +%Y%m%d).sql.gz
# Основной бэкап
restic backup
--exclude-file="$EXCLUDE_FILE"
--tag "scheduled"
--host "$(hostname)"
$BACKUP_PATHS
/backup/db/
2>&1 | tee -a "$LOG_FILE"
# Очистка старых снимков
log "Pruning old snapshots..."
restic forget
--keep-daily 7
--keep-weekly 4
--keep-monthly 6
--keep-yearly 2
--prune
2>&1 | tee -a "$LOG_FILE"
# Проверка целостности (раз в неделю)
if [ "$(date +%u)" -eq 7 ]; then
log "Running integrity check..."
restic check --read-data-subset=10% 2>&1 | tee -a "$LOG_FILE"
fi
# Healthcheck ping
curl -fsS -m 10 --retry 5 "$HEALTHCHECK_URL" > /dev/null 2>&1
log "Backup completed successfully." Файл исключений /etc/restic/excludes.txt:
*.tmp
*.swp
*.cache
/var/cache
/var/tmp
/tmp
node_modules
__pycache__
.venv Политика хранения
Ключевой момент — restic forget с флагами --keep-*. Моя стандартная политика:
- 7 ежедневных — можно откатиться на любой день последней недели
- 4 еженедельных — покрытие последнего месяца
- 6 ежемесячных — полгода истории
- 2 ежегодных — на случай, если проблему обнаружат через год
Это баланс между стоимостью хранения и глубиной истории. Для маленьких серверов (до 20 ГБ данных) можно хранить больше.
Бэкап баз данных
Базы данных нельзя бэкапить просто копированием файлов — получите повреждённый дамп. Только логический дамп или snapshot на уровне файловой системы.
# PostgreSQL: отдельные базы
pg_dump -U postgres -Fc mydb > /backup/db/mydb-$(date +%Y%m%d).dump
# PostgreSQL: все базы
pg_dumpall -U postgres | gzip > /backup/db/pg-all-$(date +%Y%m%d).sql.gz
# MySQL/MariaDB
mysqldump --all-databases --single-transaction --quick | gzip > /backup/db/mysql-$(date +%Y%m%d).sql.gz
# MongoDB
mongodump --archive=/backup/db/mongo-$(date +%Y%m%d).archive --gzip Флаг --single-transaction в mysqldump критичен — без него дамп может быть неконсистентным.
Бэкап Docker volumes
Если приложения работают в Docker, данные живут в volumes. Два подхода:
# Подход 1: остановить контейнер, скопировать volume
docker compose stop app
docker run --rm
-v myapp_data:/data:ro
-v /backup/docker:/backup
alpine tar czf /backup/myapp-data-$(date +%Y%m%d).tar.gz -C /data .
docker compose start app
# Подход 2: без остановки (для некритичных данных)
docker run --rm
-v myapp_uploads:/data:ro
-v /backup/docker:/backup
alpine tar czf /backup/uploads-$(date +%Y%m%d).tar.gz -C /data . Для баз данных в Docker — всегда через docker exec и логический дамп:
docker exec postgres pg_dumpall -U postgres | gzip > /backup/db/pg-$(date +%Y%m%d).sql.gz Offsite-хранилище
Локальный бэкап — это не бэкап, это просто вторая копия на том же сервере. Для offsite я использую S3-совместимые хранилища:
Backblaze B2 — $5/TB/месяц, S3-совместимый API. Мой основной выбор для offsite. Дёшево и надёжно.
MinIO — self-hosted S3. Если у вас есть второй сервер в другом дата-центре, можно поднять MinIO и хранить бэкапы там. Бесплатно, но вы отвечаете за надёжность сами.
# Настройка restic с Backblaze B2
export B2_ACCOUNT_ID="your-account-id"
export B2_ACCOUNT_KEY="your-account-key"
restic -r b2:my-backup-bucket:/server-01 init Автоматизация через systemd
Cron работает, но systemd timer лучше: логирование через journal, обработка пропущенных запусков, зависимости между сервисами.
# /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic backup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
Nice=10
IOSchedulingClass=idle
Environment="HOME=/root" # /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run restic backup daily at 3 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
# Проверить статус
systemctl list-timers restic-backup.timer
journalctl -u restic-backup.service --since today Persistent=true — если сервер был выключен в 3:00, бэкап запустится при следующей загрузке. RandomizedDelaySec — разброс до 30 минут, чтобы несколько серверов не ломились в S3 одновременно.
Мониторинг здоровья бэкапов
Бэкап, который молча падает — хуже, чем отсутствие бэкапа. Создаёт ложное чувство безопасности. Минимальный мониторинг:
Healthchecks.io (или аналог) — сервис, который ожидает ping с определённой периодичностью. Не пришёл ping — alert. Бесплатный тариф покрывает 20 проверок.
Проверка свежести снимков:
#!/bin/bash
LAST_SNAPSHOT=$(restic snapshots --json --latest 1 | jq -r '.[0].time')
LAST_EPOCH=$(date -d "$LAST_SNAPSHOT" +%s)
NOW_EPOCH=$(date +%s)
AGE_HOURS=$(( (NOW_EPOCH - LAST_EPOCH) / 3600 ))
if [ "$AGE_HOURS" -gt 26 ]; then
echo "WARNING: Last backup is ${AGE_HOURS} hours old!" |
mail -s "Backup alert: $(hostname)" admin@example.com
fi Тестирование восстановления
Самое важное и самое игнорируемое. Бэкап, который нельзя восстановить — не бэкап. Я делаю restore-тест раз в квартал:
# Восстановление в отдельную директорию
restic restore latest --target /tmp/restore-test --include /var/www
# Проверка: сравнить с оригиналом
diff -rq /var/www /tmp/restore-test/var/www
# Восстановление конкретного файла
restic restore latest --target /tmp/restore-test --include /etc/nginx/nginx.conf
# Восстановление базы данных
gunzip < /tmp/restore-test/backup/db/postgres-20260210.sql.gz | psql -U postgres
# Очистка
rm -rf /tmp/restore-test Автоматизировать restore-тест полностью сложно (нужна тестовая среда), но хотя бы раз в квартал вручную — обязательно. Запишите процедуру восстановления в runbook и убедитесь, что она работает.
Итого
Надёжная система бэкапов — это не один инструмент, а процесс:
- Правило 3-2-1 как фундамент
- restic для дедуплицированных зашифрованных бэкапов
- Логические дампы для баз данных (pg_dump, mysqldump)
- systemd timer для автоматизации
- Healthchecks для мониторинга
- Ежеквартальные restore-тесты для уверенности
Настройка занимает пару часов. Потеря данных без бэкапов стоит дней, недель, а иногда — всего проекта. Выбор очевиден.