Сегодня разбираем материал 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. Только постоянный поток.
Что агент делает на самом деле
Когда приходит задание, агент:
- Разрешает источники (директории или Docker-тома)
- Запускает хуки перед резервным копированием
- Выполняет резервное копирование с помощью Restic
- Запускает хуки после резервного копирования
- Передаёт логи и метрики на сервер в реальном времени
- Сообщает о конечном результате
Вот примерно как агент оборачивает 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.


