RE:NODE

restic restore и check: как вернуть данные из S3

Автор Команда RE:NODE
16 мин чтения


Чтобы достать данные из restic, найдите снапшот командой restic snapshots, загляните внутрь через restic ls и восстановите в пустой каталог: restic restore <id> --target /tmp/restore --include <path>. Один файл или дамп базы данных быстрее достать через restic dump, а restic mount позволяет просматривать все снапшоты как обычные папки, когда вы ещё не знаете, что именно ищете. restic check --read-data-subset заранее докажет, что нужные вам данные по-прежнему читаются. Ничего из этого не работает без пароля от репозитория, а вернуть утерянный пароль не может никто: ни restic, ни хранилище.

Настройка репозитория, скрипт бэкапа и политика хранения описаны в статье restic backups to S3. Эта статья - вторая половина: день, когда данные понадобились, и учения, которые делают этот день скучным. Везде предполагается restic 0.17 или новее; если флаг появился позже или старые версии ведут себя иначе, об этом сказано отдельно.

Как снова попасть в репозиторий#

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

Нужны ровно три вещи: адрес репозитория (endpoint, bucket и prefix), пара ключей доступа с правом чтения bucket и пароль репозитория. С ними восстановиться можно на любой машине, где установлен restic.

/root/.restic-env
export AWS_ACCESS_KEY_ID=RNAKEXAMPLE123export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYexport AWS_DEFAULT_REGION=us-east-1export RESTIC_REPOSITORY=s3:https://s3.example.com/my-bucket/restic/web-01export RESTIC_PASSWORD_FILE=/root/.restic-password
bash
$ . /root/.restic-env$ restic cat config$ restic snapshots

restic cat config - самая маленькая из возможных проверок: она читает один объект и расшифровывает его. Если команда прошла, значит endpoint, ключи и пароль верны, и всё, что сломается дальше, связано с тем, что вы запрашиваете, а не с доступом. Первая команда на новой машине работает медленнее обычного, потому что restic строит локальный кэш метаданных в ~/.cache/restic; это нормально и происходит один раз.

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

Если вы восстанавливаетесь на другой машине, явно указывайте --host, когда имеете в виду старую. У новой машины другое имя хоста, а фильтры, которые по умолчанию берут "этот хост", ничего не найдут.

Как найти нужный снапшот#

Восстановить не тот снапшот случается чаще, чем наткнуться на повреждённый. Потратьте здесь лишнюю минуту.

bash
$ restic snapshots --host web-01 --tag nightly$ restic snapshots --latest 3 --compact$ restic snapshots --path /srv/minecraft

Фильтры комбинируются: --host, --tag и --path сужают список, а --latest n показывает только n самых новых снапшотов в каждой группе. Флаг --json даёт вывод, который можно передать скрипту.

Слово latest требует предупреждения. Без фильтров latest означает самый новый снапшот во всём репозитории, что бы в нём ни лежало. Если ваш ночной скрипт копирует /srv, а потом сохраняет mysqldump через --stdin-from-command, самым новым окажется дамп базы, и restic restore latest --target /tmp/restore вернёт вам один SQL-файл вместо сервера. Всегда привязывайте latest к чему-нибудь:

bash
$ restic restore latest --tag nightly --host web-01 --target /tmp/restore

Когда кандидат найден, посмотрите внутрь, прежде чем что-либо восстанавливать:

bash
$ restic ls a81d0b37 /srv/minecraft/world$ restic ls --long latest --tag nightly /etc/nginx$ restic find --host web-01 'server.properties'$ restic diff 4f2a1c9e a81d0b37

Каждая из трёх команд решает свою задачу:

  • restic ls выводит список файлов одного снапшота, при желании ограниченный каталогом. Флаг --long добавляет размер, права и время изменения, по которым видно, файл ли это из состояния до или после неудачной правки.
  • restic find ищет имя или шаблон по всем снапшотам и сообщает, в каких снапшотах он есть. Это самый быстрый ответ на вопрос "когда этот файл существовал в последний раз?", если его удалили три недели назад.
  • restic diff сравнивает два снапшота и перечисляет добавленные (+), удалённые (-) и изменённые (M) файлы. Если вы знаете, что что-то сломалось между вторником и средой, сравнение этих двух снапшотов обычно показывает, что именно.
КомандаОтвечает на вопрос
restic snapshotsКакие бэкапы есть, откуда и когда
restic ls <id> <dir>Что внутри одного снапшота
restic find <pattern>В каких снапшотах есть нужный файл
restic diff <a> <b>Что изменилось между двумя бэкапами

restic restore подробно#

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

bash
# Whole snapshot into an empty directory$ restic restore a81d0b37 --target /tmp/restore# One directory$ restic restore a81d0b37 --target /tmp/restore --include /srv/minecraft/world# Everything except the noise$ restic restore a81d0b37 --target /tmp/restore \    --exclude /srv/minecraft/logs --exclude '*.tmp'# Contents of one directory, without the leading path$ restic restore a81d0b37:/srv/minecraft --target /tmp/mc

Пути сохраняются как были. Восстановление /srv/minecraft/world в /tmp/restore даст /tmp/restore/srv/minecraft/world, а не /tmp/restore/world, и это сбивает с толку тех, кто ищет файлы в корне целевого каталога. Форма snapshot:subfolder в последнем примере убирает этот префикс: она восстанавливает содержимое /srv/minecraft прямо в /tmp/mc.

Параметры include и exclude повторяют те, что есть при создании бэкапа. --include и --exclude принимают шаблоны, --iinclude и --iexclude не учитывают регистр, а начиная с restic 0.17 --include-file и --exclude-file читают шаблоны из файла, по одному в строке. Без единого --include восстанавливается всё.

Параметры, добавленные в restic 0.17

В версии 0.17 restore стал заметно удобнее для восстановления в каталог, где уже есть данные:

ПараметрЧто делает
--dry-runПоказывает, что было бы восстановлено, ничего не записывая
--overwritealways (по умолчанию), if-changed, if-newer или never
--deleteУдаляет в целевом каталоге файлы, которых нет в снапшоте
--verifyЧитает восстановленные файлы обратно и проверяет их содержимое

Сочетание --overwrite if-changed и --delete превращает restore во что-то близкое к rsync из снапшота: неизменённые файлы остаются нетронутыми, изменённые перезаписываются, лишние удаляются, и в итоге целевой каталог - точная копия снапшота. Это самый быстрый способ откатить большой каталог, в котором изменилось немногое, и одновременно самая опасная команда в этой статье. Всегда сначала запускайте её с --dry-run -vv и внимательно читайте список файлов, которые она удалит.

--verify требует повторного чтения всего восстановленного, но уже с локального диска, а не по сети, и это дешёвая страховка для восстановления, которое вы собираетесь запустить в production.

Сколько это занимает

Скорость восстановления в основном определяется скоростью скачивания. restic забирает pack-файлы параллельно по нескольким соединениям (-o s3.connections=5 - значение по умолчанию для S3; на быстром канале и быстром endpoint его увеличение может помочь), расшифровывает и распаковывает их и записывает файлы. Репозиторий на удалённом endpoint при домашнем подключении упирается в это подключение, а не в restic. Измерьте скорость один раз на реальных данных, как описано в разделе об учениях ниже, а не гадайте.

restic dump: один файл, одна база, один архив#

Когда нужен один файл, полное восстановление излишне. dump выводит содержимое одного файла из снапшота в стандартный вывод:

bash
$ restic dump a81d0b37 /etc/nginx/nginx.conf > /tmp/nginx.conf.old$ diff /tmp/nginx.conf.old /etc/nginx/nginx.conf

Это естественный способ восстановить дамп базы, сохранённый через --stdin-from-command, потому что дамп вообще не нужно записывать на диск:

bash
$ restic snapshots --tag mysql --latest 1$ restic dump --tag mysql latest /app.sql | mysql app_restore$ restic dump --tag postgres latest /app.sql | psql -d app_restore

Путь - это --stdin-filename, который вы указали при создании бэкапа; он виден в столбце путей снапшота. Сначала восстановите в новую пустую базу (app_restore выше), сравните с ожидаемым и только потом направьте на неё приложение или поменяйте имена. Шаги восстановления для конкретных СУБД - пользователи, права, --no-owner для дампов Postgres в custom-формате - описаны в статьях database backups and restores и mysqldump backup and restore.

Если указать каталог вместо файла, dump выдаст tar-архив с ним, а с --archive zip - zip:

bash
$ restic dump latest --tag nightly /srv/minecraft/world > world.tar$ restic dump --archive zip latest --tag nightly /srv/minecraft/world > world.zip

Это удобно, когда данные должны оказаться там, где restic не установлен, например архив, который вы загружаете через файловый менеджер игровой панели или по SFTP, а сам сервер работает в контейнере без собственного restic.

restic mount: просмотр всех снапшотов#

restic mount представляет репозиторий как файловую систему только для чтения через FUSE:

bash
$ mkdir -p /mnt/restic$ restic mount /mnt/resticNow serving the repository at /mnt/resticUse another terminal or tool to browse the contents of this folder.When finished, quit with Ctrl-c here or umount the mountpoint.

Во втором терминале репозиторий выглядит так:

code
/mnt/restic/  ids/        one directory per snapshot ID  snapshots/  one directory per snapshot time, plus 'latest'  hosts/      snapshots grouped by host name  tags/       snapshots grouped by tag

Можно сделать cd в snapshots/2026-10-07T04:30:09+02:00/srv/minecraft, запустить ls, grep, less или diff -r относительно живого дерева и копировать отдельные файлы через cp. Это лучший инструмент, когда вы ещё не знаете, что вам нужно, например "конфиг до того, как его кто-то поправил, где-то на прошлой неделе": обычные утилиты работают сразу со всеми снапшотами.

Ограничения:

  • Нужен FUSE: Linux, macOS с macFUSE или FreeBSD. В Windows он не работает, а внутри контейнера обычно тоже, потому что там редко бывает /dev/fuse или права на его использование. Если монтирование не удаётся из-за отсутствия fusermount, установите в Debian и Ubuntu пакет fuse3 (или fuse).
  • Смонтированное видит только пользователь, который его запустил, если не добавить --allow-other и не разрешить это в /etc/fuse.conf.
  • Массовое копирование идёт медленно. Каждое чтение по требованию скачивает и расшифровывает pack-файлы; cp -r каталога на 20 GB из mount намного медленнее, чем restic restore того же каталога, который заранее планирует загрузки. Просматривайте через mount, восстанавливайте через restore.

Остановить его можно по Ctrl-C в терминале, где он запущен, или командой umount /mnt/restic (в некоторых системах fusermount -u).

Проверка репозитория до того, как он понадобится#

restic check помогает узнать о повреждении репозитория во вторник днём, а не во время аварии. У него два уровня.

bash
$ restic check$ restic check --read-data-subset=5%$ restic check --read-data-subset=3/12$ restic check --read-data

Обычный check проверяет структуру: что индекс согласован, что дерево каждого снапшота полно и что каждый упомянутый chunk существует в каком-нибудь pack-файле. Он читает только метаданные и завершается быстро. Сами pack-файлы он не открывает, поэтому не заметит pack, байты в котором испортились.

--read-data скачивает каждый pack и сверяет каждый chunk с его хешем. Это единственное полное доказательство, и на большом репозитории оно означает полную загрузку со всем, что к ней прилагается: временем и исходящим трафиком. --read-data-subset читает только часть:

  • --read-data-subset=5% при каждом запуске выбирает случайные 5% pack-файлов. За много запусков покрывается большая часть репозитория, но для конкретного pack гарантий нет.
  • --read-data-subset=n/t делит pack-файлы на t групп и читает группу n. Если запускать 1/12 в январе, 2/12 в феврале и так далее, весь репозиторий прочитывается раз в год, и каждый pack ровно один раз.

Ежемесячное задание cron с номером месяца в роли n - разумный вариант по умолчанию для репозиториев, которые слишком велики для полного чтения:

bash
$ restic check --read-data-subset="$(date +%-m)/12"

Чистый запуск заканчивается сообщением no errors were found. Всё остальное стоит остановки. При ошибках check завершается с ненулевым кодом, поэтому плановый запуск может оповестить вас тем же путём, что и неудавшийся бэкап - см. monitoring that tells you something.

Если check нашёл ошибки

Не запускайте prune и forget на репозитории, который не проходит check: так вы только усложните разбор повреждений. Действуйте по порядку:

  1. Запустите restic check ещё раз с чистым кэшем (--no-cache или переместите ~/.cache/restic в сторону). Повреждённый локальный кэш может давать ошибки, которых в репозитории нет.
  2. Если проблема в индексе, restic repair index (restic 0.16 и новее; в старых версиях это rebuild-index) перестроит его по реально имеющимся pack-файлам.
  3. Если pack-файлы пропали или повреждены, сделайте новый бэкап из источника, пока источник ещё существует. Chunk-и, которые всё ещё лежат на диске, загрузятся заново, и это исцелит все снапшоты, которые ими пользуются.
  4. Только после этого думайте о restic repair snapshots: он переписывает снапшоты, убирая ссылки на данные, которых действительно больше нет. Эти файлы пропадут из истории, а не вернутся.

Повреждение репозитория на исправном хранилище случается редко. Когда это происходит, причина обычно вне restic: хранилище тихо потеряло объекты, правило жизненного цикла на bucket удалило старые файлы или два инструмента писали в один prefix.

Реальность потерянного пароля#

Каждый объект в репозитории restic зашифрован мастер-ключом, а сам мастер-ключ хранится в каталоге keys/ в зашифрованном вашим паролем виде. Чёрного хода, ссылки для сброса и обращения в поддержку, которые бы помогли, не существует. Если утеряны все пароли от репозитория, данные не вернуть ни вам, ни хранилищу, ни разработчикам restic. Так задумано.

Вы можете сделать так, чтобы этого не случилось:

bash
$ restic key list$ restic key add          # asks for a new password$ restic key passwd       # changes the password of the current key$ restic key remove 3e1b0c9d

Три привычки помогают этого избежать:

  • Держите больше одного ключа. restic key add добавляет второй пароль, который открывает тот же репозиторий. Один храните на сервере в /root/.restic-password, другой - в менеджере паролей или в запечатанном конверте, который лежит отдельно от сервера. Каждый ключ открывает всё, они равноправны.
  • Проверяйте копию, а не только оригинал. Пароль в менеджере паролей с опечаткой не является резервной копией пароля. Один раз откройте репозиторий сохранённой копией: restic -r ... --password-file /tmp/pw-from-vault snapshots.
  • Знайте, чего смена пароля не делает. key passwd и key remove меняют круг тех, кто может открыть мастер-ключ, но не перешифровывают данные. Любой, у кого когда-то был рабочий пароль и копия файла ключа, по-прежнему может расшифровать репозиторий. Если пароль утёк, настоящее решение - новый репозиторий: выполните restic init для свежего и перенесите нужные снапшоты в него через restic copy.

Ключи доступа и пароль - разные секреты. Утёкший секретный ключ S3 позволяет удалить или скачать ваши зашифрованные pack-файлы, но не прочитать их; поменяйте его в панели провайдера. Утёкший пароль репозитория без ключей S3 ничего не даст, пока злоумышленник не доберётся и до bucket. Храните их порознь.

Учебное восстановление restic#

Бэкап, который вы ни разу не восстанавливали, - это гипотеза. Общая методика - что измерять, что обычно ломается, как писать инструкцию - описана в статье testing a restore before you need it. Для restic учения достаточно короткие, чтобы проводить их каждый квартал:

credentials onlysnapshots, restoreМенеджер паролейпароль, endpointЧистая машинаrestic установленS3 bucketрепозиторий restic/tmp/restoreвременный каталогПроверкаcanary, diff, запуск
Учебное восстановление с чистой машины
  1. Начните с машины, которая не является сервером. Одноразовая виртуальная машина, второй небольшой сервер, ваш ноутбук. Установите restic. Смысл в том, чтобы доказать: восстановиться можно, не имея ничего с исходной машины.
  2. Используйте только то, что хранится снаружи. Endpoint, bucket и prefix, ключ доступа, пароль - из менеджера паролей, а не скопированные с сервера. Если вам пришлось заглянуть на сервер за чем-либо, запишите это: ваше восстановление зависит от того, что вышло из строя.
  3. Выберите старый снапшот. Не latest: самый старый из тех, что ещё входят в срок хранения, проверит, что политика хранения сработала и старые данные целы.
  4. Восстанавливайте с включённым таймером. time restic restore <id> --target /tmp/restore --verify. Запишите размер и время. Это число, делённое на размер вашего самого большого сервера, и есть реальное время восстановления по этому каналу.
  5. Восстановите и базу данных. restic dump ... | mysql app_restore или аналог, затем посчитайте строки в важных таблицах и найдите самую свежую временную метку.
  6. Проверьте содержимое. Найдите canary - файл или строку в базе с датой бэкапа - и сравните несколько ключевых файлов с источником через diff. Если можно, запустите приложение на восстановленных данных.
  7. Заодно проверьте данные. restic check --read-data-subset=1/12 добавит к доказательствам ещё одну двенадцатую репозитория.
  8. Запишите, чего не хватало. Конфигурационный файл вне копируемых путей, флаг запуска, секрет, который жил только в переменной окружения. Исправляйте бэкап, а не только инструкцию.

Для игровых серверов те же учения подтверждают, что мир восстанавливается и загружается; правила сброса сохранений, благодаря которым мир вообще получается согласованным, описаны в статье game server backups to S3.

Ошибки, которые встретятся при восстановлении#

Сообщение (фрагмент)Причина
wrong password or no key foundНеверный пароль или пароль от другого репозитория
Is there a repository at the following location?Неверный bucket, prefix или endpoint
repository is already lockedБлокировку держит работающий или аварийно завершившийся бэкап или prune
no matching ID foundОпечатка в ID снапшота или фильтр, который его скрывает
SignatureDoesNotMatchНеверный секретный ключ, регион или расхождение часов

Несколько замечаний по каждому пункту:

  • Неверный пароль. Проверьте, что RESTIC_PASSWORD_FILE указывает на нужный файл и что в нём лежит пароль и больше ничего: ни пробела в начале, ни второй строки. Начиная с 0.17 restic при неверном пароле завершается с кодом 12, который могут проверять скрипты.
  • Нет репозитория. Prefix - часть адреса. s3:https://s3.example.com/my-bucket/restic/web-01 и .../my-bucket/restic/web01 - разные репозитории, и один из них не существует. Выведите содержимое bucket любым клиентом S3, чтобы увидеть, что там на самом деле. Если endpoint требует адресации path-style, добавьте -o s3.bucket-lookup=path, как объясняет статья S3 path-style vs virtual-hosted.
  • Заблокировано. restore, ls, dump и mount берут неисключительную блокировку и работают параллельно с бэкапами, но не с prune. Если других процессов нет, restic unlock безопасно снимет устаревшие блокировки. Если восстановление ждать не может, --no-lock пропускает блокировку в командах только для чтения, с риском читать в тот момент, когда prune удаляет данные у вас из-под рук.
  • Ошибки подписи. Секретный ключ неверен, или часы машины отстают настолько, что подпись запроса отклоняется. Прежде чем искать что-либо ещё, исправьте время через NTP.

FAQ#

Можно ли восстановить бэкап restic без restic?

Нет. Репозиторий зашифрован, сжат и разбит на chunk-и; ничто другое его прочитать не может. restic - один статический бинарный файл для Linux, macOS, Windows и BSD, так что держите ссылку на загрузку в инструкции. Если вам нужен обычный архив для того, что не может запустить restic, получите его через restic dump на машине, где restic есть.

Можно ли восстановить в Windows бэкап, сделанный в Linux?

Да, restic restore работает между операционными системами. Владельцы в стиле Unix и часть битов прав не переносятся на NTFS, а mount в Windows недоступен, но содержимое файлов возвращается целым.

Как восстановить только один удалённый файл?

Найдите последний снапшот, в котором он был, командой restic find --host web-01 'filename', затем выполните restic dump <id> /full/path/to/file > file. Это запишет только этот файл и не затронет остальное в каталоге.

Перезаписывает ли restore более новые файлы в целевом каталоге?

По умолчанию да: --overwrite always заменяет существующие файлы. Начиная с restic 0.17 можно выбрать if-newer, чтобы не трогать файлы новее снапшота, или never, чтобы менялись только отсутствующие файлы. Восстановление в пустой каталог снимает вопрос полностью.

Как часто запускать check с read-data?

Обычный check - раз в неделю, а ротируемый --read-data-subset=n/12 - раз в месяц: так каждый pack читается раз в год без полной загрузки каждый раз. Для небольшого репозитория полный --read-data раз в месяц достаточно дёшев, чтобы просто делать его.

Достаточно ли репозитория restic на одном сервере S3?

Как одной копии - да. Хранилище RE:NODE S3, например, держит одну копию на NVMe в одном месте, без репликации: это хорошая копия вне машины, но не единственная. restic copy во второй репозиторий в другом месте закрывает остальное, как разбирает статья S3 storage sizing and the 3-2-1 rule.