Kubernetes용 백업 & 복원 오퍼레이터 · Postgres 15–17 · PostGIS · MySQL 8.4
모든 순간을 붙잡아 두세요.
- 복원 시점
- 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에서의 논리 전체 백업 처리량, 임시 파일 없이 스트리밍
- 20ms데이터베이스 1,000개와 백업 100,000개가 있을 때 콘솔 개요의 p99
k3d 테스트 하네스(4 vCPU, 클러스터 내부 MinIO)에서 make test-perf와 make test-ui로 측정했습니다. 수치는 저장소의 docs/PERF.md에 있습니다.
01작동 방식다섯 단계
실시간 변경에서 쿼리할 수 있는 샌드박스까지.
데이터베이스 안에 에이전트도, 볼륨 스냅샷도 없습니다. Tenax는 네트워크로 변경 사항을 읽고, 파드를 떠나기 전에 봉인하며, 요청하면 어떤 초든 되돌려 놓습니다.
- 01
스트리밍
예약된 전체 백업과 함께 모든 변경 사항을 발생하는 즉시 받아 옵니다. Postgres는 Tenax 자체 WAL 수신기로, MySQL은 mysqlbinlog로 처리합니다.
WAL · binlog - 02
암호화
모든 바이트는 파드 안에서 임시 파일 없이 한 번의 스트리밍으로 zstd 압축되고 여러분의 공개 키에 맞춰 age로 암호화됩니다.
zstd → age - 03
저장
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살펴보기1분
시작부터 복원까지 1분.
데모 카탈로그를 대상으로 실제 명령어와 실제 콘솔을 보여 줍니다. 모든 데이터베이스를 나열하고, 밤사이 무엇이 바뀌었는지 확인한 뒤, 샌드박스로 복원합니다.
콘솔
두 버전 사이에 무엇이 바뀌었는지 알 수 있습니다.
모든 백업에는 테이블, 행 수, 크기의 인벤토리가 담겨 있습니다. 모든 변경 세그먼트에는 그 세그먼트가 한 일의 요약이 담겨 있습니다. 그래서 무엇이든 복원하기 전에, 잃어버린 행이 아직 남아 있는 버전이 어느 것인지 확인할 수 있습니다.
- 어떤 두 백업 사이든 차이 비교. 테이블별 스키마 변경, 행 수, 크기.
- 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에도 끄떡없습니다.
백업은 밤중에 뻔한 이유로 실패합니다. 멈춘 마운트, 죽은 파드, 반쯤 쓰인 파일. 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
절대 멈추지 않음
마운트도 블록 디바이스도 없고, 데이터베이스는 네트워크로만 접근합니다. 모든 스트림에는 진행 감시기가 있고, 모든 도구는 자체 프로세스 그룹에서 실행됩니다.
크래시 전용
언제든 어떤 파드든 종료해도 됩니다. 백업은 매니페스트가 마지막으로 기록된 뒤에야 존재하며, 카오스 테스트 스위트가 백업 도중 Tenax를 종료해 이를 증명합니다.
데이터는 컨트롤러가 아니라 Job에서 이동합니다.
컨트롤러는 예약만 합니다. mover와 streamer가 하나의 스트리밍 파이프라인으로 바이트를 버킷까지 곧바로 옮기고, restorer가 이를 되돌려 놓습니다.
프로덕션에 부담 없이
절대 쓰지 않는 최소 권한 데이터베이스 사용자 하나. 호스트별 슬롯, 대역폭 제한, 서버가 바쁠 때 백업을 연기하는 부하 보호 장치.
변경 스트리밍
Postgres는 복제 슬롯에 연결하는 Tenax 자체 WAL 수신기로, MySQL은 mysqlbinlog로 처리합니다. 조용한 데이터베이스에서도 복원 가능 기간이 현재까지 이어집니다.
설계부터 빠르게
임시 파일 없는 스트리밍 파이프라인, 병렬 멀티파트 업로드, 멀티스레드 zstd, 그리고 밀리초 단위로 응답하는 콘솔 뒤의 인메모리 카탈로그.
있는 줄도 모를 만큼 가볍습니다.
Tenax는 Go 바이너리 하나입니다. 컨트롤러와 API는 예약하고 읽기만 하며, 데이터는 작업이 끝나면 종료되는 단기 Job에서 이동합니다. 그래서 백업과 백업 사이에는 클러스터에 무거운 것이 남지 않습니다.
엔드투엔드 및 카오스 테스트 스위트 실행 후 k3d 테스트 클러스터(데이터베이스 12개)에서 kubelet이 보고한 워킹 세트입니다. 유휴 상태 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Ð÷…
모든 데이터 오브젝트는 여러분의 수신자(X25519)에 맞춰 암호화된 age 파일입니다. 매니페스트만 평문 JSON이며, 비밀 정보는 담겨 있지 않습니다.
떠나기 전에 암호화
모든 오브젝트는 클라이언트 측에서 age로 여러분의 키에 맞춰 암호화됩니다. 버킷은 평문을 절대 보지 못하며, 콘솔의 카탈로그 키로는 데이터를 읽을 수 없습니다.
데이터를 읽을 수 없는 카탈로그 키
콘솔은 별도의 카탈로그 ID로 인벤토리와 마스킹된 미리 보기를 읽습니다. 덤프, 기준 백업, WAL, binlog는 복호화할 수 없습니다.
처음부터 끝까지 검사
매니페스트에는 저장된 모든 오브젝트의 SHA-256이 담겨 있어 매번 읽기가 끝날 때 검사되며, 매니페스트 자체는 조건부 PUT으로 마지막에 기록됩니다.
비밀 정보는 보이지 않게
데이터베이스 자격 증명은 argv나 환경 변수가 아니라 봉인된 인메모리 파일을 통해 하위 도구에 전달됩니다. 로그는 비밀처럼 보이는 것을 모두 가립니다.
역할과 감사 기록
Cloudflare Access로 로그인하며, viewer부터 restorer-admin까지 네 가지 역할이 있습니다. 제자리 복원에는 최상위 역할과 직접 입력하는 확인이 필요합니다.
잠금된 파드
non-root, 읽기 전용 루트 파일시스템, 모든 capability 제거, RuntimeDefault seccomp. 데이터 경로 파드는 emptyDir만 마운트합니다.
06비교cron 작업과
버킷에 쌓이는 야간 덤프 그 이상.
| 이럴 때… | cron + pg_dump 스크립트 | Tenax |
|---|---|---|
| 09:41:27로 되돌리기 | 어젯밤 덤프, 최대 24시간 손실 | 기간 안의 어떤 초든 |
| 백업이 복원되는지 증명하기 | 장애가 난 뒤에야 알게 됨 | 여러분의 검사를 포함한 예약 검증 복원 |
| 사라진 행이 있는 버전 찾기 | 여러 개를 복원해서 들여다보기 | 콘솔의 차이 비교와 변경 요약 |
| 위험 없이 살펴보기 | 공유 스테이징 데이터베이스 위에 복원 | 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.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…] # 공개 키만; 개인 키는 오프라인에 보관
데이터베이스 옆에 대상 선언
docs/sql/의 SQL로 tenax 데이터베이스 사용자를 만든 뒤 대상을 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는 어떤 데이터베이스를 지원하나요?
PostGIS를 포함한 Postgres 15, 16, 17을 물리 또는 논리 전체 백업과 WAL 스트리밍으로 지원합니다. MySQL 8.4는 논리 전체 백업과 binlog 스트리밍으로 지원합니다.
얼마나 이전까지, 얼마나 정밀하게 복원할 수 있나요?
보존 티어가 유지하는 모든 전체 백업, 그리고 정책이 정한 특정 시점 복원 기간 안의 어떤 초든 가능합니다. 예시 prod 정책은 7일 기간과 함께 일일 14개, 주간 8개, 월간 12개, 연간 5개의 전체 백업을 보존합니다.
Tenax나 스토리지 제공업체가 제 데이터를 읽을 수 있나요?
버킷은 읽을 수 없습니다. 모든 오브젝트는 업로드 전에 여러분의 수신자에 맞춰 age로 암호화됩니다. 콘솔은 데이터 자체는 복호화할 수 없는 카탈로그 키로 인벤토리를 읽습니다. 복원은 여러분이 제공한 age ID로 복호화하므로, 그 오프라인 사본을 보관해 두세요.
백업 도중 파드가 죽으면 어떻게 되나요?
인정되는 것은 아무것도 없습니다. 백업은 모든 오브젝트가 업로드되고 체크섬이 확인된 뒤 매니페스트가 기록되어야 비로소 존재합니다. 중간에 끝난 실행에는 매니페스트가 없으므로 복원 대상으로 제시되지 않습니다. 카오스 테스트 스위트가 백업 도중 Tenax를 종료해 이를 증명합니다.
Tenax는 볼륨을 마운트하거나 스냅샷을 사용하나요?
아니요. 데이터베이스에는 네트워크로만 접근하며, 파드는 emptyDir 외에는 아무것도 마운트하지 않습니다. hostPath, FUSE, iSCSI, 블록 디바이스를 쓰지 않으며 기본적으로 PVC도 없습니다. 멈춘 스토리지 드라이버가 백업을 멈추게 할 수 없습니다.
Tenax가 데이터베이스에 요구하는 것은 무엇인가요?
데이터베이스마다 절대 쓰지 않는 최소 권한 tenax 사용자 하나로, docs/sql/의 SQL로 만듭니다. 복제 권한이 없어도 Tenax는 논리 백업을 수행하며, 어떤 초로든 복원하려면 무엇을 부여해야 하는지 알려 줍니다.
Tenax는 오픈 소스인가요?
The Republic Lex Aequa License 1.0에 따라 소스가 공개되어 있습니다. 자신의 데이터를 보호하기 위해서라면 상업적 기업의 프로덕션 환경을 포함해 누구나 무료로 사용, 수정, 재배포할 수 있습니다. 제3자를 위해 판매하거나 서비스로 호스팅하는 권리는 유보되어 있습니다.