본문으로 건너뛰기
2.1 전체 아키텍처

2.1 전체 아키텍처

CloudNativePG의 구조는 한 문장으로 요약된다. 사람이 원하는 상태를 Cluster라는 선언(CRD)으로 적어 두면, Operator가 그 선언을 실제 Pod와 PVC로 만들어 내고 계속 그 상태로 유지한다. PostgreSQL 자체의 고가용성은 새로운 기술이 아니라, 십수 년간 운영에서 검증된 물리 복제(streaming replication, WAL shipping)를 그대로 쓴다. CloudNativePG가 하는 일은 그 복제 위에 Kubernetes 방식의 자동 운영을 얹는 것이다.

구성 요소와 관계

핵심 등장인물은 넷이다.

  • Operator — 클러스터 하나에 하나 떠 있으면서 모든 Cluster를 감시하는 컨트롤러
  • Cluster — “PostgreSQL 인스턴스를 몇 개, 어떤 스펙으로 원한다"를 적은 CRD(원하는 상태)
  • Pod(인스턴스) — 실제 postgres 프로세스가 도는 단위. .spec.instances 수만큼 생성
  • PVC — 각 Pod가 데이터를 저장하는 영구 볼륨. Pod와 1:1

이들의 관계는 위에서 아래로 흐른다.

    flowchart TD
  DBA["DBA"] -->|apply| CR["Cluster CRD<br/>(원하는 상태)"]
  OP["Operator"] -->|감시·조정| CR
  OP -->|생성·관리| P1["Pod primary"]
  OP -->|생성·관리| P2["Pod replica 1"]
  OP -->|생성·관리| P3["Pod replica 2"]
  P1 --> V1[("PVC 1")]
  P2 --> V2[("PVC 2")]
  P3 --> V3[("PVC 3")]
  P1 -->|streaming| P2
  P1 -->|streaming| P3

  classDef node fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  class DBA,CR,OP,P1,P2,P3 node
  

DBA는 Pod를 직접 만들지 않는다. Cluster에 원하는 상태만 적고, 나머지는 Operator가 채운다. Pod가 죽거나 스펙이 바뀌면 Operator가 그 차이를 감지해 현재 상태를 원하는 상태로 되돌린다. 이 되돌리는 과정을 조정 루프(reconciliation loop)라 부른다.

CloudNativePG는 스토리지 계층 복제가 아니라 애플리케이션 계층 복제를 쓴다. 즉 공유 디스크에 의존하지 않고, PostgreSQL이 스스로 WAL을 흘려보내 replica를 따라오게 한다. 그래서 각 Pod는 자기만의 로컬 볼륨(PVC)을 갖는 shared-nothing 구조가 된다.

primary와 replica

한 Cluster 안에는 쓰기를 받는 primary 하나와, primary를 실시간으로 따라오는 replica 여러 개가 있다. replica는 hot standby로 떠 있어 읽기 쿼리를 처리할 수 있고, primary에 문제가 생기면 그중 하나가 승격(promotion)되어 새 primary가 된다.

복제 방식은 세 가지다.

방식동작쓰임
asynchronous streamingprimary가 커밋을 기다리지 않고 WAL 전송기본. 성능 우선
synchronous streamingreplica 반영을 확인한 뒤 커밋 완료데이터 무손실 우선
WAL shipping파일 단위 WAL을 object store 경유 전달fallback·아카이브

synchronous를 쓰면 커밋 시점에 최소 한 replica가 데이터를 받았음이 보장되므로 데이터 손실(RPO)을 0에 가깝게 만들 수 있지만, replica가 느리면 primary 쓰기도 함께 느려진다. 성능과 내구성 사이의 선택이다.

서비스: 어디로 접속하는가

Pod는 죽고 새로 뜨면서 이름과 IP가 바뀐다. 그래서 애플리케이션은 특정 Pod가 아니라 Operator가 관리하는 서비스 이름으로 접속한다. Cluster 이름이 pg라면 다음 세 서비스가 생긴다.

  • pg-rw — 항상 현재 primary로 연결. 쓰기 트래픽
  • pg-ro — replica들로만 분산 연결. 읽기 전용 워크로드
  • pg-r — primary·replica 아무 인스턴스로나 연결. 읽기
    flowchart TD
  APP["애플리케이션"]
  RW["pg-rw 서비스"]
  RO["pg-ro 서비스"]
  APP -->|쓰기| RW
  APP -->|읽기 분산| RO
  RW --> PRI["primary"]
  RO --> R1["replica 1"]
  RO --> R2["replica 2"]
  PRI -->|streaming| R1
  PRI -->|streaming| R2

  classDef node fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  class APP,RW,RO,PRI,R1,R2 node
  

failover로 primary가 바뀌면 Operator가 pg-rw의 대상 엔드포인트를 새 primary로 자동으로 갱신한다. 애플리케이션은 접속 문자열을 바꿀 필요 없이 계속 pg-rw만 바라보면 된다.

배포 토폴로지

단일 Kubernetes 클러스터, 다중 AZ

권장 구성은 세 개 이상의 availability zone(AZ)에 노드를 나눠 배치하고, primary와 replica를 서로 다른 AZ의 노드에 두는 것이다. AZ 하나가 통째로 죽어도 다른 AZ의 replica가 살아 있으므로 Operator가 그쪽을 승격해 서비스를 잇는다.

    flowchart TD
  subgraph AZa["AZ a"]
    PRI["primary"]
  end
  subgraph AZb["AZ b"]
    R1["replica 1"]
  end
  subgraph AZc["AZ c"]
    R2["replica 2"]
  end
  PRI -->|streaming| R1
  PRI -->|streaming| R2

  classDef node fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  class PRI,R1,R2 node
  
Operator의 권한은 자신이 속한 Kubernetes 클러스터 하나로 한정된다. 즉 서로 다른 Kubernetes 클러스터 사이의 자동 failover는 하지 못한다. 리전 단위 재해 복구가 필요하면 아래 replica cluster 구성으로 넘어가야 한다.

프로덕션에서는 PostgreSQL 전용 워커 노드를 두는 것을 권장한다. 노드에 label과 taint를 걸어 다른 워크로드가 끼어들지 못하게 한다.

# postgres 전용 노드에 label 부여
kubectl label node <node> node-role.kubernetes.io/postgres=

# postgres 워크로드만 스케줄되도록 taint
kubectl taint node <node> node-role.kubernetes.io/postgres=:NoSchedule

노드는 AZ당 하나씩, 3의 배수로 두는 구성이 이상적이다.

다중 Kubernetes 클러스터 (replica cluster)

리전 장애까지 견디려면 별도의 Kubernetes 클러스터에 replica cluster를 둔다. replica cluster는 그 자체가 하나의 Cluster 리소스이며, 원본에서 WAL shipping이나 streaming replication으로 계속 복구(continuous recovery) 상태를 유지한다. 그 안에서 대표 역할을 하는 인스턴스를 designated primary라 부른다.

    flowchart TD
  subgraph K8sA["Kubernetes A (주)"]
    PRI["primary"]
    RA["replica"]
  end
  subgraph K8sB["Kubernetes B (DR)"]
    DP["designated primary"]
    RB["cascading standby"]
  end
  OBJ[("object store<br/>WAL archive")]
  PRI --> RA
  PRI -->|WAL archive| OBJ
  OBJ -->|restore| DP
  PRI -.->|streaming| DP
  DP --> RB

  classDef node fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
  class PRI,RA,DP,RB node
  
  • 계획된 전환(switchover) — 관리자가 원본 primary를 강등하고 designated primary를 승격. 큰 데이터를 다시 복제할 필요 없이 역할만 바꾼다.
  • 계획되지 않은 전환(failover) — 원본 Kubernetes 클러스터가 통째로 죽었을 때 replica cluster를 승격. 마지막으로 받지 못한 WAL만큼 데이터 손실이 생길 수 있다.

클러스터 사이의 이런 전환은 Operator가 자동으로 해 주지 않으므로, GitOps나 IaC로 사람이 통제한다.

정리

  • Cluster(원하는 상태) → Operator(조정) → Pod·PVC(실제 상태)로 흐른다
  • primary 하나 + replica 여럿, 복제는 streaming(async/sync)·WAL shipping
  • 접속은 Pod가 아니라 -rw·-ro·-r 서비스로. failover 시 -rw는 자동 갱신
  • 단일 클러스터 다중 AZ가 기본, 리전 재해 복구는 replica cluster
  • Operator의 자동화는 자기 Kubernetes 클러스터 안으로 한정

다음 절(2.2)에서는 Pod 안에서 postgres 프로세스를 실제로 지키는 인스턴스 매니저를 본다.