Tenax
RU
GitHub

Оператор резервного копирования и восстановления для Kubernetes · Postgres 15–17 · PostGIS · MySQL 8.4

Сохраните каждую секунду.

Tenax делает резервные копии баз данных Postgres и MySQL, работающих в Kubernetes, в ваш собственный бакет S3 и передаёт в потоке каждое изменение в момент, когда оно происходит. Выберите секунду внутри окна и получите ровно эту базу данных в песочнице, к которой можно делать запросы. Данные шифруются ещё до выхода из кластера, а архитектура crash-only гарантирует, что ничего не зависнет.

Один бинарник, один чарт. Ваш бакет, ваши ключи.

Окно восстановления на момент времени · team/app · политика prod окно 7 дней · цепочка непрерывна

Восстановить на
2026-10-10 14:02:37 UTC
Базовая копия
20261010T020712Z-4c1e
Затем применить
114
$ tenax restore team/app --at 2026-10-10T14:02:37Z --wait

Посмотрите, как шкала отматывается назад, или проведите по окну сами. Оранжевые отметки — ночные полные резервные копии; бирюзовая полоса — поток изменений между ними. Иллюстрация политики prod: полная копия каждую ночь, окно 7 дней.

  • 9sна восстановление базы Postgres объёмом 644 MiB в песочницу: загрузка, расшифровка, восстановление и проверки
  • 4.2×меньше в бакете: физическая резервная копия 683 MiB хранится как 161 MiB
  • 72MiB/sпропускная способность логической полной резервной копии на 4 vCPU, в потоке, без временных файлов
  • 20msp99 обзора консоли при 1 000 баз данных и 100 000 резервных копий

Измерено с помощью make test-perf и make test-ui на тестовом стенде k3d (4 vCPU, MinIO в кластере). Цифры приведены в docs/PERF.md репозитория.

01Как это работаетпять шагов

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

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

  1. 01

    Поток

    Полные резервные копии по расписанию и каждое изменение в момент, когда оно происходит: собственный приёмник WAL в Tenax для Postgres, mysqlbinlog для MySQL.

    WAL · binlog
  2. 02

    Шифрование

    Каждый байт сжимается zstd и шифруется age для ваших открытых ключей прямо в поде, за один потоковый проход без временных файлов.

    zstd → age
  3. 03

    Хранение

    Параллельные multipart-загрузки в любой S3-совместимый бакет, SHA-256 для каждого объекта и манифест, записанный последним.

    *.zst.age · manifest.json
  4. 04

    Восстановление до секунды

    Укажите время или ID резервной копии. Tenax выберет базовую копию и применит изменения ровно до этой секунды.

    --at 14:02:37
  5. 05

    Песочница

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

    --ttl 6h

02Восстановлениечетыре назначения

Восстанав­ливайте куда угодно, не трогая продакшен.

Восстановление — такой же ресурс Kubernetes, как любой другой. Укажите время или ID резервной копии и выберите, куда восстанавливать. Tenax подберёт базовую копию и изменения для применения, а затем выполнит ваши проверки, прежде чем признать восстановление успешным.

SANDBOX

Одноразовая копия

Приватная база данных с TTL, учётными данными и консолью запросов только для чтения. Удаляется сама.

--to sandbox --ttl 6h
NEW

Новая база данных

Восстанавливается и проверяется в промежуточной песочнице, затем копируется под новым именем. Существующие объекты никогда не перезаписываются.

--to new --database app_copy
IN PLACE

Настоящий откат

Сначала создаёт страховочную резервную копию и просит ввести имя цели. Требуется роль restorer-admin.

--to in-place --confirm app
DOWNLOAD

Файл дампа

Расшифрованный сжатый логический дамп по ссылке, которая истекает через час. Записывается в аудит.

--to download

03Смотритеодна минута

Одна минута — от старта до восстановления.

Настоящие команды и настоящая консоль на демо-каталоге: список всех баз данных, что изменилось за ночь, восстановление в песочницу.

0:58 · со звуком · 1,9 МБ

Консоль

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

Знайте, что изменилось между двумя версиями.

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

  • Сравнение любых двух резервных копий. Изменения схемы, число строк и размер по таблицам.
  • Сводки изменений по каждому сегменту WAL или binlog. Вставки, обновления, удаления и DDL по таблицам, так что TRUNCATE в 22:48 легко найти.
  • Маскированный предпросмотр. Примеры строк с замаскированными столбцами из вашего списка, доступные по ключу каталога, который не может расшифровать сами данные.

CLI

CLI
$ tenax targets
TARGET              ENGINE    ORG/PROJECT/ENV      LAST FULL             PITR WINDOW                                  STORED    HEALTH
analytics/events    postgres  acme/data/staging    2026-10-07 02:11:47Z  -                                            19.1GiB   ok
auth/users          postgres  acme/platform/prod   2026-10-07 02:11:06Z  2026-09-23 02:11:06Z → 2026-10-07 18:50:52Z  1.5GiB    ok
billing/invoices    mysql     acme/billing/prod    2026-10-07 02:09:03Z  2026-09-23 02:09:03Z → 2026-10-07 18:50:52Z  6.7GiB    ok
billing/ledger      postgres  acme/billing/prod    2026-10-07 02:08:22Z  2026-09-23 02:08:22Z → 2026-10-07 18:50:52Z  14.3GiB   ok
maps/geo            postgres  acme/maps/prod       2026-10-07 02:10:25Z  2026-09-23 02:10:25Z → 2026-10-07 18:50:52Z  23.3GiB   ok
shop-dev/orders-db  postgres  acme/shop/dev        2026-10-07 02:12:28Z  -                                            17.4MiB   ok
shop/catalog-db     postgres  acme/shop/prod       2026-10-07 02:07:41Z  2026-09-23 02:07:41Z → 2026-10-07 18:50:52Z  731.0MiB  ok
shop/orders-db      postgres  acme/shop/prod       2026-10-07 02:07:00Z  2026-09-23 02:07:00Z → 2026-10-07 18:50:52Z  9.6GiB    ok
web/wordpress       mysql     acme/marketing/prod  2026-10-07 02:09:44Z  2026-09-23 02:09:44Z → 2026-10-07 18:50:52Z  607.4MiB  ok
CLI
$ tenax diff 20261006T020700Z-bd79 20261007T020700Z-6da5
from  20261006T020700Z-bd79 (2026-10-06 02:07:00Z)
to    20261007T020700Z-6da5 (2026-10-07 02:07:00Z)

20261007 applied (was 20261006)
column orders.gift_note added
table audit_log +158,175 rows (+2%)
table order_items +62,923 rows (+1%)
table orders +15,655 rows (+1%)
table coupons −3,120 rows (−100%)
table customers +531 rows (+1%)

14 segments in between:
  00000001000000A100000000  18:51:32–19:20:32 · 800 commits · audit_log +3348 / ~1116 / −83 · order_items +1327 / ~442 / −33 · orders +330 / ~110 / −8 · customers +11 / ~3 / −0
  00000001000000A100000001  19:21:32–19:50:32 · 813 commits · audit_log +3352 / ~1116 / −83 · order_items +1328 / ~442 / −33 · orders +330 / ~110 / −8 · customers +13 / ~3 / −0
  00000001000000A100000002  19:51:32–20:20:32 · 826 commits · audit_log +3356 / ~1116 / −83 · order_items +1329 / ~442 / −33 · orders +330 / ~110 / −8 · customers +15 / ~3 / −0
CLI
$ tenax restore shop/orders-db --at 20261006T020700Z-bd79 --ttl 6h --reason "coupons truncated"
restore orders-db-sandbox-60414a15fc created (sandbox from 20261006T020700Z-bd79, 0 segments)
restore  shop/orders-db-sandbox-60414a15fc

$ tenax sandbox list
NAME      ENGINE    SOURCE            OWNER               PHASE         EXPIRES               IN-CLUSTER
sbx-7f3a  postgres  shop/orders-db    maria@acme.example  Ready         2026-10-08 00:37:32Z  tenax-sbx-sbx-7f3a.tenax-sandboxes.svc:5432
sbx-c210  mysql     billing/invoices  dev@acme.example    Provisioning  -

Вывод демо-сервера из репозитория: go run ./hack/demo, затем направьте на него CLI или браузер.

Консоль, CLI и API

Единый JSON API, описанный в OpenAPI, с ролями из Cloudflare Access. Каждая запись и каждый запрос попадают в аудит.

Создан для GitOps

Хранилища, политики, цели и восстановления — это CRD. Уровни хранения, оповещения и дашборд Grafana входят в чарт.

Проверено, а не на авось

Плановые проверочные восстановления выполняют ваши SQL-проверки на настоящем восстановлении и помечают резервную копию как проверенную. Время восстановления экспортируется как метрика.

04Надёжностьcrash-only

Никогда не зависает. Даже kill -9 нипочём.

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

Никогда не зависает

Никаких монтирований и блочных устройств; к базам данных — только по сети. У каждого потока есть сторож прогресса, а каждый инструмент работает в своей группе процессов.

Crash-only

Убейте любой под в любой момент. Резервная копия существует только после того, как последним записан её манифест, и хаос-тесты убивают Tenax посреди копирования, чтобы это доказать.

Данные перемещаются в Job, никогда — в контроллере.

Контроллер только планирует. Movers и streamers передают байты через единый потоковый конвейер прямо в ваш бакет, а restorer возвращает их обратно.

Бережно к продакшену

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

Поток изменений

Собственный приёмник WAL в Tenax на слоте репликации для Postgres и mysqlbinlog для MySQL. Окно доходит до настоящего момента даже на малоактивной базе.

Быстрый по устройству

Потоковые конвейеры без временных файлов, параллельные multipart-загрузки, многопоточный zstd и каталог в памяти за консолью, которая отвечает за миллисекунды.

Настолько лёгкий, что о нём легко забыть.

Tenax — это один бинарник на Go. Контроллер и API только планируют и читают; данные перемещаются в короткоживущих Job, которые завершаются после работы, так что между резервными копиями в кластере не остаётся ничего тяжёлого.

54MBодин статический бинарник: контроллер, API и консоль, movers, streamers, restorer и CLI
18MBсжатая загрузка, без среды выполнения и sidecar-контейнеров
21MiBпамяти контроллера в покое при наблюдении за кластером баз данных
19MiBпамяти API и консоли в покое, с каталогом в памяти

Working set по данным kubelet в тестовом кластере k3d (12 баз данных) после сквозных и хаос-тестов; CPU в покое — несколько милликоров. Для каждой базы с восстановлением на момент времени дополнительно работает streamer, около 70 MiB во время передачи.

05Безопасностьшифрование у источника

Ваш бакет видит только шифротекст.

Шифрование происходит в поде, до загрузки, для ключей, которых у Tenax нет. Доступ разделён по ролям, а каждая запись и каждый запрос оставляют запись в журнале аудита.

Что видит ваш бакет
$ head -c 200 data/part-00001.zst.age
age-encryption.org/v1
-> X25519 Qm9yZWQ/IE5vdGhpbmcgdG8gc2VlIGhlcmUu…
--- 7Hc1kS0zR2QeYc…
ÌÞ#ø§ñ¹t¤¢Ý}ϰë։ÿ]Wœ3Ð÷…

Каждый объект данных — файл age, зашифрованный для ваших получателей (X25519). Только манифест хранится в открытом JSON, и секретов в нём нет.

  • Шифрование до отправки

    Каждый объект шифруется на стороне клиента с помощью age для ваших ключей. Бакет никогда не видит открытый текст, а ключ каталога консоли не может читать данные.

  • Ключ каталога, который не читает данные

    Консоль читает описи и маскированные предпросмотры с отдельной идентичностью каталога. Она не может расшифровать дампы, базовые копии, WAL и binlog.

  • Сквозная проверка

    Манифест хранит SHA-256 каждого сохранённого объекта, который сверяется в конце каждого чтения, а сам манифест записывается последним условным PUT.

  • Секреты вне поля зрения

    Учётные данные баз данных попадают в дочерние инструменты через запечатанные файлы в памяти, никогда — через argv или окружение. В журналах скрывается всё, что похоже на секрет.

  • Роли и журнал аудита

    Вход через Cloudflare Access и четыре роли, от viewer до restorer-admin. Для восстановления на месте нужна старшая роль и подтверждение вводом имени.

  • Защищённые поды

    Без root, корневая файловая система только для чтения, все capabilities сброшены, seccomp RuntimeDefault. Поды, работающие с данными, монтируют только emptyDir.

06Сравнениес заданием cron

Больше, чем ночной дамп в бакете.

Когда нужно…скрипт cron + pg_dumpTenax
Вернуться к 09:41:27Вчерашний дамп, до 24 ч потерьЛюбая секунда в окне
Доказать, что копия восстанавливаетсяУзнать во время инцидентаПлановые проверочные восстановления с вашими проверками
Найти версию с пропавшими строкамиВосстановить несколько и посмотретьСравнения и сводки изменений в консоли
Посмотреть без рискаВосстановить поверх общей staging-базыПриватная песочница с TTL
Чтобы бакет ничего не виделШифрование на стороне сервера, если настроеноШифрование age на стороне клиента вашими ключами
Пережить гибель узла посреди копированияНедописанный файл, который выглядит целымНичто не считается, пока последним не записан манифест
Знать, что всё ещё работаетТишинаОповещения о просроченных копиях, разорванных цепочках и задержке

07Установкаоколо десяти минут

С нуля до готовности к восстановлению.

  1. Установите чарт

    CRD, контроллер, API и консоль, RBAC, оповещения и дашборд.

    helm install tenax charts/tenax -n tenax-system --create-namespace \
      --set image.repository=registry.example.com/tenax/tenax --set image.tag=v0.1.0 \
      --set 'targetNamespaces={team-a}'
  2. Укажите бакет и ваши ключи

    Любое S3-совместимое хранилище. Сгенерируйте ключ age с помощью age-keygen и храните офлайн-копию.

    apiVersion: tenax.run/v1alpha1
    kind: BackupStore
    metadata: { name: main }
    spec:
      s3:
        endpoint: https://s3.example.com
        region: us-east-1
        bucket: db-backups
        credentialsSecret: { namespace: tenax-system, name: tenax-s3 }
      encryption:
        ageRecipients: [age1…]   # только открытый ключ; закрытый остаётся офлайн
  3. Объявите цель рядом с базой данных

    Создайте пользователя базы данных tenax с помощью SQL из docs/sql/, затем закоммитьте цель в git.

    apiVersion: tenax.run/v1alpha1
    kind: BackupTarget
    metadata: { name: app, namespace: team-a }
    spec:
      engine: postgres
      endpoint: { host: postgres, port: 5432 }
      databases: [app]
      policy: prod             # полные копии каждую ночь, окно восстановления 7 дней
      credentials:
        secretRef: { name: tenax-postgres }
  4. Восстановите что-нибудь — намеренно

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

    tenax targets
    tenax restore team-a/app --at 2026-10-07T09:41:27Z --wait
    tenax query <sandbox> -e "select count(*) from orders"

Полное руководство, все поля CRD и REST API — в документации.

08FAQпрямые ответы

О чём инженеры спрашивают первым делом.

Какие базы данных поддерживает Tenax?

Postgres 15, 16 и 17, включая PostGIS, с физическими или логическими полными резервными копиями и потоковой передачей WAL. MySQL 8.4 с логическими полными копиями и потоковой передачей binlog.

Насколько далеко назад можно восстановить и с какой точностью?

Любую полную резервную копию, которую сохраняют ваши уровни хранения, и любую секунду внутри окна восстановления, заданного политикой. Пример политики prod держит окно 7 дней и 14 ежедневных, 8 еженедельных, 12 ежемесячных и 5 ежегодных полных копий.

Может ли Tenax или мой поставщик хранилища прочитать мои данные?

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

Что будет, если под погибнет посреди резервного копирования?

Ничего, что имело бы значение. Резервная копия существует только после записи манифеста, когда каждый объект загружен и проверен по контрольной сумме. У незавершённого запуска манифеста нет, поэтому он никогда не предлагается для восстановления. Хаос-тесты убивают Tenax посреди копирования, чтобы это доказать.

Монтирует ли Tenax тома или использует снимки?

Нет. К базам данных он обращается только по сети, а его поды не монтируют ничего, кроме emptyDir: ни hostPath, ни FUSE, ни iSCSI, ни блочных устройств, и по умолчанию никаких PVC. Зависший драйвер хранилища не может подвесить резервное копирование.

Что Tenax нужно от моей базы данных?

Один пользователь tenax с минимальными привилегиями на базу данных, который никогда не пишет, создаётся с помощью SQL из docs/sql/. Без прав репликации Tenax всё равно делает логические резервные копии и подсказывает, что выдать для восстановления на любую секунду.

Tenax — это open source?

Исходный код доступен по лицензии The Republic Lex Aequa License 1.0. Любой может бесплатно использовать, изменять и распространять его, в том числе в продакшене коммерческих компаний, для защиты собственных данных. Продажа или предоставление в виде сервиса третьим лицам защищены правами.

Проверьте восстановление до того, как оно понадобится.