Оператор резервного копирования и восстановления для Kubernetes · Postgres 15–17 · PostGIS · MySQL 8.4
Сохраните каждую секунду.
Tenax делает резервные копии баз данных Postgres и MySQL, работающих в Kubernetes, в ваш собственный бакет S3 и передаёт в потоке каждое изменение в момент, когда оно происходит. Выберите секунду внутри окна и получите ровно эту базу данных в песочнице, к которой можно делать запросы. Данные шифруются ещё до выхода из кластера, а архитектура crash-only гарантирует, что ничего не зависнет.
Один бинарник, один чарт. Ваш бакет, ваши ключи.
- Восстановить на
- 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 читает изменения по сети, запечатывает их до выхода из пода и возвращает любую секунду, когда вы попросите.
- 01
Поток
Полные резервные копии по расписанию и каждое изменение в момент, когда оно происходит: собственный приёмник WAL в Tenax для Postgres, mysqlbinlog для MySQL.
WAL · binlog - 02
Шифрование
Каждый байт сжимается zstd и шифруется age для ваших открытых ключей прямо в поде, за один потоковый проход без временных файлов.
zstd → age - 03
Хранение
Параллельные multipart-загрузки в любой S3-совместимый бакет, SHA-256 для каждого объекта и манифест, записанный последним.
*.zst.age · manifest.json - 04
Восстановление до секунды
Укажите время или ID резервной копии. Tenax выберет базовую копию и применит изменения ровно до этой секунды.
--at 14:02:37 - 05
Песочница
Результат попадает в приватную базу данных с TTL и консолью запросов только для чтения. Ваши проверки выполняются до того, как восстановление будет признано успешным.
--ttl 6h
02Восстановлениечетыре назначения
Восстанавливайте куда угодно, не трогая продакшен.
Восстановление — такой же ресурс Kubernetes, как любой другой. Укажите время или ID резервной копии и выберите, куда восстанавливать. Tenax подберёт базовую копию и изменения для применения, а затем выполнит ваши проверки, прежде чем признать восстановление успешным.
Одноразовая копия
Приватная база данных с TTL, учётными данными и консолью запросов только для чтения. Удаляется сама.
--to sandbox --ttl 6hНовая база данных
Восстанавливается и проверяется в промежуточной песочнице, затем копируется под новым именем. Существующие объекты никогда не перезаписываются.
--to new --database app_copyНастоящий откат
Сначала создаёт страховочную резервную копию и просит ввести имя цели. Требуется роль restorer-admin.
--to in-place --confirm appФайл дампа
Расшифрованный сжатый логический дамп по ссылке, которая истекает через час. Записывается в аудит.
--to download03Смотритеодна минута
Одна минута — от старта до восстановления.
Настоящие команды и настоящая консоль на демо-каталоге: список всех баз данных, что изменилось за ночь, восстановление в песочницу.
Консоль
Знайте, что изменилось между двумя версиями.
Каждая резервная копия содержит опись своих таблиц, строк и размеров. Каждый сегмент изменений содержит сводку того, что он сделал. Поэтому ещё до восстановления видно, в какой версии ещё были потерянные строки.
- Сравнение любых двух резервных копий. Изменения схемы, число строк и размер по таблицам.
- Сводки изменений по каждому сегменту WAL или binlog. Вставки, обновления, удаления и DDL по таблицам, так что TRUNCATE в 22:48 легко найти.
- Маскированный предпросмотр. Примеры строк с замаскированными столбцами из вашего списка, доступные по ключу каталога, который не может расшифровать сами данные.
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
$ 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
$ 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 устроен так, что ни одна из этих проблем не может его заклинить или выдать себя за резервную копию.
- data/part-00001.zst.age
- data/part-00002.zst.age
- data/part-00003.zst.age
- manifest.json
- data/part-00001.zst.age
- data/part-00002.zst.age
- data/part-00003.zst.age
- manifest.json
Никогда не зависает
Никаких монтирований и блочных устройств; к базам данных — только по сети. У каждого потока есть сторож прогресса, а каждый инструмент работает в своей группе процессов.
Crash-only
Убейте любой под в любой момент. Резервная копия существует только после того, как последним записан её манифест, и хаос-тесты убивают Tenax посреди копирования, чтобы это доказать.
Данные перемещаются в Job, никогда — в контроллере.
Контроллер только планирует. Movers и streamers передают байты через единый потоковый конвейер прямо в ваш бакет, а restorer возвращает их обратно.
Бережно к продакшену
Один пользователь базы данных с минимальными привилегиями, который никогда не пишет. Слоты на хост, ограничение полосы и защита от нагрузки, откладывающая копирование, когда сервер занят.
Поток изменений
Собственный приёмник WAL в Tenax на слоте репликации для Postgres и mysqlbinlog для MySQL. Окно доходит до настоящего момента даже на малоактивной базе.
Быстрый по устройству
Потоковые конвейеры без временных файлов, параллельные multipart-загрузки, многопоточный zstd и каталог в памяти за консолью, которая отвечает за миллисекунды.
Настолько лёгкий, что о нём легко забыть.
Tenax — это один бинарник на Go. Контроллер и API только планируют и читают; данные перемещаются в короткоживущих Job, которые завершаются после работы, так что между резервными копиями в кластере не остаётся ничего тяжёлого.
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_dump | Tenax |
|---|---|---|
| Вернуться к 09:41:27 | Вчерашний дамп, до 24 ч потерь | Любая секунда в окне |
| Доказать, что копия восстанавливается | Узнать во время инцидента | Плановые проверочные восстановления с вашими проверками |
| Найти версию с пропавшими строками | Восстановить несколько и посмотреть | Сравнения и сводки изменений в консоли |
| Посмотреть без риска | Восстановить поверх общей staging-базы | Приватная песочница с TTL |
| Чтобы бакет ничего не видел | Шифрование на стороне сервера, если настроено | Шифрование age на стороне клиента вашими ключами |
| Пережить гибель узла посреди копирования | Недописанный файл, который выглядит целым | Ничто не считается, пока последним не записан манифест |
| Знать, что всё ещё работает | Тишина | Оповещения о просроченных копиях, разорванных цепочках и задержке |
07Установкаоколо десяти минут
С нуля до готовности к восстановлению.
Установите чарт
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}'Укажите бакет и ваши ключи
Любое 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…] # только открытый ключ; закрытый остаётся офлайн
Объявите цель рядом с базой данных
Создайте пользователя базы данных 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 }
Восстановите что-нибудь — намеренно
Первая полная резервная копия запустится сама. Затем попробуйте то, что, как вы надеетесь, никогда не понадобится.
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. Любой может бесплатно использовать, изменять и распространять его, в том числе в продакшене коммерческих компаний, для защиты собственных данных. Продажа или предоставление в виде сервиса третьим лицам защищены правами.