Резервное копирование имеет неудобное свойство: оно месяцами выглядит успешным. Планировщик отчитывается о выполненном задании, график мониторинга ровно зелёный, и у команды закрепляется уверенность, что данные под защитой. Проверить эту уверенность можно одним способом - вернуть копию на отдельную машину и убедиться, что файлы читаются и приложения запускаются. Квартальный тест восстановления превращает надежду в проверенный факт с цифрами и протоколом. Ниже - процедура испытания, инструменты проверок, типичные дефекты и метрики.

Непроверенная резервная копия не гарантирует возврата данных

Смысл правила прост: ценность бэкапа определяется не фактом создания, а фактом успешного восстановления. Копия записана на диск, но gzip-архив усечён из-за переполнения раздела. Репозиторий зашифрован, но ключ остался на списанном сервере. Полный образ на месте, но цепочка приращений разорвана.

Механика расхождения банальна. Типовой скрипт в bash выглядит невинно:

set -euo pipefail
mysqldump --single-transaction shop | gzip > /backup/shop.sql.gz

Без pipefail конвейер возвращает код последней команды. Если mysqldump оборвал соединение на середине таблицы, gzip всё равно завершится без ошибок, файл ляжет на диск, cron зафиксирует успех, а внутри окажется половина базы. Страховка из set -o pipefail или проверка кода ${PIPESTATUS[0]} ловит такой случай, но встречается не везде. Файл существует, размер ненулевой, даты свежие, а распаковка обрывается на 60% объёма - и узнаёт об этом администратор, когда откладывать уже нельзя. Рабочая формула жёсткая: копия существует после первого успешного восстановления.

Автоматические проверки целостности архивов и их честные границы

Приятная часть: заметная доля проблем ловится автоматически, без развёртывания машин. Утилиты сжатия проверяют архив без полной распаковки:

gzip -t shop.sql.gz
zstd -t system.tar.zst
xz -t dumps.tar.xz
tar -tzf system.tar.gz | head -20

Ключ -t читает файл целиком и сверяет контрольные суммы блоков, а tar -tzf печатает оглавление, где обрыв списка виден сразу. Для файловых сумм порядок привычный:

sha256sum system.tar.gz > system.tar.gz.sha256
sha256sum -c system.tar.gz.sha256

Специализированные инструменты идут дальше: borg check --repository-only проверяет структуру репозитория, borg check --archives-only сверяет суммы сегментов, ключ --verify-data пересчитывает суммы всех блоков и ловит тихое искажение на носителе. Команда restic check сверяет индекс и структуру, а с ключом --read-data читает и проверяет все файлы данных целиком. На хранилище в десятки терабайт проверка займёт часы, поэтому её ставят в отдельное окно расписания.

Неприятная часть: перечисленные проверки подтверждают одно - байты на диске совпадают с записанными. Они не подтвердят, что ключ расшифровки лежит в сейфе, что дамп загрузится в сервер доступной версии, что распакованная система вообще запустится. Проверка целостности - первый уровень, полный тест доводит данные до логического уровня, до открытой базы и запущенного приложения. Только там обнаруживаются нечитаемые таблицы, потерянные внешние ключи и ссылки на несуществующие устройства.

Процедура квартального теста восстановления на отдельной машине

Рабочая схема сочетает лёгкую и тяжёлую части. Лёгкая - выборка случайных файлов из нескольких архивов, она занимает минуты и выполняется хоть каждую неделю. Тяжёлая - одно полное восстановление всей машины, его проводят раз в квартал. Полный цикл выглядит так:

  1. выбор случайных файлов из свежего и полугодового архива плюс одно полное восстановление всей машины;
  2. развёртывание чистой тестовой системы в изолированном сегменте сети и перенос копии на неё;
  3. проверка читаемости файлов, целостности баз данных и запуска приложений;
  4. замер длительности каждой операции и сравнение с целевым временем восстановления;
  5. запись результатов в письменный протокол с задачами по устранению найденных дефектов.

Список файлов для выборки генерируют случайно, а не по памяти:

find /var/www -type f | shuf -n 25 > /tmp/sample.txt
tar -xzpf web-full.tar.gz -C /mnt/restore --numeric-owner
sha256sum /mnt/restore/var/www/index.html

Ключ --numeric-owner сохраняет числовые идентификаторы владельцев, без него права исказятся до проверки. Контрольные суммы восстановленных файлов сверяют с суммами на рабочем сервере: расхождение суммы означает расхождение содержимого. Отдельный путь вынимает из архива borg extract /mnt/backup/repo::web-full var/www/index.html, у restic аналог - restic restore latest --target /mnt/restore --include /var/www.

Полное восстановление пофайловой копии начинается с чистого диска тестовой виртуальной машины:

qemu-img create -f qcow2 restore-test.qcow2 100G
mkfs.ext4 /dev/vdb1
mount /dev/vdb1 /mnt/restore
tar -xzpf system-full.tar.gz -C /mnt/restore --numeric-owner

Дальше проверка загрузочности. В системах семейства Debian последовательность привычная:

mount --bind /dev /mnt/restore/dev
mount --bind /proc /mnt/restore/proc
mount --bind /sys /mnt/restore/sys
chroot /mnt/restore grub-install /dev/vdb
chroot /mnt/restore update-grub

Классический подводный камень - идентификаторы разделов: восстановленный /etc/fstab ссылается на UUID диска прежнего сервера, и без правки fstab по свежему выводу blkid загрузка остановится в аварийном режиме. Если копия хранится как образ диска, путь короче: zcat server.img.gz | dd of=/dev/vdb bs=4M status=progress, разделы переезжают вместе с таблицей. Именно такие детали ловит только полный тест, проверка архива на месте их не увидит.

Базы данных проверяются до логического уровня, а не до факта загрузки дампа:

createdb restore_test
pg_restore -d restore_test --clean --if-exists shop.dump
psql -d restore_test -c "select count(*) from orders;"
mysql -e "create database restore_test;"
mysql restore_test < shop.sql
mysql restore_test -e "checksum table orders;"

Дамп загрузился без ошибок - половина дела. Счётчики строк, свежие записи и контрольные суммы таблиц показывают, что внутри не пустая схема и не состояние без последних транзакций. Конфигурацию веб-сервера проверяют синтаксисом nginx -t, картину после загрузки - списком сбойных единиц и портов:

systemctl --failed
journalctl -p err -b
ss -tulnp

Тестовая машина не должна видеть рабочую сеть: восстановленная копия знает старые пароли, обращается к тем же репликам и рассылает уведомления от имени рабочего сервера. Изолированный коммутатор без внешнего линка, адрес без шлюза, отключённый планировщик на время проверки - минимальный набор предосторожностей.

Типичные причины отказа бэкапов при первой попытке восстановления

Откуда берутся поздние сюрпризы? Из привычной веры, что зелёный отчёт означает рабочую копию. Сценарии повторяются годами.

Сценарий первый: задание помечено выполненным, содержимое битое. Обрыв соединения при съёме дампа, переполнение раздела, съём с отстающей реплики - все случаи оставляют файл на диске и код ноль в отчёте. Помогает связка из pipefail, контроля размера и тестовой распаковки внутри скрипта.

Сценарий второй: утерян ключ шифрования, архив цел, но копия превращается в набор случайных байтов. Третий: версии инструментов несовместимы, переход на новую мажорную версию меняет формат репозитория, а старый клиент не читает новое хранилище. Четвёртый: цепочка приращений разорвана, ротация удалила полный образ, и без первого звена вся цепочка бесполезна. Пятый: изменились пути и учётные записи, сервер переехал с /var/backups/host17 на /srv/backup/host42, SSH-ключ сменили при ротации доступов, а инструкция восстановления описывает прежнюю схему.

Общее у сценариев одно: каждый выявляется за минуты реального теста и годами не выявляется иначе. Команда узнаёт о дефекте, когда копия уже нужна, и это худшее время для знакомства с ограничениями собственной системы защиты.

Шифрование, ключи и версии инструментов в зоне постоянного риска

Ключи заслуживают отдельного разговора: их потеря необратима. У borg в режиме keyfile ключ лежит в домашнем каталоге пользователя на машине, где выполняется команда. Сторонняя копия репозитория без keyfile не расшифруется, даже если парольная фраза известна. В режиме repokey ключ хранится внутри репозитория и переезжает вместе с ним, но парольную фразу хранят так же аккуратно, как сам архив.

Экспорт снижает риск потери машины с ключом:

borg key export /mnt/backup/repo /srv/keys/repo-key.txt

Текстовый файл распечатывают и убирают в сейф. Главное - не сам факт экспорта, а проверка обратной дороги: раз в квартал ключ импортируют с бумажной копии на чистую машину и пробуют расшифровать свежий архив. У restic пароль репозитория хранится отдельно от данных; перед ротацией добавляют второй командой restic key add, а прежний отключают только после подтверждения, что новый работает.

С версиями дисциплина похожая: в протокол записывают точную версию инструмента восстановления. Дамп, снятый с PostgreSQL новой мажорной версии, нередко не загружается в старую из-за конструкций SQL, которых прежний сервер не понимает. Машина для восстановления держит под рукой пакеты нужных версий или загрузочный образ с утилитами, а обновление инструмента резервного копирования само по себе - повод для внепланового теста.

Замер времени восстановления как рабочая метрика надёжности копий

Время восстановления превращает тест из ритуала в инструмент управления. Замеряют по этапам: извлечение архива, перенос, распаковка, загрузка дампов, старт служб. Арифметика базовая:

start=$(date +%s)
tar -xzpf system-full.tar.gz -C /mnt/restore --numeric-owner
end=$(date +%s)
echo "seconds: $((end-start))"

Переведённые в скорость числа сравнивают с целевым временем возврата сервиса. Пример расчёта: архив 420 ГБ за 96 минут - около 73 МБ/с. Если цель - вернуть сервис за четыре часа, а извлечение, перенос и распаковка съедают три, решение о схеме хранения принимают до аварии, а не в её середине. Если копия лежит на съёмных носителях, время выдачи входит в замер наравне с распаковкой.

Один замер мало что доказывает, силу метрике даёт ряд: протоколы за несколько кварталов показывают тренд. Объём копии растёт, окно восстановления прежнее, значит, в какой-то момент цель перестанет выполняться, и ранняя фиксация тренда оставляет время перейти на инкрементальную схему или сменить носитель на более быстрый. Замерять нужно на оборудовании, сопоставимом с реальным возвратом: результат с быстрой машины с SSD ничего не скажет о медленном внешнем носителе.

Письменный протокол испытания и регламент для всей команды

Протокол - фиксирующая часть процедуры: без записи испытание превращается в воспоминание одного человека, а воспоминания не сравниваются между кварталами и не переживают смену работы. Рабочий шаблон лаконичен:

дата и цель испытания
исполнитель и проверяющий
идентификатор архива, размер, версия инструмента
конфигурация тестовой машины и выделенные ресурсы
время извлечения, переноса, распаковки, загрузки баз
найденные дефекты и задачи по их устранению
итог: восстановление признано успешным или требует повтора

Проверяющий - отдельная роль: когда один человек и выполняет восстановление, и оценивает результат, собственные ошибки замечаются хуже. Второй участник подтверждает итог. Очередь исполнителей полезна по той же причине: навык не должен зависеть от одного человека.

Квартальная частота - компромисс, а не догма. Полное восстановление требует инженерного времени и тестовой машины; ежемесячный прогон съест слишком много часов, а редкие проверки растягивают окно необнаруженного дефекта до многих месяцев. Три месяца закрывают большинство дрейфов: ротацию ключей, миграции, обновления, накопленные изменения в данных. Внеплановое испытание назначают после любого изменения цепочки: сменилось хранилище, обновился инструмент, переписан скрипт, переехал сервер. Прошлые протоколы просматривают перед каждым прогоном, чтобы дефекты из истории не возвращались.

В момент, когда бэкап понадобится всерьёз, у команды с отработанной процедурой есть не надежда, а план: известное время восстановления, проверенные ключи, свежий протокол и люди, которые уже делали это руками. Разница между "копии записаны" и "копии возвращены" не видна в отчётах, пока копия не нужна. Зато отлично видна в первый час реального возврата данных.