Tenax
FR
GitHub

Opérateur de sauvegarde et restauration pour Kubernetes · Postgres 15–17 · PostGIS · MySQL 8.4

Ne lâchez aucune seconde.

Tenax sauvegarde vers votre propre bucket S3 les bases Postgres et MySQL que vous exécutez dans Kubernetes, et diffuse chaque modification dès qu'elle a lieu. Choisissez une seconde dans la fenêtre et récupérez exactement cette base dans un bac à sable que vous pouvez interroger. Chiffré avant de quitter le cluster, conçu pour survivre à tout arrêt brutal : il ne se bloque jamais.

Un binaire, un chart. Votre bucket, vos clés.

Fenêtre de restauration à un instant · team/app · politique prod fenêtre 7 jours · chaîne intacte

Restaurer à
2026-10-10 14:02:37 UTC
Sauvegarde de base
20261010T020712Z-4c1e
Puis rejouer
114
$ tenax restore team/app --at 2026-10-10T14:02:37Z --wait

Regardez-le rembobiner, ou faites glisser vous-même le long de la fenêtre. Les repères orange sont les sauvegardes complètes nocturnes ; la bande cyan est le flux de modifications entre elles. Illustration d'une politique prod : complète chaque nuit, fenêtre de 7 jours.

  • 9spour restaurer une base Postgres de 644 Mio dans un bac à sable : téléchargement, déchiffrement, récupération et vérifications
  • 4.2×plus petit dans le bucket : une sauvegarde physique de 683 Mio stockée en 161 Mio
  • 72MiB/sde débit pour une sauvegarde logique complète sur 4 vCPU, en flux, sans fichier temporaire
  • 20msp99 de la vue d'ensemble de la console avec 1 000 bases et 100 000 sauvegardes

Mesuré avec make test-perf et make test-ui sur l'environnement de test k3d (4 vCPU, MinIO dans le cluster). Les chiffres sont dans docs/PERF.md du dépôt.

01Fonctionnementcinq étapes

D'une modification en direct à un bac à sable que vous pouvez interroger.

Aucun agent dans votre base, aucun instantané de volume. Tenax lit les modifications par le réseau, les scelle avant qu'elles ne quittent le pod et vous rend n'importe quelle seconde à la demande.

  1. 01

    Diffuser

    Des sauvegardes complètes planifiées, plus chaque modification dès qu'elle a lieu : le récepteur WAL de Tenax pour Postgres, mysqlbinlog pour MySQL.

    WAL · binlog
  2. 02

    Chiffrer

    Chaque octet est compressé avec zstd et chiffré avec age pour vos clés publiques, dans le pod, en une seule passe en flux, sans fichier temporaire.

    zstd → age
  3. 03

    Stocker

    Des envois multipart en parallèle vers tout bucket compatible S3, un SHA-256 par objet, et le manifeste écrit en dernier.

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

    Restaurer à la seconde

    Demandez une heure ou un identifiant de sauvegarde. Tenax choisit la sauvegarde de base et rejoue les modifications jusqu'à cette seconde exacte.

    --at 14:02:37
  5. 05

    Ouvrir un bac à sable

    Le résultat arrive dans une base privée avec TTL et une console de requêtes en lecture seule. Vos contrôles s'exécutent avant que la restauration ne compte comme réussie.

    --ttl 6h

02Restaurerquatre destinations

Restaurez n'importe où, sans toucher à la production.

Une restauration est une ressource Kubernetes comme une autre. Demandez un instant ou un identifiant de sauvegarde et choisissez la destination. Tenax planifie la sauvegarde de base et les modifications à rejouer, puis exécute vos vérifications avant de déclarer la restauration réussie.

SANDBOX

Une copie jetable

Une base privée avec TTL, identifiants et console de requêtes en lecture seule. Elle se nettoie toute seule.

--to sandbox --ttl 6h
NEW

Une nouvelle base

Restaurée et vérifiée dans un bac à sable intermédiaire, puis copiée sous un nouveau nom. Les objets existants ne sont jamais écrasés.

--to new --database app_copy
IN PLACE

Revenir en arrière pour de vrai

Fait d'abord une sauvegarde de sécurité et vous demande de taper le nom de la cible. Nécessite le rôle restorer-admin.

--to in-place --confirm app
DOWNLOAD

Un fichier de dump

Un dump logique déchiffré et compressé, derrière un lien qui expire au bout d'une heure. Audité.

--to download

03Voirune minute

Une minute, du début à la restauration.

De vraies commandes et la vraie console sur un catalogue de démonstration : listez chaque base, voyez ce qui a changé pendant la nuit, restaurez-la dans un bac à sable.

0:58 · avec son · 1,9 Mo

La console

Vue d'ensemble de la console Tenax : neuf bases avec leur dernière sauvegarde complète, fenêtre de restauration, retard, taille et santé
Toutes les bases d'un coup d'œil : dernière sauvegarde complète, fenêtre de restauration, retard du flux, taille et santé.
Page d'une base dans la console Tenax : la frise restaurable, les tendances de lignes et de taille, et la liste des versions avec badges de vérification
Une base : sa frise restaurable, ses tendances et chaque version, avec les restaurations vérifiées signalées.
Diff entre deux sauvegardes dans la console Tenax : une colonne ajoutée, une table vidée, lignes et taille par table
Deux versions côte à côte : ce qui a été appliqué, les colonnes modifiées, lignes et taille par table.

Sachez ce qui a changé entre deux versions.

Chaque sauvegarde porte un inventaire de ses tables, lignes et tailles. Chaque segment de modifications porte un résumé de ce qu'il a fait. Avant de restaurer quoi que ce soit, vous voyez quelle version contenait encore les lignes perdues.

  • Diffs entre deux sauvegardes quelconques. Changements de schéma, nombre de lignes et taille par table.
  • Résumés de modifications par segment WAL ou binlog. Insertions, mises à jour, suppressions et DDL par table : un TRUNCATE à 22:48 se retrouve facilement.
  • Aperçus masqués. Des lignes d'exemple avec les colonnes que vous indiquez masquées, lisibles avec une clé de catalogue qui ne peut pas déchiffrer les données.

La CLI

La 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
La 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
La 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  -

Sortie du serveur de démonstration du dépôt : go run ./hack/demo, puis pointez la CLI ou un navigateur dessus.

Console, CLI et API

Une API JSON décrite en OpenAPI, avec des rôles issus de Cloudflare Access. Chaque écriture et chaque requête sont auditées.

Natif GitOps

Stores, politiques, cibles et restaurations sont des CRD. Paliers de rétention, alertes et tableau de bord Grafana sont livrés avec le chart.

Vérifié, pas espéré

Des restaurations de vérification planifiées exécutent vos requêtes SQL sur une vraie restauration et marquent la sauvegarde vérifiée. Leur durée est exportée en métrique.

04Fiabilitécrash-only

Ne se bloque jamais. Encaisse un kill -9.

Les sauvegardes échouent la nuit de façons banales : un montage figé, un pod mort, un fichier à moitié écrit. Tenax est conçu pour qu'aucune ne puisse le bloquer ni se faire passer pour une sauvegarde.

Ne se bloque jamais

Pas de montage ni de périphérique bloc ; les bases uniquement par le réseau. Chaque flux a un chien de garde de progression, chaque outil tourne dans son propre groupe de processus.

Crash-only

Tuez n'importe quel pod à tout moment. Une sauvegarde n'existe qu'une fois son manifeste écrit en dernier, et la suite de chaos tue Tenax en pleine sauvegarde pour le prouver.

Les données passent par des Jobs, jamais par le contrôleur.

Le contrôleur ne fait que planifier. Movers et streamers transportent les octets dans un seul pipeline en flux jusqu'à votre bucket, et le restorer les ramène.

Doux avec la production

Un seul utilisateur de base au moindre privilège, qui n'écrit jamais. Créneaux par hôte, limites de bande passante et un garde de charge qui reporte la sauvegarde si le serveur est occupé.

Flux de modifications

Le récepteur WAL de Tenax sur un slot de réplication pour Postgres, et mysqlbinlog pour MySQL. La fenêtre atteint le présent, même sur une base inactive.

Rapide par construction

Pipelines en flux sans fichier temporaire, envois multipart en parallèle, zstd multithread et un catalogue en mémoire derrière une console qui répond en millisecondes.

Assez léger pour qu'on l'oublie.

Tenax est un unique binaire Go. Le contrôleur et l'API ne font que planifier et lire ; les données transitent par des Jobs de courte durée qui s'arrêtent une fois terminés, donc rien de lourd ne reste dans votre cluster entre deux sauvegardes.

54MBun binaire statique : contrôleur, API et console, movers, streamers, restorer et CLI
18MBen téléchargement compressé, sans runtime à installer ni sidecar
21MiBde mémoire du contrôleur au repos, qui surveille un cluster de bases
19MiBde mémoire de l'API et de la console au repos, catalogue en mémoire

Working set rapporté par le kubelet dans le cluster de test k3d (12 bases) après les suites de bout en bout et de chaos ; le CPU au repos se compte en quelques millicœurs. Chaque base avec restauration à un instant fait aussi tourner un streamer, environ 70 Mio pendant qu'il diffuse.

05Sécuritéchiffré à la source

Votre bucket ne voit que du chiffré.

Le chiffrement a lieu dans le pod, avant l'envoi, avec des clés que Tenax ne détient pas. L'accès est réparti par rôles, et chaque écriture comme chaque requête laisse une trace d'audit.

Ce que voit votre bucket
$ head -c 200 data/part-00001.zst.age
age-encryption.org/v1
-> X25519 Qm9yZWQ/IE5vdGhpbmcgdG8gc2VlIGhlcmUu…
--- 7Hc1kS0zR2QeYc…
ÌÞ#ø§ñ¹t¤¢Ý}ϰë։ÿ]Wœ3Ð÷…

Chaque objet de données est un fichier age chiffré pour vos destinataires (X25519). Seul le manifeste est en JSON clair, et il ne contient aucun secret.

  • Chiffré avant de partir

    Chaque objet est chiffré côté client avec age, pour vos clés. Le bucket ne voit jamais de texte clair, et la clé de catalogue de la console ne peut pas lire les données.

  • Une clé de catalogue qui ne lit pas les données

    La console lit inventaires et aperçus masqués avec une identité de catalogue distincte. Elle ne peut déchiffrer ni dumps, ni sauvegardes de base, ni WAL, ni binlogs.

  • Vérifié de bout en bout

    Le manifeste contient un SHA-256 pour chaque objet stocké, vérifié à la fin de chaque lecture, et il est lui-même écrit en dernier avec un PUT conditionnel.

  • Les secrets restent cachés

    Les identifiants parviennent aux outils enfants par des fichiers scellés en mémoire, jamais par argv ni par l'environnement. Les journaux masquent tout ce qui ressemble à un secret.

  • Rôles et piste d'audit

    Connexion via Cloudflare Access et quatre rôles, de viewer à restorer-admin. Restaurer sur place exige le rôle le plus élevé et une confirmation saisie.

  • Des pods verrouillés

    Sans root, système de fichiers racine en lecture seule, toutes les capabilities retirées, seccomp RuntimeDefault. Les pods de données ne montent qu'emptyDir.

06Comparéà une tâche cron

Bien plus qu'un dump nocturne dans un bucket.

Quand vous devez…script cron + pg_dumpTenax
Revenir à 09:41:27Le dump d'hier soir, jusqu'à 24 h perduesN'importe quelle seconde de la fenêtre
Prouver qu'une sauvegarde se restaureLe découvrir pendant l'incidentRestaurations de vérification planifiées avec vos contrôles
Trouver la version avec les lignes perduesEn restaurer plusieurs et regarderDiffs et résumés de modifications dans la console
Regarder sans risqueRestaurer sur un staging partagéUn bac à sable privé avec TTL
Garder le bucket aveugleChiffrement côté serveur, s'il est configuréChiffrement age côté client, avec vos clés
Survivre à un nœud qui tombe en pleine sauvegardeUn fichier à moitié écrit qui semble completRien ne compte avant l'écriture finale du manifeste
Savoir que tout fonctionne encoreLe silenceAlertes pour sauvegardes en retard, chaînes rompues et retard

07Installerune dizaine de minutes

De zéro à restaurable.

  1. Installez le chart

    Les CRD, le contrôleur, l'API et la console, le RBAC, les alertes et le tableau de bord.

    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. Indiquez un bucket et vos clés

    N'importe quel stockage compatible S3. Générez une clé age avec age-keygen et gardez-en une copie hors ligne.

    apiVersion: tenax.republic.global/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…]   # clé publique uniquement ; la clé privée reste hors ligne
  3. Déclarez une cible à côté de la base

    Créez l'utilisateur tenax avec le SQL de docs/sql/, puis commitez la cible dans git.

    apiVersion: tenax.republic.global/v1alpha1
    kind: BackupTarget
    metadata: { name: app, namespace: team-a }
    spec:
      engine: postgres
      endpoint: { host: postgres, port: 5432 }
      databases: [app]
      policy: prod             # complète chaque nuit, fenêtre de 7 jours
      credentials:
        secretRef: { name: tenax-postgres }
  4. Restaurez quelque chose, exprès

    La première sauvegarde complète démarre toute seule. Essayez ensuite ce dont vous espérez ne jamais avoir besoin.

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

Le guide complet, chaque champ des CRD et l'API REST sont dans la documentation (en anglais).

08FAQréponses franches

Ce que les ingénieurs demandent d'abord.

Quelles bases de données Tenax prend-il en charge ?

Postgres 15, 16 et 17, PostGIS compris, avec des sauvegardes complètes physiques ou logiques et le streaming du WAL. MySQL 8.4 avec des sauvegardes complètes logiques et le streaming du binlog.

Jusqu'où puis-je remonter, et avec quelle précision ?

Toute sauvegarde complète conservée par vos niveaux de rétention, et toute seconde de la fenêtre de restauration fixée par votre politique. La politique d'exemple prod garde une fenêtre de 7 jours et 14 complètes quotidiennes, 8 hebdomadaires, 12 mensuelles et 5 annuelles.

Tenax ou mon fournisseur de stockage peuvent-ils lire mes données ?

Le bucket, non : chaque objet est chiffré avec age pour vos destinataires avant l'envoi. La console lit les inventaires avec une clé de catalogue incapable de déchiffrer les données. Les restaurations déchiffrent avec une identité age que vous fournissez ; gardez-en une copie hors ligne.

Que se passe-t-il si un pod meurt au milieu d'une sauvegarde ?

Rien qui compte. Une sauvegarde n'existe qu'une fois son manifeste écrit, après l'envoi et la vérification de chaque objet. Une exécution inachevée n'a pas de manifeste et n'est donc jamais proposée à la restauration. La suite chaos tue Tenax en pleine sauvegarde pour le prouver.

Tenax monte-t-il des volumes ou utilise-t-il des instantanés ?

Non. Il n'atteint les bases que par le réseau, et ses pods ne montent rien d'autre qu'emptyDir : ni hostPath, ni FUSE, ni iSCSI, ni périphérique bloc, et aucun PVC par défaut. Un pilote de stockage figé ne peut pas bloquer une sauvegarde.

De quoi Tenax a-t-il besoin dans ma base ?

D'un utilisateur tenax aux privilèges minimaux par base, qui n'écrit jamais, créé avec le SQL de docs/sql/. Sans droits de réplication, Tenax fait quand même des sauvegardes logiques et indique quoi accorder pour restaurer à n'importe quelle seconde.

Tenax est-il open source ?

Son code est disponible sous The Republic Lex Aequa License 1.0. Chacun peut l'utiliser, le modifier et le redistribuer gratuitement, y compris en production et en entreprise, pour protéger ses propres données. Le vendre ou le proposer comme service à des tiers est réservé.

Testez votre restauration avant d'en avoir besoin.