Пост

Организация файловых бэкапов с удаленным хранилищем с помощью restic

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

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

Учтем отмеченную выше особенность и рассмотрим пример организации файловых бэкапов с использованием программы с открытым исходным кодом restic.

📝 Вводные данные

🔹 Рабочие станции

  1. Две виртуальные машины на Linux, данные с которых и будем бэкапить.
  2. Облачный сервер с большим диском для хранения.

🔹 Инструменты

  1. Мультиплатформенная программа для создания резервных копий restic.
  2. Автоматизация для restic - resticker.
  3. REST API сервер для удаленных бэкапов на restic - rest server.

🔹 restic поддерживает различные протоколы взаимодействия с локальным / удаленным репозиторием, мы же воспользуемся подготовленным rest-server в режиме append only, что позволяет создавать новые резервные копии и запрещает удаление и изменение существующих. В качестве альтернативы облачному серверу с большим диском можно использовать объектное хранилище s3 с предварительно настроенной политикой доступа, а именно с запретом на удаление данных.

🔹 Также обращу внимание на наличие в restic целого ряда зарезервированных переменных окружения.

⚡️ Разворачиваем rest-server

🔹 .htpasswd файл для двух тестовых пользователей

1
2
user1:{SHA}44rSFJQ9qtHWTBAvrsKd5K/p2j0=
user2:{SHA}KqYKj/f81HPTIeAUav2eJt85UUc=

что соответствует user1:password1 и user2:password2.

🔹 docker-compose.yml для запуска сервера

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'
services:
  restserver:
    container_name: restic-rest-server
    image: restic/rest-server:latest
    restart: unless-stopped
    volumes:
      - ${PWD}/data:/data
      - ${PWD}/config:/config
    environment:
      PASSWORD_FILE: "/config/.htpasswd"
      OPTIONS: "--append-only --private-repos"
    ports:
      - 8000:8000
  • --append-only - включает режим только на создание резервных копий без возможности удаления или изменения ранее добавленных.
  • --private-repos - доступ пользователей только к их личному репозиторию (включая вложенные репозитории) на сервере по имени, т.е. http://host:8000/user1/ для user1 и http://host:8000/user2/ для user2 соответственно.

🔹 Стартуем

1
docker compose up -d

❗️❓ На момент публикации rest-server еще не поддерживает ротацию бэкапов, но задача в план включена, обсуждение деталей реализации ведется на форуме разработчиков. Временное решение для контроля за устаревшими резервными копиями - прямо на сервере настраивать отдельный restic клиент, который смотрит на репозитории и отрабатывает очистку по заданным правилам.

🔖 Вопрос генерации и подключения сертификатов для поддержки https опустим. Подробнее об этом можно почитать здесь и здесь.

📌 Устанавливаем и настраиваем restic на первой целевой машине

🔸 Установка

1
sudo apt install restic

🔸 Создание удаленного репозитория

1
2
3
4
5
6
7
8
9
10
restic -r rest:http://user1:password1@host:8000/user1/ init

# назначаем пароль для репозитория
enter password for new repository:
enter password again:
created restic repository c724478852 at rest:http://user1:***@host:8000/user1/

Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.

🔸 Бэкап каталога tmp

1
2
3
4
5
6
7
8
9
10
11
12
13
restic -r rest:http://user1:password1@host:8000/ backup /path/to/tmp

enter password for repository:
repository 4e341361 opened (repository version 2) successfully, password is correct
created new cache in /home/user/.cache/restic
no parent snapshot found, will read all files

Files:           1 new,     0 changed,     0 unmodified
Dirs:            1 new,     0 changed,     0 unmodified
Added to the repository: 770 B (691 B stored)

processed 1 files, 15 B in 0:00
snapshot 21cb550a saved

🔸 Проверка

1
2
3
4
5
6
7
8
9
restic -r rest:http://user1:password1@host:8000/ snapshots

enter password for repository:
repository 4e341361 opened (repository version 2) successfully, password is correct
ID        Time                 Host           Tags        Paths
----------------------------------------------------------------------------------------
21cb550a  2025-01-02 03:31:06  test-vm-1  /home/user/tmp
----------------------------------------------------------------------------------------
1 snapshots

❗️ При попытке создать репозиторий с корнем, отличным от имени пользователя, получим ошибку авторизации. При попытке удалить старые копии получим ошибку сервера. Рекомендуется поэкспериментировать с обращениями к серверу и убедиться в корректности созданных ограничений.

💡 Как автоматизировать данный процесс? Заполняем переменные окружения и прописываем задание в cron для запуска restic. Все тоже самое, только завернутое в docker контейнер, позволяет сделать упомянутый выше resticker. Им и воспользуемся при работе со второй машиной.

📌 Запускаем resticker на второй целевой машине

🔸 .env файл с переменными окружения

1
2
3
4
REST_REPOSITORY=rest:http://host:8000/user2/
REST_USER=user2
REST_PASSWORD=password2
RESTIC_PASSWORD=repopassword

🔸 docker-compose.yml для запуска resticker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3'
services:
  backup:
    container_name: resticker-backup
    image: mazzolino/restic
    restart: unless-stopped
    environment:
      RUN_ON_STARTUP: "true"
      BACKUP_CRON: "0 15 1 * * *"
      RESTIC_REPOSITORY: ${REST_REPOSITORY}
      RESTIC_PASSWORD: ${RESTIC_PASSWORD}
      RESTIC_BACKUP_SOURCES: /data
      RESTIC_BACKUP_ARGS: >-
        --tag user2-tmp-backup
        --verbose
      RESTIC_REST_USERNAME: ${REST_USER}
      RESTIC_REST_PASSWORD: ${REST_PASSWORD}
      TZ: Europe/Moscow
    volumes:
      - ${PWD}/tmp:/data:ro
  • RUN_ON_STARTUP - выполнить бэкап при старте контейнера.
  • RESTIC_BACKUP_SOURCES - примонтированный в /data контейнера тольно на чтение каталог tmp.
  • BACKUP_CRON: "0 15 1 * * *" - задание cron для бэкапа указанного источника ежедневно в 01:15 ночи. Информация о формате в описании go-cron.

🔖 resticker поддерживает полный перечень переменных окружения restic, а также имеет набор дополнительных опций.

🔸 Стартуем

1
docker compose up -d

После запуска можем наблюдать проверку на наличие репозитория, попытку инициализации в случае отсутствия, процесс самого резервного копирования и установку задания cron.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Checking configured repository 'rest:http://host:8000/user2/' ...
Fatal: repository does not exist: unable to open config file: <config/> does not exist
Is there a repository at the following location?
rest:http://host:8000/user2/
Could not access the configured repository.
Trying to initialize (in case it has not been initialized yet) ...
created restic repository 73d209bbe8 at rest:http://host:8000/user2/
Please note that knowledge of your password is required to access
the repository. Losing your password means that your data is
irrecoverably lost.
Repository successfully initialized.

Executing backup on startup ...
Starting Backup at 2025-01-02 16:29:03
open repository
no parent snapshot found, will read all files
load index files
start scan on [/data]
start backup on [/data]
scan finished in 0.002s: 1 files, 47 B

Files:           1 new,     0 changed,     0 unmodified
Dirs:            1 new,     0 changed,     0 unmodified
Data Blobs:      1 new
Tree Blobs:      2 new
Added to the repository: 741 B (672 B stored)

processed 1 files, 47 B in 0:00
snapshot c5613590 saved
Backup successful
Finished backup at 2025-01-02 16:29:03 after 0 seconds

Scheduling backup job according to cron expression.
new cron: 0 15 1 * * *

🔥 Таким образом, используя связку rest-server, restic и resticker можно организовать централизованный сбор и хранение файловых бэкапов с автоматической обработкой заданий по расписанию. Наличие приватных репозиториев и режима только на запись обеспечивают защиту от случайного или преднамеренного удаления данных.

Больше полезной информации в Telegram-канале