Стратегия бэкапов: как не потерять всё

backupdevopslinuxself-hosting

Бэкапы — это та штука, о которой все знают, но настраивают единицы. А те, кто настраивают, редко проверяют восстановление. Я терял данные один раз — хватило, чтобы выстроить систему, которая работает без моего участия и которой я доверяю. Расскажу, как она устроена.

Правило 3-2-1

Начнём с фундамента. Правило 3-2-1 — минимальный стандарт надёжности:

  • 3 копии данных (оригинал + 2 резервные)
  • 2 разных типа носителей (локальный диск + облако, например)
  • 1 копия offsite (физически в другом месте)

Правило 3-2-1: три копии, два носителя, одна удалённая

Звучит как паранойя? Подумайте вот о чём: диск может умереть (тип носителя), дата-центр может сгореть (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-хранилище

Локальный бэкап — это не бэкап, это просто вторая копия на том же сервере. Для 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 и убедитесь, что она работает.

Итого

Надёжная система бэкапов — это не один инструмент, а процесс:

  1. Правило 3-2-1 как фундамент
  2. restic для дедуплицированных зашифрованных бэкапов
  3. Логические дампы для баз данных (pg_dump, mysqldump)
  4. systemd timer для автоматизации
  5. Healthchecks для мониторинга
  6. Ежеквартальные restore-тесты для уверенности

Настройка занимает пару часов. Потеря данных без бэкапов стоит дней, недель, а иногда — всего проекта. Выбор очевиден.

© 2026 Terminal Notes. Built with SvelteKit.