본문으로 건너뛰기

5.1 Storage

PostgreSQL은 상태를 가진 워크로드다. Pod가 재시작되거나 다른 노드로 옮겨가도 데이터는 그대로 살아 있어야 한다. Kubernetes에서 이 영속 스토리지를 표현하는 리소스가 PersistentVolumeClaim(PVC)이다. CloudNativePG는 인스턴스마다 PVC를 하나씩(또는 WAL·tablespace를 분리하면 여러 개) 만들어 PostgreSQL의 데이터 디렉토리 PGDATA를 담는다.

CNPG가 StatefulSet을 쓰지 않고 각 인스턴스를 개별 Pod와 PVC로 직접 관리한다는 점은 앞서 짚었다. 스토리지 관점에서 이 선택의 결과가 여기서 드러난다. Operator가 PVC의 생성·확장·재부착을 직접 통제하므로, failover나 노드 이동 시에도 어느 볼륨을 어느 인스턴스에 붙일지 세밀하게 제어할 수 있다.

PVC와 StorageClass

.spec.storage에 크기와 StorageClass만 지정하면 나머지는 Operator가 처리한다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: postgresql-storage-class
spec:
  instances: 3
  storage:
    storageClass: standard
    size: 1Gi

StorageClass는 “어떤 종류의 볼륨을 어떻게 프로비저닝할지"를 정의하는 Kubernetes 리소스다. 클라우드라면 gp3·premium SSD 같은 디스크 타입, 온프레미스라면 Ceph·Longhorn 같은 스토리지 백엔드에 대응한다. storageClass를 생략하면 클러스터의 기본 StorageClass가 쓰인다.

    flowchart TD
    CL[Cluster 리소스] --> OP[CNPG Operator]
    OP --> PVC[PVC 생성]
    PVC --> SC[StorageClass]
    SC --> PROV[프로비저너]
    PROV --> PV[(PersistentVolume)]
    PV --- POD[PostgreSQL Pod]
  

더 세밀한 제어가 필요하면 pvcTemplate로 PVC 전체를 직접 기술한다. accessMode나 volumeMode까지 지정해야 하는 경우다.

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을 별도 볼륨으로 분리

WAL(Write-Ahead Log)은 모든 변경을 먼저 기록하는 순차 쓰기 로그다. 데이터 파일에 대한 랜덤 쓰기와 WAL의 순차 쓰기가 같은 볼륨에서 경합하면 I/O 특성이 서로 다른 두 작업이 서로를 방해한다. walStorage를 선언하면 WAL을 전용 PVC로 떼어낸다.

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: separate-pgwal-volume
spec:
  instances: 3
  storage:
    size: 1Gi
  walStorage:
    size: 1Gi

WAL을 분리하면 얻는 것은 다음과 같다.

  • 성능: 데이터와 WAL의 I/O를 병렬로 처리해 경합을 줄인다.
  • 안정성: PGDATA 볼륨이 가득 차도 WAL 쓰기가 막히지 않는다. 반대 방향도 마찬가지다.
  • 운영 유연성: WAL에만 더 빠른(또는 다른 크기의) StorageClass를 붙일 수 있다.
  • 모니터링: WAL의 부하를 별도 볼륨 단위로 추적한다.
    flowchart TD
    POD[PostgreSQL Pod] --> DATA[(PVC PGDATA)]
    POD --> WAL[(PVC walStorage)]
    DATA --> SC1[StorageClass 표준]
    WAL --> SC2[StorageClass 고속]
  
한번 추가한 walStorage는 제거할 수 없다. 기존 클러스터에서 WAL 전용 볼륨을 도로 없애는 동작은 지원되지 않으므로, 분리 여부는 처음 설계할 때 결정한다.

볼륨 확장

데이터가 늘어 볼륨을 키워야 할 때, StorageClass가 확장을 허용하면 크기만 바꾸면 된다. 먼저 해당 StorageClass가 allowVolumeExpansion을 지원하는지 확인한다.

kubectl get storageclass premium-storage \
  -o jsonpath='{.allowVolumeExpansion}'

지원한다면 .spec.storage.size를 키우기만 하면 된다.

StorageClass가 온라인 리사이즈를 지원하면 크기 변경이 즉시 반영된다. 별도 조치가 필요 없다.
크기는 늘릴 수만 있다. Kubernetes 자체가 PVC 축소 API를 제공하지 않으며, CNPG도 크기를 줄이는 요청은 거부한다.

볼륨 축소

볼륨을 줄이는 것은 자동화된 경로가 없고 잘못하면 데이터가 사라질 수 있는 예민한 작업이다. CNPG는 이를 자동으로 지원하지 않는다. 굳이 줄여야 한다면 다음과 같이 인스턴스를 하나씩 교체하는 우회 절차를 따른다.

### 검증 웹훅 비활성화 `cnpg.io/validation: disabled` 애노테이션으로 크기 축소를 막는 validating webhook을 잠시 끈다. ### 크기 축소 + 인스턴스 증설 목표 크기를 줄이고 동시에 `instances`를 하나 늘려, 작은 볼륨을 가진 새 standby를 만든다. ### 검증 재활성화 축소가 반영되면 웹훅을 다시 켠다. ### 예전 볼륨 standby 교체 큰 볼륨을 쓰는 standby를 하나씩 파기해 새 볼륨으로 재생성한다. 새 standby 하나를 승격해 기존 primary를 강등하고, 옛 primary도 파기한다. 마지막에 `instances`를 원래 수로 되돌린다.
볼륨 축소는 데이터 손실 위험이 큰 작업이다. 운영 환경에서 진행하기 전 반드시 별도 환경에서 절차를 검증하고, 최신 백업을 확보한 상태에서 수행한다.

스토리지 선택 시 유의점

  • 사전 벤치마크: 운영에 투입하기 전 fio로 스토리지를, pgbench로 데이터베이스 성능을 반드시 측정한다. 스토리지와 DB 성능을 미리 재 보는 일이 가장 중요하다.
  • 암호화(encryption at rest): CNPG는 저장 데이터 암호화를 StorageClass 설정에 위임한다. 암호화가 필요하면 StorageClass 수준에서 구성한다.
  • 정적 프로비저닝 지양: 볼륨을 미리 수동으로 붙여 두는 static provisioning은 high availability와 선언형 모델을 해치므로 권장하지 않는다.
  • 블록 스토리지(Ceph·Longhorn): 스토리지 계층의 복제본 수를 1로 낮춰 write amplification을 줄인다. 복제는 CNPG의 다중 인스턴스 구성이 담당한다.

다음 절에서는 이 스토리지 위에서 도는 Pod에 CPU·메모리를 어떻게 할당하고, QoS class로 어떻게 안정성을 확보하는지 다룬다.