CloudNativePG 클러스터
프라이머리 1개, 레플리카 1개 구성과 인스턴스별 PVC, Service, 비동기 복제 조회
#핵심 요약
실습에서는 CloudNativePG 오퍼레이터와 CRD를 설치한 뒤 Cluster 자원으로 PostgreSQL 인스턴스 2개를 선언함
구성은 Primary 1개, Replica 1개, 인스턴스마다 3Gi 저장소이며 역할별 접속 Service를 제공함
파드 Running 외에 DB의 복구 상태와 pg_stat_replication을 조회하며 실습 출력은 streaming, async임
#개념과 동작 원리
| 구성 | 역할 |
|---|---|
| 오퍼레이터 | PostgreSQL 설치, 복제, 프라이머리 역할과 장애 시 레플리카 승격을 관리하는 제어 프로그램 |
| CRD | Kubernetes에 PostgreSQL용 Cluster 같은 새 자원 종류를 추가하는 정의 |
spec.instances: 2 |
실습의 프라이머리 1개, 레플리카 1개 구성 선언 |
| PVC | 인스턴스별 영속 볼륨 요청이며 실습에서는 2개가 Bound, 각 3Gi, local-path로 표시 |
| 읽기, 쓰기 Service | 실습의 -rw 역할 접속이며 프라이머리를 대상으로 사용 |
| 읽기 전용 Service | 실습의 -ro 역할 접속이며 레플리카를 대상으로 사용 |
| 전체 인스턴스 Service | 실습의 -r 역할 접속이며 역할과 무관한 인스턴스 접속용 |
실습에서는 CloudNativePG를 PostgreSQL 스트리밍 복제를 사용하는 오퍼레이터로 설명하며 RAC의 공유 DB 파일 접근과 구분함
Oracle 대응은 Data Guard, Fast-Start Failover, 서비스 재배치의 역할 비교이며 동일 설정이나 구조라는 뜻이 아님
#필수 명령어 또는 SQL
#오퍼레이터 설치
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.1.yaml
kubectl get pods -n cnpg-system
kubectl get crd | grep cnpg결과 해석:
CloudNativePG 공식 설치 문서의 오퍼레이터 매니페스트 직접 적용 방식이며, 실습 시점 버전 1.30.1을 그대로 씀
오퍼레이터 파드 cnpg-controller-manager-...가 Running이고 clusters.postgresql.cnpg.io CRD가 보이면 kubectl get cluster로 PostgreSQL 클러스터를 다룰 수 있음
#구성 선언
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: <CLUSTER_NAME>
spec:
instances: 2
storage:
size: 3Gi
bootstrap:
initdb:
database: <DB_NAME>
owner: <USERNAME>결과 해석: YAML은 원하는 구성을 선언하는 입력이며 적용 결과가 아님
수업 자료에는 파일 적용 후 Cluster 생성 메시지와 initdb 작업 → 프라이머리 → join 작업 → 레플리카 생성 출력이 있음
설명은 3GB, YAML의 선언값은 3Gi로 표기가 다름imageName은 지정하지 않았으며 실습의 PostgreSQL 18.6 출력이 이후 설치 버전까지 고정하는 것은 아님
#오퍼레이터, 자원 조회
kubectl get pods -n cnpg-system
kubectl get crd | grep cnpg
kubectl get cluster <CLUSTER_NAME>
kubectl get pods -w
kubectl get pvc
kubectl get svc결과 해석: 수업 자료에는 오퍼레이터 파드 1/1 Running, Cluster의 인스턴스 2, 준비 2, healthy 상태와 현재 프라이머리, 인스턴스별 PVC 2개와 역할별 Service가 나옴-w의 반복 출력은 상태 관찰이며 실습에서는 확인 후 Ctrl+C로 관찰을 끝냄
#DB 역할과 복제 조회
SELECT version();
SELECT pg_is_in_recovery();
SELECT client_addr, state, sync_state FROM pg_stat_replication;결과 해석: 실습 프라이머리의 pg_is_in_recovery()는 f, 복제 조회에는 레플리카 1행의 streaming, async가 있음
별도 레플리카 조회에는 pg_is_in_recovery()=t와 이행한 주문 1,000,000건이 나옴
#접속 정보와 외부 통로, 데이터 이행
kubectl exec -it pgk8s-1 -- psql <TARGET_DB_NAME>
kubectl get secret pgk8s-app -o jsonpath='{.data.username}' | base64 -d; echo
kubectl get secret pgk8s-app -o jsonpath='{.data.password}' | base64 -d; echo
kubectl port-forward --address 0.0.0.0 svc/pgk8s-rw 5432:5432export PGCLIENTENCODING=UTF8
K8S_PW='<Secret에서 꺼낸 비밀번호>'
time PGPASSWORD=<PASSWORD> pg_dump -h 127.0.0.1 -U <USERNAME> -d <TARGET_DB_NAME> -n mig --no-owner \
| PGPASSWORD="$K8S_PW" psql -h <K8S_VM_IP> -p 5432 -U <USERNAME> -d <TARGET_DB_NAME> -v ON_ERROR_STOP=1결과 해석:
오퍼레이터는 앱 계정 비밀번호를 <클러스터>-app Secret에 만들어 두며, base64는 암호화가 아니라 인코딩이라 Secret 읽기 권한이 곧 비밀번호 열람 권한임
port-forward는 실행 중인 터미널이 열려 있는 동안만 VM의 5432를 pgk8s-rw Service(프라이머리)로 이어 주는 실습용 임시 통로임
소스의 mig 스키마를 pg_dump로 떠서 파이프로 바로 k8s 쪽 psql에 넣으며, --no-owner로 소유자 지정문을 빼고 ON_ERROR_STOP=1로 첫 오류에서 멈춤
이행 뒤 양쪽에서 고객 수, 주문 수, 주문 금액 합계를 같은 SQL로 비교하고, 레플리카(pgk8s-2)에서도 pg_is_in_recovery()와 주문 건수를 확인함
#실습 클러스터 정리
kubectl delete -f ~/k8s/pgk8s.yaml
kubectl get pods
kubectl get pvc결과 해석:
#사용 조건과 주의사항
async를 동기 복제로 해석하거나 해당 시점의 건수 일치를 모든 장애의 무손실 보장으로 확장하지 않음
실습에서는 Service에 port-forward를 연결해 외부 이행 통로로 사용하며 실행 중인 터미널이 필요하다고 설명함
이는 실습의 임시 실습 경로임
수업 자료의 이행, 검증 명령과 출력은 대상 주소와 스키마 이름(mig, <USERNAME>)이 서로 다른 곳이 있어 실제 환경 값으로 맞춰 씀