Self-hosted менеджер резервных копий: как я его создал

Сегодня разбираем материал dev.to о теме «Почему я создал self-hosted менеджер резервных копий». Материал полезен тем, кто хочет быстро понять суть темы и перевести идеи в прикладные действия.


Инструментов для резервного копирования предостаточно.

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

Вот архитектура, которая за этим стоит.

Проблема

Я занимался резервным копированием небольшой инфраструктуры, состоящей из двух серверов и нескольких Docker-контейнеров, которые нуждались в регулярном резервировании.

Мои требования были просты:

  • одно место для просмотра всех резервных копий
  • чёткая видимость того, что запускалось, когда и успешно ли завершилось
  • метрики вроде объёма переданных данных и размера снимков
  • поддержка единого входа (SSO) через протокол OIDC (OpenID Connect), поскольку мой стек уже работает за Zitadel

На заднем плане присутствовало ещё одно ограничение: соответствие нормативным требованиям (compliance).

Я начал оценивать, что потребуется для соответствия ISO 27001, и видимость резервных копий, отслеживаемость и централизованный контроль быстро стали обязательными требованиями.

Backrest оказался наиболее подходящим вариантом из найденных. Он хорошо построен, а Restic под ним — надёжен как скала.

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

Существуют и другие решения, но ни одно из них не соответствовало нужной мне комбинации.

Я не смог найти инструмент с открытым исходным кодом, который сочетал бы:

  • централизованное управление
  • современный интерфейс
  • полноценную поддержку OIDC
  • структуру, подходящую для compliance-ориентированной среды

Поэтому я создал Arkeep.

Почему бы просто не использовать скрипты?

Мог ли я склеить всё вместе с помощью cron, Restic и нескольких shell-скриптов? Да.

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

Мне нужна была одна система, а не куча скриптов.

Почему бы не подключаться по SSH к машинам?

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

Я намеренно избежал этого подхода. Поддержание агентами исходящего соединения:

  • проще в развёртывании
  • проще в обеспечении безопасности
  • надёжнее в реальных условиях эксплуатации

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

Кроме того, моя инфраструктура работает за Netbird, и всё находится за SSO. Это было жёстким требованием с первого дня.

Ограничения SSO и VPN оказались довольно болезненными при проектировании, поэтому я склонился к модели, которая работает с ними, а не борется против них.

Архитектура

Система построена на модели сервер/агент.

Сервер — это плоскость управления. Он запускает:

  • REST API
  • веб-интерфейс
  • планировщик
  • gRPC-сервер
  • базу данных

Он хранит конфигурацию, планирует задания и получает результаты.

Агенты запускаются на каждой машине, резервные копии которой вы хотите создавать.

Они не открывают никаких портов. Каждый агент устанавливает исходящее соединение с сервером через постоянный gRPC-поток и ожидает заданий.

Когда нужно выполнить резервное копирование, сервер отправляет задание через этот поток.

┌─────────────────────────────────┐
│ Arkeep Server │
│ REST API │ gRPC Server │
│ Scheduler │ WebSocket Hub │
│ SQLite/PG │ Notification Svc │
└─────────────────────────────────┘ ▲ ▲ │ gRPC │ gRPC │ (persistent stream)
┌───────┴────┐ ┌───┴────────┐
│ Agent A │ │ Agent B │
│ Server 1 │ │ Server 2 │
└────────────┘ └────────────┘

Эта модель решает несколько практических проблем:

  • не требуются входящие порты
  • чисто работает за NAT и VPN-оверлеями вроде Netbird
  • мгновенное обнаружение отключения при разрыве потока
  • централизованная видимость, что критично для аудита и compliance

На высоком уровне отправка задания выглядит примерно так:

func (s *Server) DispatchJob(agentID string, job *pb.JobRequest) error { stream, ok := s.agentStreams.Get(agentID) if !ok { return fmt.Errorf("agent %s offline", agentID) } return stream.Send(job)
}

Никакого поллинга. Никакого SSH. Только постоянный поток.

Что агент делает на самом деле

Когда приходит задание, агент:

  1. Разрешает источники (директории или Docker-тома)
  2. Запускает хуки перед резервным копированием
  3. Выполняет резервное копирование с помощью Restic
  4. Запускает хуки после резервного копирования
  5. Передаёт логи и метрики на сервер в реальном времени
  6. Сообщает о конечном результате

Вот примерно как агент оборачивает Restic:

cmd := exec.Command("restic", "backup", "--json", sourcePath)
stdout, err := cmd.StdoutPipe()
if err != nil { return err
}
if err := cmd.Start(); err != nil { return err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() { var event ResticEvent if err := json.Unmarshal(scanner.Bytes(), &event); err == nil { jobStream.Send(event) }
}
return cmd.Wait()

Restic остаётся движком резервного копирования. Агент добавляет структуру, наблюдаемость и контроль.

Обнаружение Docker-томов

Для Docker-рабочих нагрузок агент может автоматически разрешать тома во время выполнения.

func resolveVolumeMounts(cli *client.Client, containerID string) ([]string, error) { ctx := context.Background() container, err := cli.ContainerInspect(ctx, containerID) if err != nil { return nil, err } var paths []string for _, mount := range container.Mounts { if mount.Type == mount.TypeVolume { paths = append(paths, mount.Source) } } return paths, nil
}

С точки зрения Restic, это просто резервное копирование директорий.

Серверная часть

Сервер написан на Go. Стек выглядит так:

  • HTTP-слой: Chi
  • Взаимодействие с агентами: gRPC
  • Планировщик: gocron
  • Миграции: golang-migrate
  • База данных: SQLite по умолчанию, PostgreSQL для более крупных установок

Когда срабатывает задание, планировщик разрешает назначенного агента, проверяет, подключён ли он, и отправляет задание через активный gRPC-поток. Если агент офлайн, задание немедленно завершается с ошибкой — без ожидания таймаута.

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

Аутентификация

Аутентификация реализована с помощью JWT (RS256).

Пароли хешируются с использованием Argon2id.

OIDC реализован через Authorization Code + PKCE посредством coreos/go-oidc. Он напрямую подключается к провайдерам вроде Zitadel, Keycloak или Authentik.

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

Фронтенд

Фронтенд — это прогрессивное веб-приложение (PWA) на Vue 3, раздаваемое непосредственно из бинарного файла сервера в виде встроенных статических ресурсов.

Отдельный веб-сервер не нужен.

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

Текущее состояние и что дальше

Arkeep сейчас находится на версии v0.1.0-beta.1.

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

Запланированные улучшения:

  • улучшенные дашборды и отчётность
  • поддержка виртуальных машин
  • больше типов назначений

Arkeep — это открытый исходный код под лицензией Apache 2.0.

Репозиторий: https://github.com/arkeep-io/arkeep

Если вы управляете резервными копиями на нескольких машинах — как вы справляетесь с этим сегодня? Используете скрипты, существующие инструменты или что-то собственное?

Ответы на эти вопросы могут быть для вас полезными

Чем Arkeep отличается от Backrest? Backrest разворачивается на каждой машине отдельно и не имеет единой точки управления. Arkeep использует модель сервер/агент: один сервер управляет всеми агентами, собирает результаты и предоставляет единый интерфейс.

Нужно ли открывать входящие порты на машинах с агентами? Нет. Агенты устанавливают исходящее gRPC-соединение с сервером и ожидают заданий через постоянный поток. Входящие порты на агентах не требуются.

Какие провайдеры OIDC поддерживаются? Любые совместимые с протоколом OIDC: Zitadel, Keycloak, Authentik и другие. Подключение реализовано через coreos/go-oidc с использованием Authorization Code + PKCE.

Можно ли использовать Arkeep для резервного копирования Docker-томов? Да. Агент умеет автоматически разрешать тома контейнеров во время выполнения и передаёт их в Restic как обычные директории.

Какую базу данных использует сервер? По умолчанию SQLite — этого достаточно для большинства небольших установок. Для более крупных инфраструктур поддерживается PostgreSQL.

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x