Kubernetes 向けバックアップ・復元オペレーター · Postgres 15–17 · PostGIS · MySQL 8.4
1秒たりとも、手放さない。
Tenax は Kubernetes で動かしている Postgres と MySQL を、あなた自身の S3 バケットにバックアップし、すべての変更を発生したそばからストリーミングします。範囲内の任意の1秒を選べば、まさにその時点のデータベースが、クエリできるサンドボックスに戻ってきます。データはクラスタを出る前に暗号化され、クラッシュオンリー設計なので決してハングしません。
バイナリひとつ、チャートひとつ。バケットも鍵もあなたのもの。
- 復元時点
- 2026-10-10 14:02:37 UTC
- ベースバックアップ
- 20261010T020712Z-4c1e
- 続けて適用
- 114
$ tenax restore team/app --at 2026-10-10T14:02:37Z --wait
巻き戻る様子を見るか、自分で範囲の上をドラッグしてください。オレンジの目盛りが毎晩のフルバックアップ、シアンの帯がその間の変更ストリームです。prod ポリシー(毎晩フル、7日間)のイメージです。
- 9s644 MiB の Postgres をサンドボックスに復元する時間(取得・復号・リカバリ・検証まで)
- 4.2×バケット上で小さく:683 MiB の物理バックアップが 161 MiB に
- 72MiB/s4 vCPU での論理フルバックアップのスループット(一時ファイルなしのストリーミング)
- 20ms1,000 データベース・10万バックアップでのコンソール概要画面の p99
k3d テスト環境(4 vCPU、クラスタ内 MinIO)で make test-perf と make test-ui により計測。数値はリポジトリの docs/PERF.md にあります。
01仕組み5つのステップ
ライブの変更から、クエリできるサンドボックスまで。
データベース内にエージェントは不要、ボリュームスナップショットも使いません。Tenax は変更をネットワーク越しに読み取り、Pod を出る前に封をして、求められればどの1秒でも取り戻します。
- 01
ストリーミング
スケジュールされたフルバックアップに加え、すべての変更を発生したそばから取り込みます。Postgres には Tenax 独自の WAL レシーバー、MySQL には mysqlbinlog。
WAL · binlog - 02
暗号化
すべてのバイトを zstd で圧縮し、age であなたの公開鍵宛てに暗号化します。Pod の中で、一時ファイルなしの1回のストリーミング処理で行います。
zstd → age - 03
保存
S3 互換の任意のバケットへ並列マルチパートでアップロード。オブジェクトごとに SHA-256 を記録し、マニフェストは最後に書き込みます。
*.zst.age · manifest.json - 04
秒単位で復元
時刻かバックアップ ID を指定するだけ。Tenax がベースバックアップを選び、その1秒ちょうどまで変更を適用します。
--at 14:02:37 - 05
サンドボックスを開く
結果は TTL 付きの専用データベースと読み取り専用のクエリコンソールに届きます。復元を成功とみなす前に、あなたのチェックが実行されます。
--ttl 6h
02復元4つの復元先
本番に触れずに、どこへでも復元。
復元はほかと同じ Kubernetes リソースです。時刻かバックアップ ID を指定し、復元先を選ぶだけ。Tenax がベースバックアップと適用する変更を計画し、検証を実行してから成功とします。
使い捨てのコピー
TTL・認証情報・読み取り専用クエリコンソール付きのプライベートなデータベース。期限が来れば自動で片付きます。
--to sandbox --ttl 6h新しいデータベース
ステージング用サンドボックスで復元・検証してから新しい名前でコピー。既存のオブジェクトは決して上書きしません。
--to new --database app_copy本当に巻き戻す
まず安全のためのバックアップを取り、対象名の入力を求めます。restorer-admin ロールが必要です。
--to in-place --confirm appダンプファイル
復号・圧縮済みの論理ダンプを、1時間で失効するリンクで提供。監査されます。
--to download03デモ1分
1分で、一覧から復元まで。
デモ用カタログに対する本物のコマンドと本物のコンソール。全データベースを一覧し、夜のあいだに何が変わったかを確認し、サンドボックスに復元します。
コンソール
2つのバージョン間で何が変わったかがわかる。
各バックアップはテーブル・行数・容量の一覧を持ち、各変更セグメントはその内容の要約を持ちます。だから復元する前に、失った行がまだ残っているバージョンがわかります。
- 任意の2つのバックアップの差分。 スキーマ変更、行数、テーブルごとの容量。
- WAL・binlog セグメントごとの変更要約。 テーブルごとの INSERT・UPDATE・DELETE と DDL。22:48 の TRUNCATE もすぐ見つかります。
- マスク済みプレビュー。 指定した列をマスクしたサンプル行。データ本体は復号できないカタログ鍵で読めます。
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
OpenAPI で記述された JSON API と、Cloudflare Access によるロール。すべての書き込みとクエリが監査されます。
GitOps ネイティブ
ストア、ポリシー、ターゲット、復元はすべて CRD。保持階層、アラート、Grafana ダッシュボードがチャートに含まれます。
願うのではなく、検証する
定期的な検証用復元が実際の復元に対して SQL チェックを実行し、バックアップを検証済みにします。復元時間はメトリクスとして出力されます。
04信頼性クラッシュオンリー
決してハングしない。kill -9 にも動じない。
バックアップは夜中に、ありふれた理由で失敗します。固まったマウント、死んだ Pod、書きかけのファイル。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
決して固まらない
マウントもブロックデバイスもなし、データベースへはネットワーク経由のみ。すべてのストリームに進捗ウォッチドッグがあり、各ツールは独自のプロセスグループで動きます。
クラッシュオンリー
どの Pod をいつ止めても大丈夫。バックアップはマニフェストが最後に書かれて初めて成立し、カオステストがバックアップ中に Tenax を止めてそれを証明します。
データは Job で運ばれ、コントローラーは通らない。
コントローラーは計画するだけ。mover と streamer がひとつのストリーミングパイプラインでバイトをバケットへ直接運び、restorer が取り戻します。
本番にやさしい
書き込みを行わない、最小権限のデータベースユーザー 1 つ。ホストごとの枠、帯域制限、サーバーが混んでいればバックアップを延期する負荷ガード。
変更ストリーミング
Postgres はレプリケーションスロット上の Tenax 独自の WAL レシーバー、MySQL は mysqlbinlog。静かなデータベースでも範囲は現在まで届きます。
設計からして速い
一時ファイルなしのストリーミングパイプライン、並列マルチパートアップロード、マルチスレッド zstd、そしてミリ秒で応答するコンソールを支えるインメモリカタログ。
存在を忘れるほど軽い。
Tenax は Go のバイナリひとつです。コントローラーと API は計画と読み取りだけを行い、データは処理が終われば終了する短命な Job で運ばれます。バックアップの合間にクラスタに重いものは残りません。
k3d テストクラスタ(12 データベース)で E2E とカオスのスイート後に kubelet が報告したワーキングセット。待機時の CPU は数ミリコア程度です。ポイントインタイム復元を有効にした各データベースでは、ストリーミング中に約 70 MiB の streamer も動きます。
05セキュリティ送信元で暗号化
バケットに見えるのは暗号文だけ。
暗号化はアップロード前に Pod の中で、Tenax が保持しない鍵に対して行われます。アクセスはロールで分けられ、すべての書き込みとクエリが監査記録に残ります。
$ head -c 200 data/part-00001.zst.age age-encryption.org/v1 -> X25519 Qm9yZWQ/IE5vdGhpbmcgdG8gc2VlIGhlcmUu… --- 7Hc1kS0zR2QeYc… ÌÞ#ø§ñ¹t¤¢Ý}ϰë։ÿ]Wœ3Ð÷…
すべてのデータオブジェクトは、受信者宛てに暗号化された age ファイル(X25519)です。平文の JSON はマニフェストだけで、秘密情報は含みません。
出ていく前に暗号化
すべてのオブジェクトはクライアント側で age によりあなたの鍵へ暗号化されます。バケットは平文を見ず、コンソールのカタログ鍵ではデータを読めません。
データを読めないカタログ鍵
コンソールはインベントリとマスク済みプレビューを、別のカタログ用 ID で読み取ります。ダンプ、ベースバックアップ、WAL、binlog は復号できません。
端から端まで検証
マニフェストは保存した全オブジェクトの SHA-256 を持ち、読み取りのたびに最後に照合されます。マニフェスト自体は条件付き PUT で最後に書き込まれます。
秘密情報は見えないところに
認証情報は封印されたメモリ上のファイルで子プロセスに渡され、argv や環境変数には決して載りません。ログは秘密らしき値をすべて伏せます。
ロールと監査証跡
Cloudflare Access でサインインし、viewer から restorer-admin までの4つのロールで管理します。インプレース復元には最上位ロールと確認入力が必要です。
固く閉じた Pod
非 root、読み取り専用のルートファイルシステム、全 capability を削除、seccomp は RuntimeDefault。データ経路の Pod がマウントするのは emptyDir だけです。
06比較cron ジョブと
バケットに置いた夜間ダンプ以上のもの。
| こんなとき… | cron + pg_dump スクリプト | Tenax |
|---|---|---|
| 09:41:27 に戻したい | 昨夜のダンプ。最大24時間分を失う | 範囲内の任意の1秒 |
| バックアップが戻せると証明したい | 障害の最中に判明する | チェック付きの定期検証用復元 |
| 消えた行が残るバージョンを探したい | いくつも復元して確かめる | コンソールの差分と変更要約 |
| リスクなしで中身を見たい | 共有ステージングに上書き復元 | TTL 付きのプライベートなサンドボックス |
| バケットに中身を見せたくない | 設定していればサーバー側暗号化 | あなたの鍵によるクライアント側 age 暗号化 |
| バックアップ中のノード障害に耐えたい | 完全に見える書きかけのファイル | マニフェストが最後に書かれるまで何も成立しない |
| 動き続けていると知りたい | 沈黙 | 遅延バックアップ、チェーン切れ、遅延へのアラート |
07インストール約10分
ゼロから復元可能へ。
チャートをインストール
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-keygen で age 鍵を作り、オフラインに控えを保管してください。
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…] # 公開鍵のみ。秘密鍵はオフラインに
データベースの隣にターゲットを宣言
docs/sql/ の SQL で tenax ユーザーを作り、ターゲットを 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 # 毎晩フル、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 ストリーミングに対応しています。
どこまで遡って、どれくらいの精度で復元できますか?
保持ティアが残しているフルバックアップならどれでも、さらにポリシーで決めたポイントインタイム範囲内なら任意の1秒に復元できます。サンプルの prod ポリシーは7日間の範囲と、日次14・週次8・月次12・年次5のフルバックアップを保持します。
Tenax やストレージ事業者にデータを読まれませんか?
バケットからは読めません。すべてのオブジェクトはアップロード前に age で受信者宛てに暗号化されます。コンソールはデータ本体を復号できないカタログ鍵でインベントリを読みます。復元時はあなたが渡す age の ID で復号するので、オフラインのコピーを保管してください。
バックアップの途中で Pod が落ちたら?
問題になることは何も起きません。バックアップは、すべてのオブジェクトのアップロードと検証が終わり、マニフェストが書かれて初めて存在します。途中で止まった実行にはマニフェストがないため、復元候補に出ることはありません。カオステストはバックアップ中の Tenax を強制終了して、これを確かめています。
Tenax はボリュームをマウントしたりスナップショットを使ったりしますか?
いいえ。データベースにはネットワーク越しにだけ接続し、Pod がマウントするのは emptyDir だけです。hostPath、FUSE、iSCSI、ブロックデバイスは使わず、デフォルトでは PVC もありません。ストレージドライバーが固まってもバックアップは止まりません。
データベース側に何が必要ですか?
データベースごとに、書き込みを一切しない最小権限の tenax ユーザーを1つ。docs/sql/ の SQL で作成します。レプリケーション権限がなくても論理バックアップは取得でき、秒単位の復元に何を付与すればよいかを Tenax が教えてくれます。
Tenax はオープンソースですか?
The Republic Lex Aequa License 1.0 のもとでソースを公開しています。自分のデータを守るためなら、本番環境や営利企業の中も含め、誰でも無料で利用・改変・再配布できます。第三者への販売やサービスとしての提供は認められていません。