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.
- 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.
- 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 - 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 - 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 - 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 - 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.
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 6hUne 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_copyRevenir 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 appUn fichier de dump
Un dump logique déchiffré et compressé, derrière un lien qui expire au bout d'une heure. Audité.
--to download03Voirune 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.
La console
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
$ 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 -
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.
- 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
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.
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.
$ 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_dump | Tenax |
|---|---|---|
| Revenir à 09:41:27 | Le dump d'hier soir, jusqu'à 24 h perdues | N'importe quelle seconde de la fenêtre |
| Prouver qu'une sauvegarde se restaure | Le découvrir pendant l'incident | Restaurations de vérification planifiées avec vos contrôles |
| Trouver la version avec les lignes perdues | En restaurer plusieurs et regarder | Diffs et résumés de modifications dans la console |
| Regarder sans risque | Restaurer sur un staging partagé | Un bac à sable privé avec TTL |
| Garder le bucket aveugle | Chiffrement côté serveur, s'il est configuré | Chiffrement age côté client, avec vos clés |
| Survivre à un nœud qui tombe en pleine sauvegarde | Un fichier à moitié écrit qui semble complet | Rien ne compte avant l'écriture finale du manifeste |
| Savoir que tout fonctionne encore | Le silence | Alertes pour sauvegardes en retard, chaînes rompues et retard |
07Installerune dizaine de minutes
De zéro à restaurable.
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}'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
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 }
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é.