스토리지
스토리지 (Storage)
스토리지는 데이터베이스 워크로드에서 가장 중요한 구성 요소예요. 스토리지는 항상 사용 가능해야 하고, 확장 가능해야 하며, 잘 수행되어야 하고, 일관성과 내구성을 보장해야 해요. 가상 머신과 베어메탈 같은 전통적인 환경에 적용되던 기대와 요구사항이 쿠버네티스가 관리하는 컨테이너 환경에서도 동일하게 유효해요.
:::info[Important] 동적으로 프로비저닝되는 스토리지에 관해서는 쿠버네티스의 특수성이 있어요. 여기에는 storage classes, persistent volumes, Persistent Volume Claims (PVCs) 가 포함돼요. VM과 물리 서버에서 데이터베이스 워크로드용 스토리지에 대해 수년간 쌓아온 귀중한 지식 위에 이런 개념들을 소유해야 해요. :::
스토리지에 접근하는 방법은 크게 두 가지가 있어요:
- 네트워크 – 직접 또는 간접적으로. (쿠버네티스가 실행 중인 호스트에 로컬로 마운트된 NFS 볼륨을 생각해 보세요.)
- 로컬 – Pod가 실행 중인 노드에 직접 연결. 여기에는 쿠버네티스의 베어메탈 설치에 직접 연결된 디스크도 포함돼요.
쿠버네티스에서 가장 흔한 사용 패턴인 네트워크 스토리지는 기존 환경에서 겪을 수 있는 것과 같은 처리량과 지연 시간 문제를 제시해요. 이러한 문제는 공유 환경에서 더 두드러질 수 있는데, 여러 애플리케이션과의 I/O 경합이 성능 결과의 변동성을 증가시키기 때문이에요.
로컬 스토리지는 쉐어드 낫싱(shared-nothing) 아키텍처를 가능하게 해주며, 더 높고 예측 가능한 성능을 보장하므로 높은 트랜잭션 및 초대형 데이터베이스(VLDB) 워크로드에 더 적합해요.
:::warning CloudNativePG로 PostgreSQL 클러스터를 배포하기 전에, 사용하는 스토리지가 데이터베이스 워크로드에 권장되는 것인지 확인하세요. fio 같은 도구로 스토리지를 먼저 벤치마킹한 다음 pgbench로 데이터베이스를 벤치마킹해서 성능 기대치를 명확히 설정하는 것을 권장해요. :::
:::info
CloudNativePG는 데이터 영속성 관리에 StatefulSet을 사용하지 않아요. 오히려 PVC를 직접 관리해요. 더 알고 싶다면 Custom pod controller를 참조하세요.
:::
출처: 문서
본문
백업과 복구
CloudNativePG는 백업과 복구 둘 다에 볼륨 스냅샷을 지원하므로, 특히 매우 큰 데이터베이스를 관리한다면 스토리지 솔루션을 선택할 때 이 측면도 고려할 것을 권장해요.
:::info[Important] 스냅샷 기능을 제공하는 지원되는 container storage interface (CSI) drivers 목록은 쿠버네티스 문서를 참조하세요. :::
CloudNativePG 벤치마킹
프로덕션에 데이터베이스를 배포하기 전에 통제된 쿠버네티스 환경에서 CloudNativePG를 벤치마킹할 것을 권장해요. Benchmarking의 지침을 따르세요.
간단히 말해, 두 수준에서 운영할 것을 권장해요:
- fio로 기반 스토리지의 성능을 측정하고, 순차 읽기, 순차 쓰기, 랜덤 읽기, 랜덤 쓰기의 처리량 같은 데이터베이스 워크로드에 관련된 메트릭을 사용해요
- PostgreSQL에 기본으로 배포되는 벤치마킹 도구인 pgbench로 데이터베이스의 성능을 측정해요
:::info[Important] 데이터베이스를 프로덕션에 투입하기 전에 스토리지와 데이터베이스 성능을 모두 측정해야 해요. 이 결과는 계획 단계(예: 용량 계획)뿐만 아니라 프로덕션 수명 주기, 특히 이런 종류의 테스트를 실행할 시간이 없는 긴급 상황에서도 매우 귀중해요. 데이터베이스는 시간이 지남에 따라 바뀌고 진화하며, 데이터의 분포도 바뀌어 성능에 잠재적으로 영향을 미칠 수 있어요. 순차 읽기나 쓰기의 이론적 최대 처리량을 아는 것은 그런 상황에서 매우 유용해요. 특히 외부 워크로드의 영향으로 결과가 변하지 않는 쉐어드 낫싱 환경에서 더욱 그렇죠.
여러분의 시스템을 알아야 해요: 벤치마킹하세요.
:::
저장 데이터 암호화 (Encryption at rest)
CloudNativePG로 저장 데이터 암호화(encryption at rest)가 가능해요. 오퍼레이터는 이를 기본 스토리지 클래스에 위임해요. 이 중요한 보안 기능에 대한 정보는 스토리지 클래스를 참조하세요.
Persistent Volume Claim (PVC)
오퍼레이터는 각 PostgreSQL 인스턴스에 대해 PGDATA를 저장하기 위한 PVC를 만들어요. 그런 다음 각 Pod에 마운트해요.
또한 다음으로 클러스터 생성도 지원해요:
- Volume for WAL에 설명된 대로 PostgreSQL WAL을 저장하는 별도의 PVC
- Tablespaces에 설명된 대로 PostgreSQL 테이블스페이스용으로 예약된 추가 별도 볼륨
CloudNativePG에서 단일 PostgreSQL 인스턴스에 연결된 볼륨들은 PVC 그룹으로 정의돼요.
스토리지 클래스를 통한 구성
:::info[Important] CloudNativePG는 모든 스토리지 클래스와 상호 교환적으로 동작하도록 설계됐어요. 평소처럼 프로덕션에 배포하기 전에 통제된 환경에서 스토리지 클래스를 제대로 벤치마킹할 것을 권장해요. :::
PostgreSQL 클래스의 스토리지를 구성하는 가장 쉬운 방법은 다음 예시처럼 특정 크기의 스토리지를 요청하는 거예요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-storage-class
spec:
instances: 3
storage:
size: 1Gi
이전 구성을 사용하면 생성된 PVC는 기본 스토리지 클래스로 충족돼요. 대상 쿠버네티스 클러스터에 기본 스토리지 클래스가 없거나, 알려진 스토리지 클래스로 PVC가 충족되도록 하려면 커스텀 리소스에 설정할 수 있어요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-storage-class
spec:
instances: 3
storage:
storageClass: standard
size: 1Gi
PVC 템플릿을 통한 구성
생성된 PVC를 더 커스터마이즈하려면 커스텀 리소스 안에 PVC 템플릿을 제공할 수 있어요. 다음 예시처럼요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-pvc-template
spec:
instances: 3
storage:
pvcTemplate:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard
volumeMode: Filesystem
WAL용 볼륨 (Volume for WAL)
기본적으로 PostgreSQL은 모든 데이터를 소위 PGDATA(디렉토리)에 저장해요. PGDATA 안의 핵심 디렉토리 중 하나는 pg_wal인데, 데이터베이스에서 발생한 트랜잭션 변경의 로그를 세그먼트 파일 형태로 담아요. (pg_wal은 PostgreSQL에서 역사적으로 pg_xlog로 알려져 있어요.)
:::info
보통 각 세그먼트 크기는 16MB이지만, walSegmentSize 옵션으로 크기를 구성할 수 있어요. 이 옵션은 Bootstrap an empty cluster에서 설명한 대로 클러스터 초기화 시 적용돼요.
:::
대부분의 경우 pg_wal이 PGDATA와 같은 볼륨에 있는 것은 괜찮아요. 하지만 WAL을 별도 볼륨에 저장하면 몇 가지 이점이 있어요:
-
I/O 성능 –
PGDATA와 다른 스토리지에 WAL 파일을 저장하면 PostgreSQL은 WAL 작업(보통 순차 쓰기)과 데이터 파일(예: 테이블과 인덱스)에 대해 병렬 I/O를 활용할 수 있어서 수직 확장성을 향상시켜요. -
더 높은 신뢰성 – WAL 파일에 전용 디스크 공간을 예약하면
PGDATA볼륨의 공간이 고갈되는 것이 WAL 쓰기를 절대 방해하지 않도록 보장할 수 있어요. 이 동작은 PostgreSQL 프라이머리가 올바르게 종료되도록 보장해요. -
세밀한 제어 –
PGDATA와pg_wal둘 다에 전용되는 공간의 양을 정의하고, WAL 구성과 체크포인트를 미세 조정하며, 비용 최적화를 위해 다른 스토리지 클래스를 사용할 수도 있어요. -
더 나은 I/O 모니터링 –
PGDATA와pg_wal둘 다의 부하와 디스크 사용량을 지속적으로 모니터링할 수 있어요. 예를 들어PGDATA가 크기 조정이 필요할 때 알려주는 알림도 설정할 수 있어요.
:::note[Write-Ahead Log (WAL)] 더 자세한 내용은 PostgreSQL 문서의 Reliability and the Write-Ahead Log를 참조하세요. :::
.spec.walStorage 옵션으로 WAL용 별도 볼륨을 추가할 수 있어요. 이 옵션은 storage 필드에 대해 설명된 것과 동일한 규칙을 따르며 전용 PVC를 프로비저닝해요. 예를 들어:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: separate-pgwal-volume
spec:
instances: 3
storage:
size: 1Gi
walStorage:
size: 1Gi
:::info[Important]
walStorage 제거는 지원되지 않아요. 일단 추가되면 WAL용 별도 볼륨은 기존 Postgres 클러스터에서 제거할 수 없어요.
:::
테이블스페이스용 볼륨
CloudNativePG는 선언적 테이블스페이스를 지원해요. 각각 단일 PostgreSQL 테이블스페이스에 전용되는 볼륨을 하나 이상 추가할 수 있어요. 자세한 내용은 Tablespaces를 참조하세요.
볼륨 확장 (Volume expansion)
쿠버네티스는 기본적으로 활성화된 PVC 확장을 허용하는 API를 노출해요. 하지만 이는 기본 StorageClass가 지원해야 해요.
특정 StorageClass가 볼륨 확장을 지원하는지 확인하려면 스토리지 클래스의 allowVolumeExpansion 필드를 읽을 수 있어요:
$ kubectl get storageclass -o jsonpath='{$.allowVolumeExpansion}' premium-storage
true
쿠버네티스 볼륨 확장 기능 사용하기
스토리지 클래스가 볼륨 확장을 지원한다면 Cluster의 크기 요구사항을 바꿀 수 있고, 오퍼레이터가 변경을 모든 PVC에 적용해요.
StorageClass가 온라인 볼륨 크기 조정을 지원하면 변경이 즉시 Pod에 적용돼요. 기본 스토리지 클래스가 그걸 지원하지 않는다면 크기 조정을 트리거하려면 Pod를 삭제해야 해요.
진행하는 가장 좋은 방법은 레플리카부터 시작해서 각 Pod가 다시 올라올 때까지 기다리며 한 번에 하나씩 삭제하는 거예요.
스토리지 재생성 (Re-creating storage)
스토리지 클래스가 볼륨 확장을 지원하지 않는다면, 다른 PVC에서 클러스터를 재생성할 수 있어요. 확장된 스토리지로 새 PVC를 할당하고 데이터베이스를 그곳으로 이동해요. 이 작업은 클러스터에 노드가 둘 이상 있을 때만 가능해요.
그렇게 하는 동안 다음 예시처럼 resizeInUseVolumes 플래그를 비활성화해서 오퍼레이터가 기존 PVC를 바꾸지 못하게 해야 해요:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgresql-pvc-template
spec:
instances: 3
storage:
storageClass: standard
size: 1Gi
resizeInUseVolumes: False
전체 클러스터를 다른 스토리지 영역으로 이동하려면 모든 PVC와 모든 Pod를 다시 만들어야 해요. 다음 예시처럼 세 개의 레플리카가 있는 클러스터가 있다고 해볼게요:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
cluster-example-1 1/1 Running 0 2m37s
cluster-example-2 1/1 Running 0 2m22s
cluster-example-3 1/1 Running 0 2m10s
다른 PVC를 사용해 클러스터를 다시 만들려면 클러스터 정의를 편집해 resizeInUseVolumes를 비활성화할 수 있어요. 그런 다음 다른 PVC에서 각 인스턴스를 다시 만들어요.
예를 들어 cluster-example-3의 스토리지를 다시 만들어 보세요:
$ kubectl delete pvc/cluster-example-3 pod/cluster-example-3
:::info[Important]
전용 WAL 볼륨을 만들었다면 이 과정에서 두 PVC를 모두 삭제해야 해요. WAL 볼륨 PVC를 다시 생성하려는 경우에도 같은 절차가 적용돼요. .spec.walStorage 섹션에 대해 resizeInUseVolumes를 비활성화해서 이렇게 할 수 있어요.
:::
예를 들어 WAL 스토리지 전용 PVC가 있다면:
$ kubectl delete pvc/cluster-example-3 pvc/cluster-example-3-wal pod/cluster-example-3
그렇게 하면 오퍼레이터가 크기 조정된 PVC로 다른 레플리카를 만드는 것을 조율해요:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
cluster-example-1 1/1 Running 0 5m58s
cluster-example-2 1/1 Running 0 5m43s
cluster-example-3-join-v2 0/1 Completed 0 17s
cluster-example-3 1/1 Running 0 10s
볼륨 축소 (Volume reduction)
쿠버네티스는 PVC를 줄이는 API를 제공하지 않으며, CloudNativePG의 validating webhook은 .spec.storage.size, .spec.walStorage.size 또는 .spec.tablespaces의 어떤 테이블스페이스 스토리지 크기를 줄이려는 시도를 거부해요. 여전히 클러스터의 스토리지를 줄일 수는 있지만, 아래 설명된 것처럼 더 작은 볼륨으로 각 인스턴스를 다시 만들어야만 해요.
:::warning CloudNativePG는 자동 볼륨 축소를 지원하지 않아요. 잘못 수행하면 데이터 손실로 이어질 수 있는 민감한 작업이기 때문이에요. 현재는 아래 설명된 감독(supervised) 절차를 통해 수동으로만 수행할 수 있어요. 이 절차는 validating webhook을 일시적으로 비활성화해야 해요. 검증이 비활성화된 동안 오퍼레이터는 보통 거부되는 스펙 변경(안전하지 않거나 파괴적인 것 포함)을 받아들여요. 주의해서 진행하고 위험을 감수하되, 가능한 한 빨리 검증을 다시 활성화하세요.
시작하기 전에 클러스터의 현재 데이터, WAL, 어떤 테이블스페이스 데이터라도 새롭고 작은 크기에 편안하게 맞는지 확인하세요. 맞지 않으면 더 작은 볼륨으로 재생성된 인스턴스가 재합류에 실패하거나 빠르게 공간이 부족해질 수 있어요. :::
영구 볼륨의 크기를 줄이려면:
-
Cluster에cnpg.io/validation: disabled주석을 설정해서 validating webhook을 비활성화하고,.spec.storage.size(그리고 있다면.spec.walStorage.size또는.spec.tablespaces의 테이블스페이스 스토리지 크기)를 새롭고 작은 값으로 설정하며, 롤아웃 동안 여유 인스턴스를 제공하기 위해.spec.instances를 1만큼 늘려요. -
cnpg.io/validation주석을 제거(또는enabled로 설정)해서 검증을 다시 활성화해요. 새롭고 작은 크기는 이제 스펙에 저장되며, 이 시점부터 오퍼레이터가 재생성하는 모든 인스턴스에 적용돼요. 기존 인스턴스는 현재 볼륨을 유지해요; 각 인스턴스에 대해 해당 인스턴스가 재생성될 때까지 오퍼레이터는 정보성cannot decrease storage requirement메시지를 로그로 기록해요. 이것은 예상된 것이며 무해해요. -
여전히 이전 크기의 볼륨을 가진 스탠바이 하나를 파괴해요. 오퍼레이터는 새롭고 작은 볼륨에서 대체 인스턴스를 프로비저닝해요 — 인스턴스 시리얼이 재활용되므로 파괴한 이름을 재사용해요:
kubectl-cnpg destroy CLUSTER INSTANCE -
오퍼레이터가 대체 인스턴스를 만들고 정상이 될 때까지 기다려요.
-
여전히 이전 크기 볼륨을 가진 나머지 모든 스탠바이에 대해 3단계와 4단계를 반복해요.
-
새로 생성된 스탠바이 중 하나를 승격해서 여전히 이전 크기 볼륨을 가진 현재 프라이머리가 스탠바이로 강등되도록 해요:
kubectl-cnpg promote CLUSTER INSTANCE -
이전 프라이머리(이제 이전 크기 볼륨을 가진 스탠바이)를 파괴해서 오퍼레이터가 새롭고 작은 볼륨에서 대체품을 프로비저닝하도록 하고, 모든 인스턴스가 정상이 될 때까지 기다려요.
-
.spec.instances를 원래 값으로 다시 줄여요.
영구 볼륨의 정적 프로비저닝
CloudNativePG는 동적 볼륨 프로비저닝과 함께 동작하도록 설계됐어요. 이 기능은 스토리지 클래스와 PVC 템플릿을 통해 사용자가 요청할 때 스토리지 볼륨을 온디맨드로 만들 수 있게 해줘요. Re-creating storage를 참조하세요.
하지만 어떤 경우에는 쿠버네티스 관리자가 스토리지 볼륨을 수동으로 만들고 쿠버네티스 클러스터 안에서의 표현을 위해 관련 PersistentVolume 객체를 만드는 것을 선호해요. 이것은 볼륨의 *사전 프로비저닝(pre-provisioning)*이라고도 알려져 있어요.
:::info[Important] 볼륨 사전 프로비저닝을 피할 것을 권장해요. 오퍼레이터의 고가용성과 자가 치유 능력에 영향을 미치기 때문이에요. CloudNativePG가 구축된 완전한 선언적 모델을 깨뜨려요. :::
CloudNativePG에서 사전 프로비저닝된 볼륨을 사용하려면:
- 쿠버네티스 밖에서 볼륨을 수동으로 만들어요.
- 실제 CSI 드라이버가 요구하는 올바른 파라미터(즉
volumeHandle,fsType,storageClassName등)로 이 볼륨에 일치하는PersistentVolume객체를 만들어요. - 각 스토리지 섹션에 대해 쿠버네티스가
PersistentVolume과 매칭하고 CloudNativePG가 필요한PersistentVolumeClaim을 만들 수 있게 돕는 일관된pvcTemplate섹션을 사용해 PostgresCluster를 만들어요.
:::warning
정적 프로비저닝에서는 사전 프로비저닝된 볼륨이 존재하는 곳에 Postgres Pod가 쿠버네티스에 의해 올바르게 스케줄링되도록 보장하는 것이 여러분의 책임이에요. (스케줄링 구성은 클러스터의 affinity 규칙에 기반해요.) 클러스터를 배포한 후 Pending 상태에 갇힌 Pod가 없는지 확인하세요. 그 상태가 지속되면 왜 그런 일이 일어나는지 조사해요.
:::
블록 스토리지 고려 사항 (Ceph/Longhorn)
Longhorn과 Ceph 같은 쿠버네티스의 대부분의 블록 스토리지 솔루션은 복원력을 높이기 위해 볼륨의 여러 레플리카를 가질 것을 권장해요. 이 접근 방식은 내장 복원력이 없는 워크로드에 잘 맞아요.
하지만 CloudNativePG는 "Synchronizing the state"에서 설명한 대로 이러한 복원력을 인스턴스 수와 그에 연결된 영구 볼륨을 통해 Postgres Cluster에 직접 통합해요.
결과적으로 스토리지 수준에서 추가 레플리카를 정의하면 쓰기 증폭(write amplification)이 생겨 불필요하게 디스크 I/O와 공간 사용량이 증가할 수 있어요.
CloudNativePG 사용 시 블록 스토리지 수준의 레플리카 수를 1로 줄이고, 전체 Cluster 리소스에 대해 스토리지 수준에서 단일 장애 지점(SPoF)이 존재하지 않도록 보장하는 것을 고려하세요. 이는 일반적으로 더 넓은 shared-nothing 아키텍처 원칙에 맞춰, 단일 스토리지 호스트 — 그리고 궁극적으로 물리 디스크 — 가 같은 Cluster의 서로 다른 인스턴스의 블록을 호스팅하지 않도록 보장하는 것을 의미해요.
Longhorn에서는 커스텀 스토리지 클래스를 만들 때 strict-local 데이터 로컬리티를 활성화해서 이 위험을 완화할 수 있어요. strict-local 데이터 로컬리티로 볼륨을 만드는 자세한 지침은 여기에서 볼 수 있어요. 이 설정은 Pod의 데이터 볼륨이 Pod 자신과 같은 노드에 있도록 보장해요.
또한 Postgres Cluster는 pod anti-affinity 규칙을 갖추어 오퍼레이터가 서로 다른 노드에 Pod를 배포하고 Longhorn이 해당 호스트에 데이터 볼륨을 배치하도록 해야 해요. 필요하다면 볼륨 레플리카 수를 일시적으로 2로 설정한 다음 다시 줄이고, 이전 레플리카를 제거해서 Longhorn에서 볼륨을 수동으로 재배치할 수 있어요. 호스트가 손상되면 cnpg 플러그인으로 destroy를 사용해 해당 인스턴스를 파괴할 수 있어요. 그러면 CloudNativePG가 다른 호스트에서 인스턴스를 다시 만들고 데이터를 복제해요.
Ceph에서는 CRUSH 규칙으로 구성할 수 있어요. CRUSH 규칙 구성 문서는 여기에서 볼 수 있어요. 이러한 규칙은 노드당 Pod당 볼륨 하나를 보장하는 것을 목표로 해요. 볼륨을 다른 풀로 가져와서 재배치할 수도 있어요.