콘텐츠로 이동

백업·복구

/admin/backup admin

플랫폼과 스택의 상태를 S3 호환 저장소로 떠 두고, 필요할 때 되돌립니다. 화면 설명 그대로 "백업을 실행하고 지난 실행을 확인합니다".

복구는 화면이 없습니다

백업은 화면에서 하지만, 복구는 아직 UI 가 없습니다. web/src/ 아래에 복구 화면·훅·API 클라이언트가 전혀 없고, POST /api/v1/admin/restores 를 직접 호출해야 합니다. 절차는 아래에 적어 두었습니다.

백업하는 법

  1. 오른쪽 위 백업 실행 을 누릅니다.
  2. 백업 대상 선택 창에서 범위를 고릅니다.
  3. 받아 갈 항목(아래 6개)을 체크합니다.
  4. 볼륨을 포함하면 확인 문구를 입력해야 합니다.
  5. 백업 시작.

백업은 동기로 끝납니다. 요청이 201 로 돌아오는 시점에 이미 작업이 끝나 있습니다. 화면은 진행 중인 실행이 있을 때만 5초마다 목록을 새로 읽습니다.

백업 범위 세 가지

선택 모드 값 담는 것
전체 — 플랫폼 + 모든 스택 백업 세트 플랫폼 1건 + 스택마다 1건
스택 하나 (네임스페이스 표시) full 플랫폼 + 그 스택
플랫폼만 (스택 없음) platform_only 플랫폼 DB·Keycloak DB

스택을 하나 고르면 기본으로 첫 스택이 선택되고, 항목 6개가 모두 체크돼 있습니다. 플랫폼만 을 고르면 스택 전용 항목 4개는 비활성화되고, 전체 를 고르면 항목 선택 자체가 서버 판단으로 넘어갑니다.

받아 가는 항목 여섯 개

항목 무엇이 들어가나
platform_db Nullus 플랫폼 PostgreSQL (pg_dump --format=custom)
keycloak_db Keycloak 자체 데이터베이스
openbao_kv 스택별 OpenBao KV 를 논리 재귀 내보내기 (raft 스냅샷 아님)
ns_resources 네임스페이스 리소스 — Deployment·StatefulSet·Service·ConfigMap·Secret·Ingress·Gateway/HTTPRoute·ESO·Argo CD·Prometheus 오퍼레이터 객체 + 그 네임스페이스 Helm 릴리스가 소유한 클러스터 범위 객체(CRD·ClusterRole·ClusterRoleBinding) + Argo CD 대상 네임스페이스와 레지스트리 pull Secret. PVC 는 일부러 제외(볼륨 쪽이 맡습니다)
shared_resources 마지막 스택과 함께 회수되는 클러스터 공용 자원 — Gateway API CRD·GatewayClass, 내부 CA(ClusterIssuer·Certificate·Secret)
volume 네임스페이스의 모든 PVC 를 헬퍼 파드로 tar 떠 가기

Helm 릴리스 상태는 따로 담지 않습니다

릴리스 Secret(sh.helm.release.v1.*)이 ns_resources 에 함께 들어가고, 차트 이름·버전·리비전은 매니페스트에 기록됩니다. cert-manager 와 metrics-server 릴리스 자체는 백업하지 않고 복구 때 다시 설치합니다.

볼륨을 포함하면 서비스가 멈춥니다

볼륨 항목이 들어가면 그 네임스페이스의 Deployment·StatefulSet 을 전부 레플리카 0 으로 내리고, 파드가 사라진 뒤 아카이브하고, 끝나면 되돌립니다. 실패해도 되돌립니다.

그래서 화면에 주황색 경고가 뜨고 확인 문구를 요구합니다.

범위 입력할 문구
스택 하나 그 네임스페이스 이름
전체 all

서버도 같은 규칙을 다시 확인하고, 틀리면 실행을 만들기 전에 400 BACKUP_CONFIRM_REQUIRED 로 거절합니다. 실제 멈춘 시간은 표의 정지 시간 컬럼에 기록됩니다.

이력 표

컬럼 내용
시작 실행이 만들어진 시각
대상 스택 이름, 플랫폼이면 플랫폼. 세트 소속이면 세트 <앞 8자리> 배지가 붙습니다
모드 full · platform_only · stack_only
크기 total_bytes 를 사람이 읽는 단위로
정지 시간 볼륨 때문에 멈춘 초. 없으면 —
상태 pending·running(파랑) · succeeded(초록) · partial(주황) · failed(빨강)

partial 은 일부 항목만 성공한 상태입니다. 초록으로 칠하지 않은 건 의도입니다 — 전부 받아 온 백업과 구분해야 하니까요. 실패 사유는 상태 옆에 작은 글씨로 붙습니다.

정합성 확인 (verify)

POST /api/v1/admin/backups/{id}/verify

응답은 {backup_run_id, ok, checked_count, schema_check, missing_objects[], checksum_fails[]} 입니다.

verify 는 암호를 풀어 보지 않습니다

각 산출물에 대해 저장소에 객체가 있는지, 업로드 때 메타데이터에 적어 둔 sha256 이 DB 기록과 같은지, 그리고 스키마 버전이 복구 가능한지를 봅니다. 내려받아 다시 해시하거나 복호화하지는 않습니다. 그래서 나중에 저장소에서 바이트가 깨지거나 잘린 경우는 잡지 못합니다. 그건 실제 복구 때 암호를 풀면서 드러납니다(청크마다 "마지막" 표시를 AAD 로 묶어 두어 잘린 암호문은 열리지 않습니다).

백업 세트

하나의 백업 실행에는 플랫폼 또는 스택 하나만 담깁니다 — 스택 둘을 한 실행에 담지 않습니다. 스택마다 클러스터와 금고가 따로이기 때문입니다.

전체 를 고르면 set_id 를 공유하는 실행 여러 개가 만들어집니다.

  • 순서: 플랫폼(platform_only) 먼저, 그다음 완료된 스택마다 하나(stack_only + 그 스택 금고)
  • 차례로, 동기로 돕니다. 한 건이 실패해도 나머지는 계속합니다
  • 세트 상태는 저장하지 않고 그때그때 계산합니다 — 하나라도 진행 중이면 running, 전부 실패면 failed, 전부 성공이면 succeeded, 그 외 partial

복구

복구는 API 로만 합니다.

curl -X POST "$API/api/v1/admin/restores" \
  -H 'Content-Type: application/json' \
  -d '{"backup_run_id":"<백업 ID>","confirm":"<같은 백업 ID>","namespace":"<스택 네임스페이스>"}'
  • confirm 에는 백업 ID 를 그대로 넣습니다. 다르면 400 RESTORE_CONFIRM_REQUIRED.
  • 백업에 stack_id 가 있고 모드가 platform_only 가 아니면 namespace 가 필수입니다.
  • 진행 상태는 GET /api/v1/admin/restores/{id} 로 봅니다.

복구가 하는 일 (실제 순서)

  1. 선행 조건 — ENCRYPTION_KEY(32바이트), 백업 봉인 키(32바이트), 목적지 자격증명, 목적지 도달 확인. 하나라도 없으면 아무것도 건드리지 않고 멈춥니다
  2. 백업이 succeeded 또는 partial 인지 확인
  3. 산출물 정합성 사전 점검 (verify 와 같은 방식)
  4. 스키마 확인 — 현재 DB 가 dirty 거나, 백업 버전이 현재보다 높으면 중단
  5. 차트 버전 차이는 경고로만 남기고 막지 않습니다
  6. 스택 쪽 — 네임스페이스 확보 → 내부 CA Secret 을 먼저 적용 → cert-manager·metrics-server 재설치 → 공용 자원을 없는 것만 적용 → 매니페스트의 크기·StorageClass 로 PVC 재생성 → 볼륨 tar 복원 → 네임스페이스 리소스 적용
  7. 플랫폼 쪽 — pg_restore --clean --if-exists. 백업 이력 테이블은 복원하지 않습니다. 이어서 Keycloak DB(실패해도 치명적이지 않음)
  8. 워크로드를 매니페스트의 원래 레플리카로 되돌림
  9. 스택 금고 KV 를 그 뒤에 가져오기 (기본 2분까지 10초 간격 재시도, 실패해도 복구는 성공)
  10. 참조 정합성 점검 → integrity_report
  11. 게이트웨이 주소 재고정 — 러너·Argo CD 의 hostAliases 를 지금 게이트웨이 주소로 고쳐 씀

복구는 기존 것을 지우지 않습니다

네임스페이스·릴리스·PVC 를 삭제하지 않습니다. 네임스페이스가 있는지 확인한 뒤 그 위에 덮어씁니다. 그래서 백업 시점에 없던 리소스는 복구 뒤에도 남습니다 — 깨끗한 상태로 되돌리려면 네임스페이스를 먼저 지우세요.

dangling 은 무엇인가

복구 결과의 integrity_report.dangling[] 은 복원된 데이터베이스가 가리키는 비밀 경로 중, 복원된 금고에 실제로 없는 것의 목록입니다({token_source_id, path}).

조직의 토큰 출처 경로를 하나씩 금고에 있는지 물어보고, 없는 것을 모읍니다. checked_paths 는 확인한 경로 수입니다. 경고일 뿐 복구는 성공으로 보고되며, 해당 토큰 출처는 다시 등록해야 합니다.

세트 복구

플랫폼을 먼저 복구합니다. 플랫폼이 실패하면 "플랫폼 복구 실패 — 스택은 되돌리지 않았습니다" 로 전체를 멈춥니다. 스택들은 서로 독립적으로 이어 갑니다.

저장소와 암호화

항목 값
저장 위치 클러스터 밖 S3 호환 버킷 하나
객체 키 [<prefix>/]backup-<실행ID>/<이름>
암호화 스트리밍 AES-256-GCM, 1 MiB 청크, 헤더 NBK1
압축 없음 — 볼륨은 tar -cpf - 그대로 (pg_dump 내부 압축만 있음)
로컬 디스크 쓰지 않음 (파이프로 바로 업로드)
키 하나뿐 (backup.seal_key, 32바이트). ENCRYPTION_KEY 및 DB 비밀번호와 달라야 하고, 같으면 서버가 뜨지 않습니다

멈추기 전에 목적지 사전 점검을 합니다 — 버킷 존재 확인에 그치지 않고 탐침 객체를 실제로 PUT·DELETE 해 봅니다.

버킷에 매니페스트 파일은 없습니다

매니페스트는 데이터베이스 컬럼에만 있고 GET /api/v1/admin/backups/{id} 로 꺼냅니다. 버킷만 들고 다른 곳에서 복구할 수는 없습니다. 산출물 이름에도 .enc·.zst 가 붙지 않습니다 — 압축하지 않고, 암호화 여부는 이름에 드러나지 않습니다.

예약 백업

구현돼 있지만 기본값은 꺼짐입니다.

항목 기본값
backup.schedule.enabled false
주기 24시간
1회 제한 시간 4시간
보존 일 7 · 주 4 · 월 3

앞 백업이 아직 돌고 있으면 그 틱을 건너뜁니다. 보존 정책은 객체만 지우고 이력 행은 남기며, 가장 최근 성공 백업은 절대 지우지 않습니다. 보존은 예약 백업에서만 돌고, 수동 백업에는 적용되지 않습니다.

더 읽을 것

지금 없는 것

없는 것 비고
백업 삭제 API DELETE /backups/:id 가 없습니다. 보존 정책만 지웁니다
감사 로그 백업·복구는 audit_logs 에 남지 않습니다 — 누가 언제 복구했는지 추적할 수 없습니다
복구 화면 API 전용
복구 후 스모크 검사 없습니다. 복구가 succeeded 라도 로그인·git clone·이미지 pull·파이프라인 재실행은 직접 확인하세요
키 에스크로·회전 코드에는 없습니다. 봉인 인터페이스가 BYOK 교체 지점으로만 열려 있습니다