본문으로 건너뛰기

7.3 Recovery와 PITR

Recovery는 기존 물리 백업으로부터 인스턴스를 시작하는 과정입니다. 여기서 CNPG의 중요한 성격 하나를 짚어야 합니다. CNPG는 망가진 클러스터를 제자리에서 되살리는 in-place 복구를 하지 않습니다. 대신 백업을 재료로 새 Cluster를 부트스트랩합니다. 그래서 복구는 언제나 새 Cluster 리소스의 bootstrap.recovery로 표현됩니다.

복구 소스 세 가지​

새 Cluster의 bootstrap.recovery가 어디서 데이터를 가져오는지에 따라 세 방식이 있습니다.

  • object store에서 복구: Barman Cloud Plugin과 ObjectStore로 오브젝트 스토리지의 백업을 읽습니다.
  • volume snapshot에서 복구: Kubernetes 볼륨 스냅샷을 되살리고 WAL로 마무리합니다.
  • Backup 리소스에서 복구: 같은 namespace에 있는 Backup 리소스를 이름으로 참조합니다.

object store에서 복구​

복구 소스가 될 ObjectStore를 정의하고, 새 Cluster가 externalClusters로 그것을 가리킵니다.

apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: cluster-example-backup
spec:
configuration:
destinationPath: https://STORAGEACCOUNTNAME.blob.core.windows.net/CONTAINERNAME/
azureCredentials:
storageAccount:
name: recovery-object-store-secret
key: storage_account_name
storageKey:
name: recovery-object-store-secret
key: storage_account_key
wal:
maxParallel: 8
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore
spec:
superuserSecret:
name: superuser-secret
bootstrap:
recovery:
source: origin
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: cluster-example-backup
serverName: cluster-example

bootstrap.recovery.source의 값(origin)이 externalClusters의 한 항목 이름과 맞아야 합니다. serverName은 백업을 뜰 때 사용된 원본 클러스터 이름을 가리킵니다.

노트

wal.maxParallel은 아카이브에서 WAL을 동시에 몇 개까지 내려받을지를 정합니다. 값을 키우면 WAL 재생이 빨라져 복구 시간이 줄어듭니다. 복구 노드의 네트워크·CPU 여유에 맞춰 조정합니다.

volume snapshot에서 복구​

볼륨 스냅샷 이름을 bootstrap.recovery.volumeSnapshots에 지정합니다. WAL이 별도 스토리지에 있으면 walStorage도 함께 줍니다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore
spec:
bootstrap:
recovery:
source: origin
volumeSnapshots:
storage:
name: <snapshot name>
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: cluster-example-backup
serverName: cluster-example

Backup 리소스에서 복구​

같은 namespace에 Backup 리소스가 있으면 이름으로 바로 참조합니다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example-initdb
spec:
instances: 3
bootstrap:
recovery:
backup:
name: backup-example
storage:
size: 1Gi

복구가 진행되는 순서​

매니페스트를 적용한 뒤 새 Cluster 안에서 무슨 일이 일어나는지 알아 두면, 복구가 오래 걸릴 때 어느 단계에 있는지 가늠할 수 있습니다.

  1. base backup 복원: Operator가 첫 인스턴스에 init container를 넣고, 그 컨테이너가 object store(또는 스냅샷)에서 base backup을 새 PVC로 전개합니다. 이 단계의 시간은 백업 크기와 네트워크, 스토리지 속도에 비례합니다.
  2. recovery mode 기동: 전개가 끝나면 인스턴스 매니저가 PostgreSQL을 recovery mode로 띄웁니다. 이 구간에서 PostgreSQL은 떠 있지만 접속을 받지 않습니다. liveness probe는 정상으로 판정하므로 "Pod는 Running인데 접속이 안 된다"가 이 단계의 정상 모습입니다. restore_command가 archive에서 WAL을 순서대로 가져와 재생하며, wal.maxParallel을 키우면 이 구간이 짧아집니다.
  3. target 도달과 promote: recoveryTarget이 있으면 그 지점에서, 없으면 archive의 마지막 WAL(latest timeline)까지 재생한 뒤 recovery를 끝내고 primary로 승격합니다. 이때 새 timeline이 시작됩니다.
  4. 마무리: Operator가 superuser 비밀번호를 설정하고 bootstrap.recovery에 선언한 애플리케이션 database와 owner를 준비합니다. 나머지 인스턴스는 이 새 primary로부터 복제해 replica로 합류합니다.
노트

복원된 Cluster가 자기 백업도 남기게 하려면 plugins에 backup용 ObjectStore를 함께 선언하면 됩니다. 다만 복구 원본과 같은 ObjectStore를 backup 대상으로 재사용할 때는 serverName을 원본과 다르게 둬야 합니다. 같으면 원본 archive를 덮어쓰게 되고, 아래 "운영 시 주의점"의 안전 점검에 걸려 Cluster가 setup 상태에 멈춥니다.

Point-in-Time Recovery(PITR)​

PITR는 base backup을 복원한 뒤 WAL을 원하는 시점까지만 재생해 멈추는 복구입니다. 유효한 WAL archive(7.2)가 있어야 합니다. recovery target을 지정하면 Operator가 필요한 복구 파라미터를 자동으로 생성합니다.

base backup에서 recovery target까지의 PITR 재생

아래는 특정 시각까지 되돌리는 예입니다. recoveryTarget.targetTime을 준 것 외에는 object store 복구와 같습니다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore-pitr
spec:
instances: 3
storage:
size: 5Gi
bootstrap:
recovery:
source: origin
recoveryTarget:
targetTime: "2023-08-11 11:14:21.00000+02"
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: cluster-example-backup
serverName: cluster-example

실수로 실행한 DROP TABLE 직전으로 되돌리는 상황이 PITR의 대표적 쓰임새입니다.

recovery target 옵션​

멈출 지점은 다음 중 하나로 지정합니다.

target의미
targetTime되돌릴 시각. RFC 3339 형식 또는 타임스탬프
targetXID멈출 트랜잭션 ID
targetNamepg_create_restore_point()로 만든 명명 복원 지점
targetLSN멈출 WAL 로그 위치(LSN)
targetImmediate일관된 상태에 도달하는 즉시 멈춤
경고

targetName·targetXID·targetImmediate를 쓸 때는 backupID를 함께 지정해야 합니다. 반면 targetTime·targetLSN은 Operator가 가장 가까운 백업을 자동으로 찾아 줍니다.

targetName으로 명명 복원 지점까지 되돌리는 예는 다음과 같습니다.

bootstrap:
recovery:
source: origin
recoveryTarget:
backupID: 20220616T142236
targetName: 'restore_point_1'

복구 후 애플리케이션 DB 설정​

복구가 끝난 뒤 만들 데이터베이스·소유자·자격 증명을 함께 선언할 수 있습니다.

bootstrap:
recovery:
database: app
owner: app
secret:
name: app-secret

복구가 완료되면 Operator는 데이터베이스가 없으면 만들고, 사용자가 없으면 만들고, 소유권을 부여한 뒤 secret에서 비밀번호를 갱신합니다.

경고

복구 모드 동안에는 catalog를 포함해 데이터베이스에 어떤 변경도 허용되지 않습니다. role 관련 오버라이드는 primary로 승격된 뒤에야 적용됩니다.

운영 시 주의점​

  • 복구는 base backup에 barman-cloud-restore, WAL에 barman-cloud-wal-restore를 사용하며, init container가 이 과정을 투명하게 처리합니다.
  • snapshot 복구 후 replica를 만들 때 Operator가 pg_basebackup으로 폴백할 수 있는데, 대용량 DB에서는 느립니다. 스냅샷 복구는 단일 인스턴스 클러스터로 시작한 뒤 스케일업하는 것이 안전합니다.
  • 공유 스토리지 버킷의 기존 데이터를 덮어쓰지 않도록 안전 점검이 걸려 있습니다. 충돌하면 클러스터가 setup 상태에 머물고 Pod가 에러를 냅니다.
노트

안전 점검을 우회하는 cnpg.io/skipEmptyWalArchiveCheck: enabled 어노테이션은 기존 백업을 덮어쓸 수 있어 극히 신중하게만 사용합니다. 잘못 쓰면 복구 재료 자체를 잃습니다.

정리​

CNPG 복구는 in-place가 아니라 새 Cluster의 bootstrap.recovery로 부트스트랩하며, 소스는 object store·volume snapshot·Backup 리소스 세 가지입니다. PITR는 base backup에 WAL archive를 결합해 원하는 시점까지 재생하고 멈추는 복구로, target은 time·XID·name·LSN·immediate 중 하나를 고릅니다.

  • targetName·targetXID·targetImmediate는 backupID가 필요하고, targetTime·targetLSN은 자동으로 백업을 찾습니다.
  • 스냅샷 복구는 단일 인스턴스로 시작한 뒤 스케일업하고, 안전 점검 우회 어노테이션은 최후의 수단으로만 씁니다.

이것으로 백업(7.1)·WAL archiving(7.2)·복구(7.3)가 하나의 continuous backup 사이클로 이어집니다.