콘텐츠로 이동
Study Note온프렘 쿠버네티스

7. 데이터베이스 — CloudNativePG

오퍼레이터는 “운영 절차를 코드로 옮긴 것” 이다 — 페일오버·백업·업그레이드가 그 절차다

이 장에서 처음 나오는 말6개
CNPGCloudNativePG
쿠버네티스에서 PostgreSQL을 운영하는 오퍼레이터. Cluster CRD 하나로 복제·페일오버·백업·업그레이드를 선언한다. 현재 온프렘의 사실상 기본값이다.
프라이머리 · 레플리카Primary · Replica
쓰기를 받는 인스턴스와, 그 변경을 따라 복제하는 인스턴스. 프라이머리가 죽으면 레플리카 하나가 승격(promote)된다.
WALWrite-Ahead Log
데이터 파일을 고치기 전에 먼저 기록하는 변경 로그. 복제도 시점 복구도 전부 이 로그로 이뤄진다 — PostgreSQL의 심장이다.
PITRPoint-In-Time Recovery, 시점 복구
"어제 오후 3시 상태로" 되돌리는 복구. 베이스 백업 + 그 이후의 WAL이 있어야 가능하다.
페일오버 · 스위치오버Failover · Switchover
프라이머리가 죽어서 넘어가는 것과, 계획적으로 넘기는 것. 후자는 업그레이드 때 쓴다.
동기 · 비동기 복제Synchronous · Asynchronous
커밋을 레플리카가 받을 때까지 기다리는지. 동기는 데이터를 안 잃지만 느리고, 레플리카가 죽으면 쓰기가 멈출 수 있다.

문제 — StatefulSet으로 띄우면 그때부터가 일이다

섹션 제목: “문제 — StatefulSet으로 띄우면 그때부터가 일이다”

Postgres를 StatefulSet + PVC로 띄우는 건 어렵지 않다. 30줄이면 뜬다. 문제는 뜬 다음이다.

상황StatefulSet 직접클라우드 RDS에서는
파드가 있던 노드가 죽었다사람이 확인하고 옮긴다. 볼륨이 로컬이면 못 옮긴다자동 페일오버
프라이머리가 응답이 없다레플리카가 있어도 아무 일도 안 일어난다승격 후 엔드포인트 전환
어제 오후 3시로 되돌려야 한다pg_dump가 어제 새벽 것뿐이라 못 한다시점 복구
마이너 버전을 올려야 한다순서를 손으로 — 잘못하면 복제가 깨진다클릭
앱이 읽기 부하를 나누고 싶다레플리카 주소를 앱이 직접 알아야 한다읽기 엔드포인트 제공

해법 — 오퍼레이터가 절차를 대신한다

섹션 제목: “해법 — 오퍼레이터가 절차를 대신한다”

CloudNativePG는 Cluster 리소스 하나로 그 절차를 전부 선언한다.

Cluster CRD를 받은 CNPG 오퍼레이터가 primary와 replica 둘을 만들고, primary가 WAL 스트리밍 복제와 오브젝트 스토리지 백업을 하며 이상 시 replica가 승격되는 구조

오퍼레이터가 만들어 주는 서비스 셋이 특히 중요하다 — 앱은 파드 이름을 몰라도 된다.

서비스가리키는 곳앱에서
<이름>-rw현재 프라이머리쓰기 연결은 항상 여기. 페일오버해도 주소가 안 바뀐다
<이름>-ro레플리카들읽기 전용 부하 분산
<이름>-r전부(프라이머리 포함)아무 데나 읽어도 될 때
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: platform-pg
namespace: database
spec:
instances: 3 # 프라이머리 1 + 레플리카 2
imageName: ghcr.io/cloudnative-pg/postgresql:17.5 # 태그를 고정한다
storage:
size: 200Gi
storageClass: db-retain # 2장에서 만든 Retain SC
walStorage: # WAL을 데이터와 다른 볼륨에
size: 50Gi
storageClass: db-retain
postgresql:
parameters:
max_connections: "300"
shared_buffers: "2GB"
# 상태 노드에만 뜨게 (2장)
affinity:
nodeSelector:
node-role: data
tolerations:
- key: dedicated
operator: Equal
value: data
effect: NoSchedule
enablePodAntiAffinity: true # 세 인스턴스를 다른 노드에
topologyKey: kubernetes.io/hostname
monitoring:
enablePodMonitor: true # Prometheus가 긁어 간다 (8장)
backup:
barmanObjectStore:
destinationPath: s3://pg-backups/platform-pg
endpointURL: https://s3.example.internal # 6장의 그 엔드포인트
s3Credentials:
accessKeyId:
name: pg-backup-s3
key: ACCESS_KEY_ID
secretAccessKey:
name: pg-backup-s3
key: SECRET_ACCESS_KEY
wal:
compression: gzip
data:
compression: gzip
retentionPolicy: "30d"
모드커밋이 언제 성공하나잃을 수 있는 것쓸 자리
비동기(기본)프라이머리 디스크에 쓰면페일오버 시 마지막 몇 트랜잭션대부분. 관측·내부 도구 DB
동기레플리카가 받았다고 답하면없음(설정한 만큼)결제·주문처럼 잃으면 안 되는 데이터
spec:
postgresql:
synchronous:
method: any
number: 1 # 레플리카 1대가 받으면 커밋 성공

백업과 복구 — 여기가 오퍼레이터를 쓰는 진짜 이유

섹션 제목: “백업과 복구 — 여기가 오퍼레이터를 쓰는 진짜 이유”
주기적 베이스 백업과 연속 WAL 아카이브를 오브젝트 스토리지에 두고, 새 Cluster의 bootstrap.recovery가 둘을 읽어 targetTime 시점의 DB로 복구하는 흐름
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: platform-pg-nightly
namespace: database
spec:
schedule: "0 30 2 * * *" # 초 필드가 있는 6자리 cron (매일 02:30)
backupOwnerReference: self
cluster:
name: platform-pg

운영 — plugin이 대부분을 해 준다

섹션 제목: “운영 — plugin이 대부분을 해 준다”
터미널 창
# 설치: kubectl krew install cnpg
kubectl cnpg status platform-pg -n database # 프라이머리·복제 지연·백업 상태 한 화면
kubectl cnpg status platform-pg -n database --verbose
kubectl cnpg promote platform-pg platform-pg-2 -n database # 계획된 스위치오버
kubectl cnpg backup platform-pg -n database # 즉시 백업
kubectl cnpg restart platform-pg -n database # 롤링 재시작
# 접속 (진단용)
kubectl -n database exec -it platform-pg-1 -- psql -U postgres -c '\l'
  1. 오퍼레이터 업그레이드 — 릴리스 노트를 먼저 본다. 1.29.1에서 심각도 높은 CVE가 수정된 전례가 있으니 오퍼레이터 자체의 보안 릴리스는 미루지 않는다.

  2. Postgres 마이너 버전 — imageName 태그만 바꾸면 오퍼레이터가 롤링으로 처리한다. 레플리카 먼저, 마지막에 스위치오버 후 옛 프라이머리.

  3. Postgres 메이저 버전 — 별도 절차다. 다운타임이 있거나 논리 복제로 옮긴다. 반드시 복구본으로 먼저 리허설한다.

  4. 각 단계 뒤에 kubectl cnpg status로 복제 지연이 0으로 돌아오는지 확인한다.

CNPG는 앱만을 위한 게 아니다. 플랫폼 자신이 쓴다.

사용자무엇을 넣나없으면
Keycloak사용자 · 세션 · client 설정SSO가 통째로 멈춘다 (Keycloak 배포 설계)
Grafana대시보드 · 사용자 · 알림 규칙SQLite면 HA가 불가능하다 (관측 덱 5장)
사내 앱업무 데이터
터미널 창
# ① 클러스터 상태 (Primary / Replication lag / Backup 세 줄만 봐도 대부분 판별된다)
kubectl cnpg status platform-pg -n database
# ② 백업이 실제로 올라가 있나 — 오브젝트 스토리지 쪽에서 확인한다
mc ls store/pg-backups/platform-pg/ --recursive | tail
# ③ WAL 아카이브가 밀리지 않나 (밀리면 디스크가 찬다)
kubectl -n database exec -it platform-pg-1 -- \
psql -U postgres -c "SELECT * FROM pg_stat_archiver;"
# ④ 복제 지연
kubectl -n database exec -it platform-pg-1 -- \
psql -U postgres -c "SELECT application_name, state, sync_state, \
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes FROM pg_stat_replication;"
증상흔한 원인확인
파드가 Pending스토리지 클래스 문제 · anti-affinity로 놓을 노드가 없음describe pod의 Events (2장)
볼륨이 계속 차오름WAL 아카이브 실패pg_stat_archiver의 last_failed_time
백업 실패S3 자격증명 · 엔드포인트 · path-style오퍼레이터 로그, 6장 함정 넷
페일오버가 안 일어남인스턴스가 1개 · 레플리카가 이미 비정상kubectl cnpg status
쓰기가 전부 멈춤동기 복제 number가 너무 높다synchronous 설정
복구가 특정 시점 이전으로 안 감WAL 보존 기간이 짧다retentionPolicy와 실제 버킷 내용
  • replicas를 늘려도 복제 DB가 되지 않는다 — 승격·복제·백업은 오퍼레이터의 일이다
  • CNPG는 Cluster 하나로 선언하고, -rw · -ro 서비스가 페일오버를 앱에서 감춰 준다
  • 백업은 베이스 백업 + WAL 아카이브 → 오브젝트 스토리지. 이 둘이 있어야 PITR이 된다
  • WAL 아카이브 실패는 즉시 알림 대상이다 — 방치하면 디스크가 차서 DB가 멈춘다
  • 동기 복제는 number를 레플리카 수보다 작게. 아니면 한 대 장애에 쓰기가 멈춘다
  • 복구는 리허설한 것만 된다. 분기 1회 새 네임스페이스에 복구본을 띄운다