Проверка репозитория restic: чтение, восстановление и повреждения

Проверка репозитория restic: чтение, восстановление и повреждения

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

В этой статье мы используем Debian 12, restic и Python 3, чтобы выполнить штатные проверки и обнаружить намеренное повреждение во временном каталоге. Мы не читаем рабочие данные, не форматируем диски и не изменяем существующие репозитории резервных копий. Эксперимент подходит для файловых резервных копий на выделенных серверах. Для работающей базы данных сначала нужно создать согласованную копию средствами самой СУБД и только затем включать её в процесс файлового резервного копирования.

1. Определяем, что именно доказывает каждая проверка

restic объединяет содержимое файлов в зашифрованные пакеты данных и поддерживает снимки, индексы и связи между объектами. Обычная команда check проверяет прежде всего структуру репозитория; check --read-data дополнительно читает и проверяет все пакеты данных. Эти проверки не заменяют друг друга, а тестовое восстановление остаётся обязательным.

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

Зеркалирование сохраняет доступ к данным при некоторых отказах оборудования, резервные копии сохраняют предыдущие версии, а проверка целостности выявляет отклонения. Ни один из этих механизмов не заменяет реального восстановления.

Выделенные серверы OnuCloud на AMD штатно оснащаются двумя накопителями NVMe по 1 ТБ, каждый с прямым подключением, без аппаратного RAID. Размещение исходного каталога и репозитория резервных копий на разных дисках одного сервера позволяет изолировать часть отказов отдельного диска, но не создаёт удалённую резервную копию в независимом домене отказа. Локальный репозиторий ниже используется только для проверки последовательности действий.

2. Создаём воспроизводимый изолированный набор данных

Сначала установите инструменты. Затем выполняйте команды эксперимента в одном сеансе Bash, чтобы не потерять временные переменные. Для установки нужны права администратора; сам эксперимент можно проводить от имени обычного пользователя.

sudo apt-get update
sudo apt-get install restic python3

umask 077
LAB=$(mktemp -d /var/tmp/restic-lab.XXXXXXXX)
export LAB
export RESTIC_REPOSITORY="$LAB/repository"
export RESTIC_PASSWORD_FILE="$LAB/password"

mkdir -p "$LAB/source"
python3 - <<'PY'
import os
import secrets
from pathlib import Path

root = Path(os.environ["LAB"])
(root / "password").write_text(secrets.token_hex(32) + "\n")
(root / "source" / "record.txt").write_text(
    "backup verification\n", encoding="utf-8"
)
(root / "source" / "payload.bin").write_bytes(os.urandom(1024 * 1024))
PY

restic version
restic init
(
  cd "$LAB"
  restic backup source
)
restic snapshots

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

Здесь всего один снимок, поэтому далее можно использовать latest. В рабочей среде следует фиксировать конкретный ID снимка: если во время проверки завершится новое резервное копирование, цель восстановления может измениться. Временный пароль для эксперимента не означает, что организовано полноценное хранение ключей. Данные для расшифровки рабочего репозитория необходимо хранить отдельно и иметь возможность получить их при необходимости.

3. Отдельно проверяем полное чтение и содержимое после восстановления

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

restic check
restic check --read-data
restic restore latest --target "$LAB/restored"
diff -r -- "$LAB/source" "$LAB/restored/source"

Все четыре команды должны завершиться с нулевым кодом; при совпадении данных diff ничего не выводит. В текущем наборе есть только обычные файлы, поэтому сравнения содержимого и каталогов достаточно. Для реальных резервных копий нужно отдельно проверять владельцев, права доступа, связи между файлами и расширенные атрибуты, необходимые приложению.

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

Если уже check завершилась ошибкой, прекратите дальнейшую штатную приёмку и сохраните текущее состояние для расследования. Если проверка структуры прошла, а полное чтение — нет, сосредоточьтесь на чтении пакетов данных и целостности их содержимого. Нормальный список снимков не является основанием продолжать использование репозитория.

4. Повреждаем только копию и убеждаемся, что проверка её отклоняет

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

cp -a --reflink=never "$LAB/repository" "$LAB/damaged"

python3 - <<'PY'
import os
from pathlib import Path

data = Path(os.environ["LAB"]) / "damaged" / "data"
pack = next(p for p in sorted(data.rglob("*")) if p.is_file())
with pack.open("r+b") as f:
    f.seek(pack.stat().st_size // 2)
    pos = f.tell()
    original = f.read(1)
    if not original:
        raise RuntimeError("Empty data pack")
    f.seek(pos)
    f.write(bytes([original[0] ^ 1]))
print(pack.name)
PY

if restic -r "$LAB/damaged" check --read-data \
  >"$LAB/damaged-check.log" 2>&1; then
  printf '%s\n' 'UNEXPECTED_SUCCESS'
else
  printf '%s\n' 'EXPECTED_FAILURE'
fi
cat "$LAB/damaged-check.log"
restic check --read-data

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

Этот шаг доказывает возможность обнаружить повреждение, но не возможность его исправить. Изменение одного байта в пакете данных может затронуть один или несколько объектов. Не используйте этот результат для оценки доли данных, пострадавших при реальном инциденте, и не назначайте эту копию целью резервного копирования.

5. Переносим выводы эксперимента в правила проверки рабочей среды

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

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

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

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

Часто задаваемые вопросы

Читает ли restic check все сохранённые данные?

Нет. Обычная проверка в основном анализирует структуру и ссылки. Параметр --read-data включает чтение и проверку всех пакетов данных. Изолированное восстановление со сверкой содержимого выполняется отдельно.

Можно ли сразу очистить повреждённый репозиторий?

Сначала следует остановить изменяющие репозиторий задания, сохранить журналы и копию репозитория, проверить хранилище и канал передачи. Не запускайте forget, prune или ремонт без подготовки.

Выделенный физический сервер AMD EPYC 7543P

от $472 в месяц

Настроить физический сервер OnuCloud Проверить цены на серверы OnuCloud

Подбор конфигурации физического сервера под нагрузку

Выделенный сервер AMD EPYC 7543P. Резервирование хранилища и резервное копирование данных планирует администратор.