복구
복구 (Recovery)
PostgreSQL에서 물리적 백업으로부터 클러스터를 복구하는 방법을 알려 드릴게요. CloudNativePG에서 복구는 기존 클러스터에 인플레이스로 수행되지 않고 새 클러스터를 부트스트랩하는 방식으로 이뤄져요. 객체 스토어, 볼륨 스냅샷, Backup 객체 기반 복구와 시점 복구(PITR)까지 정리해 드릴게요.
출처: 문서
본문
PostgreSQL에서 **복구(recovery)**란 기존 물리적 백업에서 인스턴스를 시작하는 프로세스를 말해요. PostgreSQL의 복구 시스템은 견고하고 기능이 풍부하며, **시점 복구(PITR, Point-In-Time Recovery)**를 지원해요. 이는 가장 이른 사용 가능한 백업부터 가장 최근의 아카이빙된 WAL 파일까지 클러스터를 특정 순간으로 복원하는 기능이에요.
:::info[Important] PITR을 수행하려면 유효한 WAL 아카이브가 필요해요. :::
CloudNativePG에서 복구는 기존 클러스터에서 인플레이스(in-place)로 수행되지 않아요. 대신 물리적 백업에서 새 클러스터를 부트스트랩하는 데 사용돼요.
:::note
bootstrap 스탠자를 구성하는 방법에 대한 자세한 내용은 Bootstrap을 참고해 주세요.
:::
recovery 부트스트랩 모드를 사용하면 물리적 베이스 백업에서 클러스터를 초기화하고 관련 WAL 파일을 재생해 시스템을 일관된 상태, 그리고 선택적으로 시점 상태로 만들 수 있어요.
CloudNativePG는 다음을 통한 복구를 지원해요:
- 플러그형 백업 및 복구 인터페이스(CNPG-I): Barman Cloud Plugin 같은 외부 도구와의 통합을 가능하게 해요.
- 볼륨 스냅샷에서의 네이티브 복구: 기본 Kubernetes 스토리지 인프라가 지원하는 경우.
- Barman Cloud를 통한 객체 스토어에서의 네이티브 복구: 버전 1.26부터 플러그인 기반 접근 방식을 위해 deprecated됐어요.
버전 1.26에서 네이티브 Barman Cloud 지원이 deprecated되면서, 이 섹션은 이제 두 가지 지원 복구 방법에 초점을 맞춰요: 객체 스토어에서의 복구를 위한 Barman Cloud Plugin 사용과 볼륨 스냅샷에서의 복구를 위한 네이티브 인터페이스예요.
:::info[Important] 레거시 문서는 Appendix B – 객체 스토어에서의 복구를 참고해 주세요. :::
Barman Cloud Plugin으로 객체 스토어에서 복구 (Recovery from an Object Store with the Barman Cloud Plugin)
이 섹션은 권장되는 Barman Cloud Plugin을 사용해 객체 스토어에서 PostgreSQL 클러스터를 복구하는 방법을 설명해요.
:::info[Important]
객체 스토어에는 CloudNativePG Cluster가 생성한 백업 데이터가 포함되어 있어야 해요—즉 deprecated된 네이티브 Barman Cloud 통합 또는 Barman Cloud Plugin 중 하나를 통해요.
:::
:::info 전체 내용은 Barman Cloud Plugin 문서의 "Postgres 클러스터 복구" 섹션을 참고해 주세요. :::
베이스 백업과 WAL 파일을 모두 보유한 객체 스토어를 정의하는 것부터 시작해요. Barman Cloud Plugin은 이를 위해 커스텀 ObjectStore 리소스를 사용해요. 다음 예시는 Azure Blob Storage용으로 하나를 구성하는 방법을 보여줘요:
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
다음으로, 정의한 ObjectStore를 사용하도록 Cluster 리소스를 구성해요. bootstrap 섹션에서 복구 소스를 지정하고, 플러그인을 참조하는 externalCluster 항목을 정의해요:
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
VolumeSnapshot 객체에서 복구 (Recovery from VolumeSnapshot Objects)
:::warning
VolumeSnapshot에서 primary 인스턴스를 복구한 후 레플리카를 만들 때, 오퍼레이터는 이를 동기화하기 위해 pg_basebackup을 사용하는 것으로 폴백할 수 있어요. 이 프로세스는 전체 베이스 백업을 포함하므로 특히 대규모 데이터베이스에서 상당히 느릴 수 있어요. 이 제한은 향후 스케일-업 프로세스에서 온라인 백업과 PVC 복제 지원으로 해결될 예정이에요.
:::
CloudNativePG를 사용하면 기존 Cluster에 속한 PersistentVolumeClaim(PVC)의 VolumeSnapshot에서 새 클러스터를 만들 수 있어요.
이 스냅샷은 볼륨 스냅샷 백업용 선언적 API를 사용해 생성돼요.
복구 프로세스를 완료하려면 새 클러스터도 변경을 재적용하고 복구를 완료하는 데 필요한 WAL 아카이브에 대한 접근을 제공하는 외부 클러스터를 참조해야 해요.
다음 예시는 베이스 백업용 VolumeSnapshot과 Barman Cloud Plugin을 통해 접근하는 WAL 아카이브를 모두 사용해 복구되는 클러스터를 보여줘요:
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
백업된 클러스터가 WAL 파일을 저장하는 데 별도의 PVC를 사용했다면 복구에도 그것을 포함해야 해요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore
spec:
[...]
bootstrap:
recovery:
volumeSnapshots:
storage:
name: <snapshot name>
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
walStorage:
name: <snapshot name>
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
이전 예시는 애플리케이션 데이터베이스와 그 소유 사용자가 기본값으로 app이라고 가정해요. 복원되는 PostgreSQL 클러스터가 다른 이름을 사용한다면, "애플리케이션 데이터베이스 구성"에 문서화된 대로 복구 단계를 종료하기 전에 이 이름들을 지정해야 해요.
:::warning 스냅샷에서 replica-mode 클러스터를 부트스트랩할 때, primary뿐 아니라 스탠바이 인스턴스에도 스냅샷을 활용하려면 다음을 권장해요:
1. 단일 인스턴스 replica 클러스터로 시작하세요. primary 인스턴스는 스냅샷과 소스 클러스터의 사용 가능한 WAL을 사용해 복구돼요.
2. replica 클러스터의 primary 스냅샷을 찍으세요.
3. 원하는 대로 replica 클러스터의 인스턴스 수를 늘리세요.
:::
Backup 객체에서 복구 (Recovery from a Backup object)
클러스터를 만들 네임스페이스에 Backup 리소스가 이미 있다면, 다음 예시처럼 .spec.bootstrap.recovery.backup.name을 사용해 이름을 지정할 수 있어요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example-initdb
spec:
instances: 3
bootstrap:
recovery:
backup:
name: backup-example
storage:
size: 1Gi
이 부트스트랩 방법을 사용하면 복원해야 하는 백업에 대한 참조만 지정하면 돼요.
이전 예시는 애플리케이션 데이터베이스와 그 소유 사용자가 기본값으로 app이라고 가정해요. 복원되는 PostgreSQL 클러스터가 다른 이름을 사용한다면, "애플리케이션 데이터베이스 구성"에 문서화된 대로 복구 단계를 종료하기 전에 이 이름들을 지정해야 해요.
추가 고려사항 (Additional Considerations)
객체 스토어, 볼륨 스냅샷, 또는 기존 Backup 리소스 중 어디에서 복구하든, Cluster가 완전히 primary로 승격되어 쓰기 작업을 수락할 때까지 카탈로그를 포함한 데이터베이스에 대한 변경은 허용되지 않아요. 이 제한에는 Cluster가 primary로 전환될 때까지 연기되는 모든 역할 오버라이드가 포함돼요.
결과적으로 다음 고려사항이 적용돼요:
- 애플리케이션 데이터베이스 이름과 사용자는 복원 중인 백업에서 복사돼요. 오퍼레이터는 현재 기본 시크릿을 백업하지 않는데, 이는 Kubernetes 클러스터의 일반적인 유지 관리 활동의 일부이기 때문이에요.
- 원래 postgres 사용자 비밀번호를 보존하려면
enableSuperuserAccess를 구성하고superuserSecret을 제공하세요.
기본적으로 복구는 기본 대상 타임라인(latest)의 가장 최근 사용 가능한 WAL까지 계속돼요. 선택적으로 recoveryTarget을 지정해 시점 복구를 수행할 수 있어요 (자세한 내용은 시점 복구(PITR) 참고).
:::info[Important]
barmanObjectStore.wal.maxParallel 옵션을 사용해 복구 객체 스토어에서 트랜잭션 로그를 동시에 다운로드함으로써 아카이브에서 WAL 페치 속도를 높이는 것을 고려해 보세요.
:::
시점 복구 (Point in time recovery - PITR)
모든 WAL을 최신 것까지 재생하는 대신, 베이스 백업을 추출한 후 PostgreSQL에 언제든 WAL 재생을 중지하도록 요청할 수 있어요. PostgreSQL은 이 기법으로 PITR을 달성해요. WAL 아카이브의 존재가 필수적이에요.
:::info[Important] PITR에는 복구 대상에 설명된 옵션을 사용해 복구 대상을 지정해야 해요. :::
복구 대상을 지정하면 오퍼레이터는 이 기능이 작동하는 데 필요한 구성 파라미터를 생성해요.
객체 스토어에서의 PITR (PITR from an object store)
이 예시는 앞서 Barman Cloud plugin용으로 정의한 Azure의 동일한 복구 객체 스토어를 사용하며, 베이스 백업과 WAL 아카이브를 모두 포함해요. 복구 대상은 요청된 타임스탬프를 기반으로 해요.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore-pitr
spec:
instances: 3
storage:
size: 5Gi
bootstrap:
recovery:
# WAL 아카이브와 베이스 백업을 포함한 복구 객체 스토어
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
이 예시에서는 타임스탬프 형태의 targetTime만 지정하면 돼요. 복구를 시작할 베이스 백업을 지정할 필요는 없었어요.
backupID 옵션은 복구 프로세스를 시작할 베이스 백업을 지정할 수 있게 해주는 옵션이에요. 기본적으로 이 값은 비어 있어요.
이 값(Barman 백업 ID 형태)을 할당하면 오퍼레이터는 그 백업을 복구의 베이스로 사용해요.
:::info[Important] 그런 백업이 존재하고 접근 가능한지 확인해야 해요. :::
백업 ID를 지정하지 않으면 오퍼레이터는 다음과 같이 복구의 베이스 백업을 감지해요:
targetTime또는targetLSN을 사용하면 오퍼레이터는 그 대상보다 먼저 완료된 가장 가까운 백업을 선택해요.- 그 외에는 오퍼레이터는 시간 순서상 마지막 사용 가능한 백업을 선택해요.
VolumeSnapshot 객체에서의 시점 복구 (PITR from VolumeSnapshot Objects)
다음 예시는 다음을 사용해 **시점 복구(PITR)**를 수행하는 방법을 보여줘요:
PGDATA디렉토리의 KubernetesVolumeSnapshot으로, 베이스 백업을 제공해요. 이 스냅샷은recovery.volumeSnapshots섹션에 지정되며 이름은test-snapshot-1이에요.- 아카이빙된 WAL 파일을 포함한 복구 객체 스토어 (이 경우 MinIO). 객체 스토어는 Barman Cloud Plugin
ObjectStore리소스(여기서는 표시되지 않음)로 정의되며, 외부 클러스터 구성을 가리키는recovery.source필드를 사용해 참조돼요.
클러스터는 recoveryTarget.targetTime 옵션을 사용해 특정 시점으로 복원돼요.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example-snapshot
spec:
# ...
bootstrap:
recovery:
source: origin
volumeSnapshots:
storage:
name: test-snapshot-1
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
recoveryTarget:
targetTime: "2023-07-06T08:00:39Z"
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: minio-backup
serverName: cluster-example
이 설정을 통해 CloudNativePG는 볼륨 스냅샷에서 베이스 데이터를 복원하고 객체 스토어에서 WAL 세그먼트를 적용해 원하는 복구 대상에 도달할 수 있어요.
:::note
백업된 클러스터에 walStorage가 활성화되어 있었다면, VolumeSnapshot 객체에서의 복구에서 언급한 대로 PGWAL 디렉토리가 포함된 볼륨 스냅샷도 지정해야 해요.
:::
:::warning 볼륨 스냅샷의 베이스 백업 종료 시간이 복구 대상 타임스탬프 이전인지 확인하는 것은 여러분의 책임이에요. :::
:::warning 마지막 베이스 백업 이후 클러스터에 테이블스페이스(tablespace)를 추가하거나 제거했다면 WAL 재생이 실패해요. 테이블스페이스 변경 시점과 복구 대상 타임스탬프 사이의 베이스 백업이 필요해요. :::
복구 대상 (Recovery targets)
사용할 수 있는 복구 대상 기준은 다음과 같아요:
targetTime
: 복구가 진행되는 타임스탬프로, RFC 3339 형식 또는 timestamp 형식으로 표현돼요.
(정확한 중지 지점은 exclusive 옵션에도 영향을 받아요.)
:::note
명시적 타임존 접미사가 없는 타임스탬프(예: 2023-07-06 08:00:39)는 UTC로 해석돼요.
:::
:::warning
모호함을 피하려면 타임스탬프에 항상 명시적 타임존을 지정하세요.
예를 들어 2023-07-06 08:00:39 대신 2023-07-06T08:00:39Z 또는 2023-07-06T08:00:39+02:00을 사용하세요.
:::
:::warning PostgreSQL 복구는 지정된 시간 이후에 발생하는 첫 번째 트랜잭션을 만나면 중지돼요. 대상 시간 이후에 그런 트랜잭션이 없으면 복구 프로세스가 실패해요. :::
targetXID
: 복구가 진행되는 트랜잭션 ID.
(정확한 중지 지점은 exclusive 옵션에도 영향을 받아요.)
트랜잭션 ID는 트랜잭션 시작 시 순차적으로 할당되지만 트랜잭션은 다른 숫자 순서로 완료될 수 있다는 점을 기억하세요.
복구되는 트랜잭션은 지정된 트랜잭션 이전에(그리고 선택적으로 포함해) 커밋된 것들이에요.
targetName
: 복구가 진행되는 명명된 복원 지점(pg_create_restore_point()로 생성됨).
targetLSN
: 복구가 진행되는 write-ahead 로그 위치의 LSN.
(정확한 중지 지점은 exclusive 옵션에도 영향을 받아요.)
targetImmediate : 일관된 상태에 도달하는 즉시, 즉 가능한 한 빨리 복구가 종료돼요. 온라인 백업에서 복원할 때 이는 백업을 받는 것이 종료된 지점을 의미해요.
:::info[Important]
오퍼레이터는 targetTime 또는 targetLSN을 지정하면 가장 가까운 백업을 검색할 수 있어요. 하지만 나머지 대상인 targetName, targetXID, targetImmediate에서는 이것이 불가능해요. 이 경우 backupID를 지정하는 것이 필수적이에요.
:::
이 예시는 targetName 기반 복구 대상을 사용해요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
[...]
bootstrap:
recovery:
source: origin
recoveryTarget:
backupID: 20220616T142236
targetName: 'restore_point_1'
[...]
각 recoveryTarget 구성에서 대상 중 하나만 선택할 수 있어요.
또한 특정 타임라인으로 복구를 강제하려면 targetTLI를 지정할 수 있어요.
기본적으로 이전 파라미터는 포함(inclusive)으로 간주되며, PostgreSQL의 동작과 일치하게 복구 대상 바로 뒤에서 중지해요.
exclusive 파라미터를 true로 설정하면 복구 대상 바로 앞에서 중지하는 제외(exclusive) 동작을 요청할 수 있어요. 다음 예시는 이 동작을 보여주며, 베이스 백업과 WAL 아카이브 모두에 Azure의 blob 컨테이너를 사용해요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-restore-pitr
spec:
instances: 3
storage:
size: 5Gi
bootstrap:
recovery:
source: origin
recoveryTarget:
backupID: 20220616T142236
targetName: "maintenance-activity"
exclusive: true
externalClusters:
- name: origin
plugin:
name: barman-cloud.cloudnative-pg.io
parameters:
barmanObjectName: cluster-example-backup
serverName: cluster-example
애플리케이션 데이터베이스 구성 (Configure the application database)
복구된 클러스터에 대해 추가 구성으로 애플리케이션 데이터베이스 이름과 자격 증명을 구성할 수 있어요. 애플리케이션 데이터베이스 자격 증명을 업데이트하려면 직접 비밀번호를 생성해 시크릿으로 저장하고 데이터베이스가 그 시크릿을 사용하도록 업데이트할 수 있어요. 또는 오퍼레이터가 무작위 보안 비밀번호로 시크릿을 생성해 사용하게 할 수도 있어요. 시크릿에 대한 자세한 내용은 빈 클러스터 부트스트랩을 참고해 주세요.
:::info[Important]
Cluster가 복구 모드에 있는 동안에는 카탈로그를 포함한 데이터베이스에 대한 어떤 변경도 허용되지 않아요. 이 제한에는 Cluster가 primary로 전환될 때까지 연기되는 모든 역할 오버라이드가 포함돼요. 이 단계 동안 사용자는 소스 클러스터에 정의된 그대로 유지돼요.
:::
다음 예시는 라이브 클러스터에서 부트스트랩한 후 소유자 app과 제공된 시크릿 app-secret에 저장된 비밀번호로 app 데이터베이스를 구성해요.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
[...]
spec:
bootstrap:
recovery:
database: app
owner: app
secret:
name: app-secret
[...]
위 구성으로 다음은 복구가 완료된 후에만 발생해요:
app데이터베이스가 없으면 생성돼요.app사용자가 없으면 생성돼요.app사용자가app데이터베이스의 소유자가 아니면 소유권이app사용자에게 부여돼요.owner값이 시크릿의username값과 일치하면 애플리케이션 사용자(여기서는app사용자)의 비밀번호가 시크릿의password값으로 업데이트돼요.
복구가 내부적으로 작동하는 방식 (How recovery works under the hood)
객체 스토리지에 업로드된 데이터를 사용해 기존 백업에서 새 클러스터를 부트스트랩할 수 있어요. 오퍼레이터는 barman-cloud-restore 도구(베이스 백업용)와 barman-cloud-wal-restore 도구(WAL 파일용, 요청 시 병렬 지원 포함)를 사용해 복구 프로세스를 오케스트레이션해요.
recovery 부트스트랩 방법에 대한 세부 내용과 지침은 백업에서 부트스트랩을 참고해 주세요.
:::info[Important]
PostgreSQL PITR이 어떻게 작동하는지 익숙하지 않다면, 복구 클러스터를 .spec.postgresql.parameters 측면에서 원래 클러스터와 동일하게 구성할 것을 권장해요. 새 클러스터가 복원된 후에는 원하는 대로 설정을 변경할 수 있어요.
:::
작동 방식은 오퍼레이터가 새 클러스터의 첫 번째 인스턴스에 init 컨테이너를 주입하고, init 컨테이너가 객체 스토리지에서 백업 복구를 시작하는 것이에요.
:::info[Important] 새 PVC에서의 베이스 백업 복사 시간은 백업 크기와 네트워크 및 스토리지 속도에 따라 달라져요. :::
베이스 백업 복구 프로세스가 완료되면 오퍼레이터는 복구 모드에서 Postgres 인스턴스를 시작해요. 이 단계에서 PostgreSQL은 실행 중이지만 연결을 수락할 수 없고, 파드는 liveness 프로브에 따라 정상으로 간주돼요. restore_command를 통해 PostgreSQL은 아카이브에서 WAL 파일을 가져오기 시작해요. maxParallel 옵션을 설정하고 병렬 WAL 복원 기능을 활성화하면 이 단계의 속도를 높일 수 있어요.
이 단계는 PostgreSQL이 대상에 도달할 때(WAL의 끝 또는 PITR의 경우 필요한 대상) 종료돼요. 선택적으로 PITR을 수행하기 위해 recoveryTarget을 지정할 수 있어요. 지정하지 않으면 복구는 기본 대상 타임라인(latest)의 가장 최근 사용 가능한 WAL까지 계속돼요.
복구가 완료되면 오퍼레이터는 필요한 슈퍼유저 비밀번호를 인스턴스에 설정해요. 새 primary 인스턴스는 평소처럼 시작하고, 나머지 인스턴스는 레플리카로 클러스터에 합류해요.
이 프로세스는 사용자에게 투명하며 파드에서 실행되는 인스턴스 매니저가 관리해요.
백업 섹션이 있는 클러스터로 복원 (Restoring into a Cluster with a Backup Section)
클러스터를 복원할 때 매니페스트에는 백업 객체 스토어 리소스를 가리키는 Barman Cloud plugin이 포함된 plugins 섹션이 있을 수 있어요. 이를 통해 새로 생성된 클러스터가 백업 정책이 구성되어 있다면 복구 직후 WAL 파일 아카이빙과 백업을 즉시 시작할 수 있어요.
같은 클러스터에서 백업과 복구에 동일한 ObjectStore 구성을 재사용하지 마세요. 꼭 해야 한다면, 각 클러스터가 고유한 serverName을 사용해 백업 또는 WAL 아카이브 데이터의 우발적 덮어쓰기를 방지하세요.
:::warning
CloudNativePG는 클러스터가 공유 스토리지 버킷의 기존 데이터를 덮어쓰는 것을 방지하는 안전 검사를 포함해요. 충돌이 감지되면 클러스터는 Setting up primary 상태로 유지되고 관련 파드는 오류로 실패해요. 파드 로그에 다음과 같이 표시돼요:
ERROR: WAL archive check failed for server recoveredCluster: Expected empty archive.
:::
:::info[Important]
복구된 클러스터에 cnpg.io/skipEmptyWalArchiveCheck 어노테이션을 enabled로 설정하면 이 안전 검사를 우회할 수 있어요. 하지만 PostgreSQL의 복구 프로세스에 매우 익숙하지 않다면 이는 강력히 권장되지 않아요. 검사를 잘못 건너뛰면 심각한 데이터 손실로 이어질 수 있어요. 주의해서, 그리고 전문가 시나리오에서만 사용하세요.
:::