Задача клиента
К нам обратился владелец интернет-магазина электроники из Санкт-Петербурга — компания с оборотом более 200 млн рублей в год и каталогом из 15 000 товаров. Причина обращения была экстренной: за неделю до этого они потеряли базу данных заказов за последние 3 дня из-за сбоя SSD на сервере.
История оказалась классической: бэкапы «вроде бы были», но при попытке восстановления выяснилось, что cron-задача на rsync молча падала с ошибкой уже 4 месяца — никто не проверял логи. Единственная копия данных была на том же физическом сервере.
Клиент потерял:
- 3 дня заказов — пришлось восстанавливать вручную из email-уведомлений
- ~800 000 рублей прямых убытков и упущенной прибыли
- Доверие клиентов — часть заказов была потеряна безвозвратно
Задача, которую поставил клиент: выстроить такую систему бэкапов, чтобы подобная ситуация была невозможна в принципе.
Аудит текущей ситуации и проектирование стратегии
Наши инженеры начали с аудита существующей инфраструктуры. Потеря данных — это не вопрос «если», а вопрос «когда». По статистике, 60% компаний, потерявших данные, прекращают деятельность в течение 6 месяцев. К счастью, наш клиент обратился к нам вовремя.
Мы сформулировали три ключевых вопроса, на которые должна отвечать грамотная стратегия:
- Что копировать? — критичные данные, конфигурации, базы данных
- Куда копировать? — локально, удалённо, в облако
- Как часто? — RPO (Recovery Point Objective) определяет допустимую потерю данных
Стратегия 3-2-1: основа нашего решения
Мы предложили клиенту стратегию 3-2-1-1-0 — расширенный золотой стандарт защиты данных:
- 3 копии данных — оригинал + 2 резервные копии
- 2 разных типа носителей — например, SSD + HDD, или сервер + объектное хранилище
- 1 копия за пределами площадки — удалённый дата-центр, облако, или офф-сайт сервер
Расширенная часть:
- 1 копия offline (air-gapped) — не подключена к сети, защита от ransomware
- 0 ошибок — обязательная верификация каждого бэкапа
Именно эту стратегию мы реализовали для клиента, и именно пункт «0 ошибок» стал ключевым отличием от того, что было раньше.
Определение RPO и RTO для каждого сервиса
Вместе с клиентом мы определили допустимые параметры для каждого типа данных:
RPO (Recovery Point Objective) — максимально допустимый объём потерянных данных. RTO (Recovery Time Objective) — максимально допустимое время восстановления.
| Тип данных | Типичный RPO | Типичный RTO |
|---|
| Базы данных (production) | 5-15 минут | 1 час |
| Веб-серверы | 1-4 часа | 2 часа |
| Файловые серверы | 24 часа | 4 часа |
| Архивы и логи | 7 дней | 24 часа |
Для базы данных заказов клиента мы установили RPO = 15 минут и RTO = 1 час. Это означало: потеря максимум 15 минут данных и восстановление за час.
Первый уровень: rsync для файлов и конфигураций
Для файлов каталога товаров, изображений и конфигураций серверов мы настроили rsync — мощный инструмент инкрементальной синхронизации, который передаёт только изменившиеся части файлов.
Настройка rsync с правильными параметрами
Вот конфигурацию rsync, которую мы применили на серверах клиента:
# Локальная копия с сохранением прав
rsync -avz --progress /var/www/ /backup/www/
# Удалённый бэкап по SSH
rsync -avz --delete -e 'ssh -p 22 -i /root/.ssh/backup_key' \
/var/www/ backup@remote-server:/backup/www/
# Объяснение флагов:
# -a (archive) = -rlptgoD: рекурсия, ссылки, права, время, группа, владелец, устройства
# -v (verbose) — подробный вывод
# -z (compress) — сжатие при передаче
# --delete — удалять файлы на приёмнике, которых нет на источнике
# --progress — показывать прогресс
Также мы настроили исключения, чтобы не копировать ненужные файлы:
# Исключить файлы и директории
rsync -avz --exclude='*.log' --exclude='.cache' --exclude='node_modules' /data/ /backup/data/
# Использовать файл исключений
cat > /etc/backup-exclude.txt << 'EOF'
*.tmp
*.log
*.pid
.cache/
node_modules/
__pycache__/
EOF
rsync -avz --exclude-from=/etc/backup-exclude.txt /data/ /backup/data/
# Ограничение скорости (10 Мбит/с)
rsync -avz --bwlimit=10000 /data/ remote:/backup/data/
# Dry run — посмотреть что будет скопировано
rsync -avzn --delete /data/ /backup/data/
Ротация бэкапов с hardlinks
Мы написали скрипт «snapshot-style» бэкапов с rsync — каждый бэкап выглядит как полная копия, но занимает место только для изменённых файлов:
#!/bin/bash
# /usr/local/bin/rsync-snapshot.sh
SRC="/var/www"
DST="/backup/www"
DATE=$(date +%Y-%m-%d_%H-%M)
LATEST="$DST/latest"
NEW="$DST/$DATE"
# Создаём новый бэкап с hardlinks на предыдущий
rsync -avz --delete \
--link-dest="$LATEST" \
"$SRC/" "$NEW/"
# Обновляем симлинк на последний бэкап
rm -f "$LATEST"
ln -s "$NEW" "$LATEST"
# Удаляем бэкапы старше 30 дней
find "$DST" -maxdepth 1 -type d -name "20*" -mtime +30 -exec rm -rf {} \;
echo "Backup completed: $NEW"
Параметр --link-dest создаёт жёсткие ссылки на неизменённые файлы, экономя дисковое пространство на 90-95%. Для клиента с 50 ГБ изображений товаров это было критически важно.
Автоматизация через cron с уведомлениями
В отличие от старой настройки клиента, мы добавили обязательные уведомления о результатах каждого бэкапа:
# Открываем crontab
crontab -e
# Ежедневный бэкап в 3:00
0 3 * * * /usr/local/bin/rsync-snapshot.sh >> /var/log/backup.log 2>&1
# Ежечасный бэкап БД
0 * * * * /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1
# Еженедельный полный бэкап в воскресенье в 2:00
0 2 * * 0 /usr/local/bin/full-backup.sh >> /var/log/backup-full.log 2>&1
И самое важное — скрипт с уведомлением о результате, чтобы ситуация с «молча падающим бэкапом» не повторилась:
#!/bin/bash
# /usr/local/bin/backup-with-notify.sh
LOG="/var/log/backup-$(date +%Y%m%d).log"
rsync -avz --delete /var/www/ remote:/backup/www/ > "$LOG" 2>&1
RETCODE=$?
if [ $RETCODE -ne 0 ]; then
echo "BACKUP FAILED (exit code: $RETCODE)" | mail -s "[ALERT] Backup failed on $(hostname)" admin@itfresh.ru
exit 1
fi
# Размер бэкапа
SIZE=$(du -sh /backup/www/latest/ | cut -f1)
echo "Backup OK: $SIZE" | mail -s "[OK] Backup completed on $(hostname)" admin@itfresh.ru
Второй уровень: BorgBackup для дедупликации и шифрования
Для основной системы резервного копирования мы выбрали BorgBackup (Borg) — инструмент с дедупликацией на уровне блоков и встроенным шифрованием. Это было критически важно для e-commerce клиента с персональными данными покупателей.
Установка и инициализация зашифрованного репозитория
Вот как мы настроили Borg на серверах клиента:
# Установка из репозитория (Debian 12)
apt install borgbackup -y
# Или актуальная версия через pip
pip install borgbackup
# Инициализация локального репозитория с шифрованием
borg init --encryption=repokey /backup/borg-repo
# Инициализация удалённого репозитория
borg init --encryption=repokey-blake2 ssh://backup@remote:22/backup/borg-repo
# ВАЖНО: экспортируем ключ шифрования и храним отдельно!
borg key export /backup/borg-repo /root/borg-key-backup.txt
# Сохраняем passphrase в защищённый файл
echo 'YOUR_SECURE_PASSPHRASE' > /root/.borg-passphrase
chmod 600 /root/.borg-passphrase
export BORG_PASSCOMMAND='cat /root/.borg-passphrase'
Мы выбрали repokey-blake2 для удалённого репозитория — более быстрое хеширование BLAKE2 при том же уровне безопасности. Типы шифрования:
repokey — ключ хранится в репозитории + passphrase (рекомендуется)repokey-blake2 — то же, но с более быстрым хешированием BLAKE2keyfile — ключ хранится только локально (более безопасно, но ключ легко потерять)none — без шифрования (только для доверенных сетей)
Скрипт бэкапа с политикой хранения
Мы написали полный скрипт бэкапа, адаптированный под инфраструктуру интернет-магазина:
#!/bin/bash
# /usr/local/bin/borg-backup.sh
export BORG_REPO='/backup/borg-repo'
export BORG_PASSCOMMAND='cat /root/.borg-passphrase'
# Имя архива с датой и хостом
ARCHIVE="$(hostname)-$(date +%Y-%m-%d_%H-%M)"
# Создаём бэкап
borg create \
--verbose \
--filter AME \
--list \
--stats \
--show-rc \
--compression zstd,6 \
--exclude-caches \
--exclude '/var/cache/*' \
--exclude '/var/tmp/*' \
--exclude '/tmp/*' \
--exclude '*.pyc' \
--exclude '__pycache__' \
--exclude 'node_modules' \
"::${ARCHIVE}" \
/etc \
/var/www \
/var/lib/postgresql \
/home \
/opt
RESULT=$?
# Политика хранения: удаляем старые архивы
borg prune \
--list \
--show-rc \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--keep-yearly 2
# Компактификация (освобождение места)
borg compact
exit $RESULT
Политика хранения, которую мы согласовали с клиентом:
--keep-daily 7 — последние 7 ежедневных бэкапов--keep-weekly 4 — по одному бэкапу за последние 4 недели--keep-monthly 6 — по одному за последние 6 месяцев--keep-yearly 2 — по одному за последние 2 года
Процедура восстановления — протестировали вместе с клиентом
Мы не просто настроили бэкапы, но и провели обучение IT-специалиста клиента по восстановлению данных:
# Список всех архивов
borg list /backup/borg-repo
# Информация о конкретном архиве
borg info ::server1-2026-03-31_03-00
# Восстановление всего архива
cd /tmp/restore
borg extract ::server1-2026-03-31_03-00
# Восстановление конкретного каталога
borg extract ::server1-2026-03-31_03-00 var/www/site.ru
# Восстановление с фильтром по шаблону
borg extract ::server1-2026-03-31_03-00 --pattern '+ etc/nginx/**'
# Монтирование архива как файловой системы (FUSE)
borg mount ::server1-2026-03-31_03-00 /mnt/borg
ls /mnt/borg/etc/nginx/
borg umount /mnt/borg
# Сравнение архива с текущим состоянием
borg diff ::server1-2026-03-30_03-00 ::server1-2026-03-31_03-00
Третий уровень: Restic для облачного бэкапа
Для выполнения правила «1 копия за пределами площадки» мы настроили Restic — современный инструмент с нативной поддержкой S3-совместимых хранилищ, включая Yandex Object Storage.
Установка и настройка Restic
Вот как мы настроили Restic для бэкапа в облако:
# Установка
apt install restic -y
# Или последняя версия
wget https://github.com/restic/restic/releases/download/v0.17.0/restic_0.17.0_linux_amd64.bz2
bzip2 -d restic_0.17.0_linux_amd64.bz2
chmod +x restic_0.17.0_linux_amd64
mv restic_0.17.0_linux_amd64 /usr/local/bin/restic
# Инициализация локального репозитория
export RESTIC_PASSWORD='SuperSecretPass'
restic init --repo /backup/restic-repo
# Инициализация S3-совместимого репозитория (MinIO, Yandex Cloud)
export AWS_ACCESS_KEY_ID='AKID123'
export AWS_SECRET_ACCESS_KEY='SECRET456'
restic init --repo s3:https://storage.yandexcloud.net/my-backup-bucket
# Создание бэкапа
restic backup --repo /backup/restic-repo \
--exclude-file=/etc/restic-exclude.txt \
--tag webserver \
/etc /var/www /home
# Просмотр снапшотов
restic snapshots --repo /backup/restic-repo
Скрипт для бэкапа в Yandex Object Storage
Вот скрипт, который мы написали для клиента — ежедневный бэкап в Yandex Cloud:
#!/bin/bash
# /usr/local/bin/restic-s3-backup.sh
export RESTIC_REPOSITORY="s3:https://storage.yandexcloud.net/company-backups"
export RESTIC_PASSWORD_FILE="/root/.restic-password"
export AWS_ACCESS_KEY_ID="YCAJExxxxxxxxxx"
export AWS_SECRET_ACCESS_KEY="YCPxxxxxxxxxx"
# Дамп БД перед бэкапом
pg_dumpall -U postgres > /var/backups/postgresql/all_databases.sql
mysqldump --all-databases > /var/backups/mysql/all_databases.sql
# Бэкап
restic backup \
--tag "$(hostname)" \
--tag "daily" \
--exclude-caches \
--exclude='*.log' \
/etc /var/www /var/backups /home /opt
# Политика хранения
restic forget \
--keep-hourly 24 \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 \
--prune
# Проверка целостности (раз в неделю)
if [ "$(date +%u)" = "7" ]; then
restic check --read-data-subset=10%
fi
Почему мы использовали оба инструмента
Клиент спросил: зачем и BorgBackup, и Restic? Мы подготовили сравнение:
| Критерий | BorgBackup | Restic |
|---|
| Дедупликация | Лучше (content-defined chunking) | Хорошая |
| Скорость | Быстрый | Очень быстрый (Go, параллельность) |
| Cloud backends | Только SFTP | S3, Azure, GCS, B2, SFTP |
| Шифрование | AES-256-CTR + HMAC-SHA256 | AES-256 в режиме CTR + Poly1305 |
| Монтирование | FUSE mount | FUSE mount |
| Параллельное создание | Нет (блокировка) | Нет (блокировка) |
Наше решение: BorgBackup — для бэкапов на выделенный сервер (лучшая дедупликация), Restic — для бэкапов в Yandex Object Storage (нативная поддержка S3). Два уровня хранения = максимальная надёжность.
Бэкап баз данных: ключевая задача для интернет-магазина
Потеря базы данных заказов была главной болью клиента. Мы настроили специализированные бэкапы для PostgreSQL (основная БД магазина) и MySQL (CMS сайта).
PostgreSQL: ежечасные дампы и WAL-архивирование
Для базы данных заказов с RPO = 15 минут мы настроили непрерывное WAL-архивирование:
# Логический бэкап одной БД
pg_dump -U postgres -Fc -f /var/backups/postgresql/mydb_$(date +%Y%m%d).dump mydb
# Бэкап всех баз
pg_dumpall -U postgres > /var/backups/postgresql/all_$(date +%Y%m%d).sql
# Восстановление
pg_restore -U postgres -d mydb /var/backups/postgresql/mydb_20260331.dump
# Физический бэкап (точка восстановления + WAL)
# postgresql.conf:
# archive_mode = on
# archive_command = 'test ! -f /var/backups/wal/%f && cp %p /var/backups/wal/%f'
# wal_level = replica
# Создание base backup
pg_basebackup -U postgres -D /var/backups/postgresql/base -Ft -z -P
MySQL/MariaDB для CMS сайта
CMS интернет-магазина работала на MySQL. Вот конфигурация бэкапа, которую мы применили:
# Полный дамп с блокировкой
mysqldump --all-databases --single-transaction --routines --triggers \
--flush-logs --master-data=2 > /var/backups/mysql/full_$(date +%Y%m%d).sql
# Дамп конкретной БД
mysqldump --single-transaction --routines --triggers mydb > /var/backups/mysql/mydb.sql
# Сжатие на лету
mysqldump --all-databases --single-transaction | gzip > /var/backups/mysql/all_$(date +%Y%m%d).sql.gz
# Восстановление
mysql < /var/backups/mysql/full_20260331.sql
# Инкрементальный бэкап через бинлоги
mysqlbinlog /var/log/mysql/mysql-bin.000123 > /var/backups/mysql/binlog_incremental.sql
Комплексный скрипт бэкапа всех БД
Мы написали универсальный скрипт, который делает дамп всех баз и передаёт в BorgBackup:
#!/bin/bash
# /usr/local/bin/backup-databases.sh
BACKUP_DIR="/var/backups/databases"
DATE=$(date +%Y%m%d_%H%M)
mkdir -p "$BACKUP_DIR"/{postgresql,mysql}
# PostgreSQL
if command -v pg_dumpall &> /dev/null; then
echo "[$(date)] PostgreSQL backup starting..."
sudo -u postgres pg_dumpall | gzip > "$BACKUP_DIR/postgresql/all_${DATE}.sql.gz"
echo "[$(date)] PostgreSQL backup done: $(du -sh $BACKUP_DIR/postgresql/all_${DATE}.sql.gz | cut -f1)"
fi
# MySQL/MariaDB
if command -v mysqldump &> /dev/null; then
echo "[$(date)] MySQL backup starting..."
mysqldump --all-databases --single-transaction --routines | gzip > "$BACKUP_DIR/mysql/all_${DATE}.sql.gz"
echo "[$(date)] MySQL backup done: $(du -sh $BACKUP_DIR/mysql/all_${DATE}.sql.gz | cut -f1)"
fi
# Очистка старых дампов (старше 3 дней, т.к. Borg хранит историю)
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +3 -delete
echo "[$(date)] Database backup completed"
Безопасность: шифрование и защита от ransomware
Для e-commerce компании с персональными данными покупателей безопасность бэкапов критически важна. Мы настроили несколько уровней защиты.
SSH-ключи с ограничением доступа
Мы настроили безопасное автоматическое подключение к серверу бэкапов с максимальными ограничениями:
# Генерируем ключ специально для бэкапов
ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N '' -C 'backup@server1'
# Копируем на сервер бэкапов
ssh-copy-id -i /root/.ssh/backup_key.pub backup@backup-server.itfresh.ru
# Ограничиваем ключ на сервере бэкапов (authorized_keys)
# Добавляем перед ключом:
command="borg serve --restrict-to-path /backup/server1 --append-only",restrict ssh-ed25519 AAAA... backup@server1
Параметр --append-only стал ключевым элементом защиты: даже при компрометации production-сервера злоумышленник не сможет удалить существующие бэкапы.
Дополнительное GPG-шифрование
Для особо критичных данных (персональные данные покупателей) мы добавили ещё один уровень шифрования:
# Шифрование архива GPG перед отправкой
tar czf - /var/www /etc | gpg --symmetric --cipher-algo AES256 \
--passphrase-file /root/.backup-gpg-pass > /tmp/backup-$(date +%Y%m%d).tar.gz.gpg
# Отправка зашифрованного архива
rsync -avz /tmp/backup-*.gpg backup@remote:/offsite/
# Расшифровка
gpg --decrypt --passphrase-file /root/.backup-gpg-pass backup-20260331.tar.gz.gpg | tar xzf -
# Проверка хеша для верификации
sha256sum /tmp/backup-20260331.tar.gz.gpg > /tmp/backup-20260331.sha256
rsync /tmp/backup-20260331.sha256 backup@remote:/offsite/
Rclone для синхронизации с Yandex Cloud
Для синхронизации локальных бэкапов BorgBackup с облаком мы настроили Rclone:
# Установка
curl https://rclone.org/install.sh | bash
# Настройка S3-совместимого хранилища (Yandex Cloud)
rclone config
# > n (new remote)
# > Name: yandex-s3
# > Storage: s3
# > provider: Other
# > access_key_id: YCAJE...
# > secret_access_key: YCP...
# > endpoint: storage.yandexcloud.net
# > location_constraint: ru-central1
# Синхронизация бэкапов
rclone sync /backup/borg-repo yandex-s3:company-backups/borg/ \
--transfers 4 \
--checkers 8 \
--fast-list \
--log-file /var/log/rclone-backup.log \
--log-level INFO
# Проверка
rclone check /backup/borg-repo yandex-s3:company-backups/borg/
Автоматическая верификация — главный урок из инцидента
Бэкап, который не был протестирован, — это не бэкап. Именно отсутствие верификации привело к инциденту у клиента. Мы настроили автоматические проверки на нескольких уровнях.
Скрипт автоматической проверки целостности
Вот скрипт верификации, который мы написали и поставили на еженедельный cron:
#!/bin/bash
# /usr/local/bin/verify-backup.sh
export BORG_REPO='/backup/borg-repo'
export BORG_PASSCOMMAND='cat /root/.borg-passphrase'
LOG="/var/log/backup-verify.log"
ALERT_EMAIL="admin@itfresh.ru"
echo "=== Verification started: $(date) ===" >> "$LOG"
# Проверка целостности репозитория
borg check --verify-data "$BORG_REPO" >> "$LOG" 2>&1
RC=$?
if [ $RC -ne 0 ]; then
echo "BORG CHECK FAILED (rc=$RC)" >> "$LOG"
echo "Backup verification FAILED on $(hostname)" | \
mail -s "[CRITICAL] Backup verification failed" "$ALERT_EMAIL"
exit 1
fi
# Проверяем дату последнего архива
LAST_BACKUP=$(borg list --last 1 --format '{time}' "$BORG_REPO")
LAST_TS=$(date -d "$LAST_BACKUP" +%s)
NOW_TS=$(date +%s)
AGE_HOURS=$(( (NOW_TS - LAST_TS) / 3600 ))
if [ $AGE_HOURS -gt 26 ]; then
echo "Last backup is ${AGE_HOURS}h old - TOO OLD" >> "$LOG"
echo "Last backup on $(hostname) is ${AGE_HOURS} hours old!" | \
mail -s "[WARNING] Stale backup detected" "$ALERT_EMAIL"
exit 1
fi
echo "All checks passed. Last backup: ${AGE_HOURS}h ago" >> "$LOG"
Регулярное тестовое восстановление и мониторинг
Мы настроили ежемесячное автоматическое тестовое восстановление и интеграцию с мониторингом:
# Crontab: тестовое восстановление каждое 1-е число
0 5 1 * * /usr/local/bin/test-restore.sh >> /var/log/test-restore.log 2>&1
# Верификация каждое воскресенье
0 6 * * 0 /usr/local/bin/verify-backup.sh
Интеграция с Prometheus для мониторинга состояния бэкапов:
# Записываем метрику после бэкапа
# В конце скрипта бэкапа добавляем:
echo "backup_last_success_timestamp $(date +%s)" > /var/lib/node_exporter/textfile_collector/backup.prom
echo "backup_last_size_bytes $(borg info --last 1 --json | jq '.archives[0].stats.deduplicated_size')" >> /var/lib/node_exporter/textfile_collector/backup.prom
Теперь любой сбой бэкапа мгновенно вызывает оповещение в Telegram — повторение старой ситуации исключено.
Результаты внедрения
Проект был реализован за 5 рабочих дней. Вот что получил клиент:
- RPO = 15 минут для базы данных заказов — вместо «неизвестно, бэкап может не работать месяцами»
- RTO = 45 минут — полное восстановление из BorgBackup проверено на практике
- 3 уровня хранения бэкапов — локальный SSD, выделенный сервер бэкапов, Yandex Object Storage
- Автоматическая верификация каждую неделю — borg check с уведомлением при ошибке
- Ежемесячное тестовое восстановление — автоматизированное на отдельном стенде
- Шифрование всех бэкапов — AES-256, персональные данные покупателей защищены
- Append-only режим — защита от ransomware и случайного удаления
- Экономия на хранении — 85% благодаря дедупликации BorgBackup
Спустя 3 месяца после внедрения система отработала в реальной ситуации: при обновлении CMS был повреждён каталог товаров. IT-специалист клиента самостоятельно восстановил данные из бэкапа за 20 минут — без потери ни одного заказа. Если вы хотите защитить данные своего бизнеса от потерь — обратитесь в АйТи Фреш, и мы выстроим надёжную систему резервного копирования.