Резервная копия файлов и проверка восстановления
Мы научились читать состояние машины, сохранять ограниченные наблюдения и вызывать процедуру по расписанию. Завершим лабораторию упражнением на восстановление. Полезность резервной копии определяется тем, какие данные из неё действительно можно получить и как это подтверждено. Наличие папки с названием backup ещё не отвечает на эти вопросы. Поэтому начнём с точной границы копирования и заранее подготовленного места восстановления.
В полном снимке stages/16 учебного архива появился scripts/backup-release.sh и RUNBOOK.md. Источник releases/release-01 содержит три синтетических файла: README.md, release.json и скрытый .release-note. Это маленький учебный файловый выпуск. В нём нет рабочего API, пользовательских загрузок, секретов и PostgreSQL. Результаты копирования и сравнения ниже ожидаемые: при подготовке курса сценарии не выполнялись, а существующая инфраструктура не изменялась.
Договор копии до первой команды
Зададим три разных места. Исходник находится в releases/release-01. Новая копия будет записана в backups/ИМЯ. Восстановленный набор появится в restore-preview/ИМЯ. Ни один из этих путей не является /srv/deskqueue/current и не переключает действующую службу. Такое разделение позволяет рассмотреть восстановленные файлы и принять следующее решение после получения материала. Точка в начале .release-note меняет обычный показ имени, но не исключает файл из копирования каталога. Поэтому полный перечень проверяем с учётом скрытых имён, как в начале курса.
Имя копии состоит из ограниченного набора букв, цифр, точек, дефисов и подчёркиваний, начинается с буквы или цифры и не содержит разделителя пути. Это имя одного вложенного каталога, а не произвольный путь пользователя. Сценарий требует, чтобы конечное место ещё не существовало. Если копирование прервалось, повторный вызов с прежним именем будет отклонён: частичный набор нельзя незаметно смешать с новой попыткой.
Родительские каталоги backups и restore-preview уже включены в снимок. Операции планирования лишь читают источник и показывают предполагаемые изменения. Реальное создание конечной папки относится к отдельной операции копирования. Исходный выпуск предполагается неизменным на время упражнения. Для данных, которые продолжают меняться, такой предпосылки недостаточно: потребуется выбрать и описать способ получить согласованный снимок.
Полный сценарий с четырьмя операциями
plan показывает копирование выпуска в новую резервную папку, copy выполняет это копирование. restore-plan показывает копирование выбранной резервной папки в новое место восстановления, а restore создаёт восстановленный набор. В каждом случае выбор источника и назначения определяется известной операцией. Нельзя передать через параметр произвольную удалённую машину или сменить основной каталог приложения.
#!/usr/bin/env bash
set -u
set -o pipefail
umask 077
if (( $# != 3 )); then
printf 'Usage: bash backup-release.sh plan|copy|restore-plan|restore ABSOLUTE_LAB_ROOT NAME\n' >&2
exit 2
fi
operation=$1
if [[ $2 != /* || ! -d $2 ]]; then
printf 'LAB_ROOT must be an existing absolute directory\n' >&2
exit 2
fi
if root=$(cd -- "$2" && pwd -P); then :; else exit 1; fi
name=$3
if [[ ! $name =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ ]]; then
printf 'NAME must be one safe path component of at most 64 characters\n' >&2
exit 2
fi
if ! command -v rsync >/dev/null 2>&1; then
printf 'rsync is required\n' >&2
exit 2
fi
case "$operation" in
plan|copy)
source="$root/releases/release-01"
destination="$root/backups/$name"
;;
restore-plan|restore)
source="$root/backups/$name"
destination="$root/restore-preview/$name"
;;
*) printf 'Unknown operation\n' >&2; exit 2 ;;
esac
if [[ ! -d $source || -L $source || ! -d ${destination%/*} || -L ${destination%/*} ]]; then
printf 'Source and a real destination parent directory are required\n' >&2
exit 2
fi
if [[ -e $destination || -L $destination ]]; then
printf 'Destination already exists; choose a new name: %s\n' "$destination" >&2
exit 2
fi
case "$operation" in
plan|restore-plan)
rsync -ain -- "$source/" "$destination/"
;;
copy|restore)
if ! mkdir -- "$destination"; then exit 1; fi
if rsync -ai -- "$source/" "$destination/"; then
printf 'Files copied into: %s\n' "$destination"
else
result=$?
printf 'Copy is incomplete; inspect the new directory before trying again\n' >&2
exit "$result"
fi
;;
esac
Сценарий проверяет реальность каталога источника и родителя назначения, а также отклоняет символическую ссылку на этих двух границах. Это проверка известной собственной лаборатории, а не исчерпывающая защита от подмены файлов чужим пользователем. Каталог должен принадлежать читателю и быть недоступен для постороннего изменения. В нашем источнике обычные файлы; при работе с произвольным выпуском нужно отдельно рассмотреть вложенные ссылки и требуемые права.
umask ограничивает начальные права создаваемой папки, но архивное копирование способно восстановить режимы исходника. Следовательно, само наличие этой строки не гарантирует приватность всех конечных объектов. Учебный набор не содержит чувствительных данных. Для настоящих материалов заранее выбирают защищённое хранилище, права родительского каталога и допустимые правила сохранения метаданных, затем проверяют их после восстановления.
Сначала читаем план
Из корня ops-lab зададим абсолютный путь к снимку и новое имя. Имя здесь демонстрационное; если каталог с ним уже существует после собственного упражнения, выберите другое. План и копирование остаются двумя отдельными действиями, чтобы между ними можно было сопоставить источник, назначение и список файлов.
lab_root="$(pwd -P)/stages/16"
bash "$lab_root/scripts/backup-release.sh" plan "$lab_root" release-01-a
Флаг -n означает пробный проход без переноса файлов, -i выводит сведения о предполагаемых изменениях, а -a задаёт архивный набор правил. Завершающий слеш у источника означает копирование его содержимого. Таким образом, в новой папке ожидаются непосредственно все три файла: README.md, release.json и .release-note, без дополнительного слоя release-01. Значения этих параметров описаны в официальной справке rsync.
Сопоставьте строки плана с известными тремя файлами. Не стоит требовать одного точного побайтового вывода rsync: формат строки также отражает метаданные и состояние назначения. Важно понять, какой набор должен появиться и куда он направлен. Если план содержит неожиданный путь или существенно больше объектов, остановите собственное упражнение и выясните причину до переноса. После чтения плана отдельный вызов выполняет копирование:
bash "$lab_root/scripts/backup-release.sh" copy "$lab_root" release-01-a
Ожидается новый каталог backups/release-01-a со всеми тремя файлами. Ненулевой код означает, что процедура не подтверждена как завершённая. Частичная папка остаётся доступной для исследования, а сообщение предупреждает о неполной копии. Сценарий не удаляет её автоматически. Следующую попытку оформляют новым именем после понимания сбоя, чтобы следы первой операции не исчезли вместе с предполагаемой причиной.
Восстанавливаем в отдельное место
Теперь исходником становится выбранная резервная копия. Снова сначала рассмотрим пробный проход, а затем создадим новое назначение. Существующая папка восстановления не перезаписывается, и никаких действий переключения службы в сценарии нет. Возможность спокойно прочитать файлы до использования является частью самого упражнения.
bash "$lab_root/scripts/backup-release.sh" restore-plan "$lab_root" release-01-a
bash "$lab_root/scripts/backup-release.sh" restore "$lab_root" release-01-a
diff -ru -- "$lab_root/releases/release-01" "$lab_root/restore-preview/release-01-a"
При совпадении подготовленных текстовых файлов ожидается отсутствие вывода diff и код ноль. Код один означает найденные различия, а более высокий код сообщает о проблеме сравнения. Это сравнение содержимого маленького файлового набора; оно не проверяет владельцев, все метаданные, возможность запуска сервера или корректность базы. Запишите, что именно проверили, вместо неопределённого утверждения «всё восстановлено».
В сценарии отсутствует --delete. Для данного упражнения создаётся новое назначение, поэтому удаление лишних объектов не требуется. В более сложном процессе синхронизации такой параметр меняет договор: файлы назначения, отсутствующие в источнике, могут исчезнуть. Перед применением подобного поведения необходимо явно определить область синхронизации и проверить план. Наша процедура копирования отдельных выпусков не нуждается в этом дополнительном действии.
PostgreSQL имеет собственную границу восстановления
Содержимое файлового выпуска не включает записи заявок. PostgreSQL обслуживает собственные данные, поэтому копия server.js или всей папки исходников не восстанавливает таблицы. Для отдельной учебной базы можно рассматривать логический экспорт pg_dump, который получает согласованное состояние одной базы, и соответствующий способ восстановления. Архивный формат требует pg_restore, обычный SQL — другого порядка применения. Эти правила описаны в документации PostgreSQL 18.
Выбор такой процедуры требует знать версию сервера и клиента, необходимые объекты, права, место хранения и отдельную базу для проверки восстановления. Файлы пользовательских вложений также нужно согласовать с их записями в таблицах: независимо снятые копии могут представлять разные моменты времени. Здесь не подставляем универсальную команду с паролем и не обещаем защиту действующей базы. В runbook отдельно отмечено, что дамп PostgreSQL в этом упражнении отсутствует.
Протокол связывает результат со следующим действием
В RUNBOOK.md оставлены поля observed, output и next action: что заметили, где лежит подтверждение и какой шаг из него следует. После плана запишите выбранные пути; после копирования — код и название новой папки; после восстановления — результат сравнения и его пределы. Все поля при подготовке оставлены незаполненными, потому что текст урока не является протоколом выполненного восстановления.
Для настоящего процесса отдельно задают допустимую потерю последних данных, время возвращения сервиса и срок хранения копий. Локальная учебная папка находится на той же машине и может исчезнуть вместе с ней, поэтому упражнение ещё не является полной политикой хранения. Итог серии — понятная процедура наблюдения и проверки файлового восстановления, которую можно читать, объяснять и расширять по конкретным требованиям DeskQueue. Следующий рабочий шаг выбирают по сохранённому материалу и сформулированной границе данных.