본문 바로가기

Database

[DOIK 스터디 2기] CloudNativePG (PostgreSQL 오퍼레이터) 알아보기 및 간단한 실습

반응형

DOIK 스터디 2기 - 3번째 글로 PostgreSQL 오퍼레이터에 대해서 정리해본다.

 

공부하는 내용을 정리하는 글이라 부정확할 수 있음 주의

 

 

Kubernetes 상에서 Database를 운영하기 위해서는 Operator의 힘을 빌리면 많은 도움이 된다.

 

CloudNativePG는 Kubernetes 환경에서 PostgreSQL을 손쉽게 관리할 수 있도록 도와주는 Operator이다.

 

Operator 성숙도 레벨 모델에서 볼 수 있듯이, CloudNativePG는 Level 5이며 이는 제일 높은 성숙도를 자랑한다.

 

https://cloudnative-pg.io/documentation/current/operator_capability_levels/

 

 

CloudNativePG 주요 특징으로는 다음과 같다.

- 고가용성을 위해 외부 툴 없이 Kubernetes AP와 직접 통합
- Primary 인스턴스가 장애 발생 시 자동 페일오버 지원
- Replica 인스턴스 생성 자동화
- 읽기, 쓰기, 읽기-쓰기 총 3개의 서비스 역할을 제공
- PostgreSQL 설정을 선언적으로 관리 (pgaudit, auto_explain, pg_stat_statements, pg_failover_slots)
- PostgreSQL 역할, 유저, 그룹 선언적으로 관리
- PVC 템플릿을 사용한 로컬 영구 볼륨 지원
- Pod에서 영구 볼륨 스토리지 재사용
- WAL 파일을 위한 별도의 볼륨
- PostgreSQL 마이너 버전의 롤링 업데이트
- 운영자 업그레이드를 위한 현재 위치 또는 롤링 업데이트
- TLS 연결 및 클라이언트 인증서 인증
- 사용자 정의 TLS 인증서 지원(cert-manager와의 통합 포함)
- 객체 저장소(AWS S3 및 S3 호환, Azure Blob Storage 및 Google Cloud Storage)에 대한 지속적인 WAL 보관
- 볼륨 스냅샷 백업(기본 스토리지 클래스에서 지원되는 경우)
- 객체 저장소에 대한 백업(AWS S3 및 S3 호환, Azure Blob Storage 및 Google Cloud Storage)
- 볼륨 스냅샷 또는 개체 저장소의 기존 백업에서 전체 복구 및 특정 시점 복구
- PostgreSQL의 주요 업그레이드를 포함하여 기존 PostgreSQL 데이터베이스를 오프라인으로 가져오기
- 전체 PostgreSQL 클러스터 또는 선언적 방식으로 인스턴스의 하위 집합을 펜싱합니다.
- 선언적 방식으로 PostgreSQL 클러스터의 최대 절전 모드
- 동기식 복제본 지원
- 클러스터 수준에서 HA 물리적 복제 슬롯 지원
- 대기에서 백업
- 백업 보존 정책(복구 기간 기준, 객체 저장소에만 해당)
- 쓰기가 많은 시스템에서 데이터베이스가 WAL 생성을 따라갈 수 있도록 하는 병렬 WAL 보관 및 복원
- 객체 저장소 계층에서 선택적 보존 관리를 활성화하기 위해 객체 저장소에 업로드된 백업 파일에 태그 지정을 지원합니다.
- 여러 Kubernetes 클러스터에 PostgreSQL을 배포하여 프라이빗, 퍼블릭, 하이브리드 및 멀티 클라우드 아키텍처를 지원합니다.
- PgBouncer를 사용한 연결 풀링

 

 

이런 작업들을 사람이 직접 한다면 굉장히 고통스러운 작업들일텐데 CloudNativePG(Operator)가 대신 해준다고 생각해보면 안 쓸수가 없다..

 

Architecture

상태 동기화

PostgreSQ은 DBMS이기 때문에 Kubernetes에서 Stateful Workload로 운영되어야 하며, 2가지 전략 중 1가지를 선택하여 복제되어야 한다.

  • Storage-Level 복제
  • Application-Level 복제

 

그 중 CloudNativePG는 Application-Level 복제를 사용하는데 WAL(Write Ahead Log)를 기반으로 하는 내장 물리적 복제 기능을 제공한다.

 

(참고로 CloudNativePG는 Storage-Level 복제를 권장하지 않는다고 한다.)

 

 

PostgreSQL 아키텍쳐

CloudNativePG는 Kubernetes 클러스터 내 여러 상시 대기 복제본을 관리하기 위해 비동기식 / 동기식 스트리밍 복제를 기반으로 하는 클러스터를 지원한다.

 

 

권장되는 비-공유 PostgreSQL 클러스터 아키텍쳐

 

CloudNativePG는 클러스터의 토폴로지가 변경(ex. Primary 인스턴스에 장애가 발생한 경우)되면 자동으로 위의 서비스를 업데이트한다.

예를 들어 RW(Read Write)를 페일오버되어 승격된 인스턴스를 바라보도록 서비스를 자동으로 업데이트하여 애플리케이션 트래픽이 원활하게 리디렉션되도록 한다.

PostgreSQ은 3가지 모드(RW, RO, R)를 지원한다.

 

 

 

Read-Write 워크로드

RW 워크로드에는 Primary 인스턴스에게만 연결되고 Primary에 저장된 데이터는 Standby에게 복제된다.

 

 

Read-Only 워크로드

RO 워크로드에는 Syandby 인스턴스에게 라운드로빈으로 연결된다.

 

 

PostgreSQL 구성

postgresql.conf : 주요 Runtime 구성 파일

pg_hba.conf : 클라이언트 인증 파일

 

PostgreSQL Config를 변경하기 위해서 선언적으로 설정해야 하며 설정 파일들을 직접 건드릴 수는 없다.

즉 다음처럼 선언적으로 설정값을 넣어주어야 함을 의미한다.

 

# ...
  postgresql:
    parameters:
      shared_buffers: "1GB"
  # ...
PostgreSQL GUCs: Grand Unified Configuration

 

 

Monitoring

CloudNativePG는 Prometheus 호환 Metric을 9187 Port를 통해 제공한다.

 

이 경로로부터 Metric을 Scrape 하면 Grafana와 같은 곳에서 모니터링을 수행할 수 있음을 의미한다.

 

 

 

Connection Pooling

CloudNativePG는 pgBouncer를 통해 커넥션 풀링을 지원한다.

 

pgBouncer는 PostgreSQL을 위한 인기있는 Open source 커넥션 풀러이며, Pooler란 CRD를 통해 지원된다.

 

Pooler란 CRD를 통해 PgBouncer를 Deployment로 배포할 수 있으며 독립적이고 확장할 수 있으며 고가용성 DB Access layer를 구성할 수 있다.

 

Architecture

PgBouncer 기반으로 DB Access Layer 도입이 CloudNativePG 아키텍쳐를 어떻게 변경하는지 다음 그림으로 확인할 수 있다.

 

Client는 PostgreSQL 기본 서비스에 직접 연결하는 대신 PgBouncer에 해당하는 서비스에 연결할 수 있으므로 PostgreSQL에서 더 빠른 성능과 더 나은 리소스 관리를 위해 기존 연결을 재사용할 수 있다.

 

 

Pooler CRD를 다음의 예시처럼 배포하면 PgBouncer를 배포할 수 있다.

apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
  name: pooler-example-rw
spec:
  cluster:
    name: cluster-example

  instances: 3
  type: rw
  pgbouncer:
    poolMode: session
    parameters:
      max_client_conn: "1000"
      default_pool_size: "10"

 

PgBouncer 또한 Prometheus 호환 메트릭을 제공하며 9127 Port로 접근하면 된다.

 

 

 

 

여기까지 간단하게 CloudNativePG가 어떤걸 해주고 어떤 아키텍쳐를 가지고 있는지 등을 알아보았다.

 

이제 간단히 실습을 진행해보자.

 

 

1. CloudNativePG 설치

1.1. Operator Lifecycle Manager(OML) 설치

 

OLM은 k8s cluster 상에서 운영중인 Operator 운영을 좀 더 쉽게 도와준다.

curl -sL <https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.25.0/install.sh> | bash -s v0.25.0

 

 

 

 

 

1.2. CloudNative-PG Operator 설치

curl -s -O <https://operatorhub.io/install/cloudnative-pg.yaml>

cat cloudnative-pg.yaml | yh

kubectl create -f cloudnative-pg.yaml

 

operator 네임스페이스에 cnpg-controller-manager 파드가 설치된 것을 확인할 수 있다.

 

또한 CRD를 확인해보면 poolers, clusters, backups, scheduledbackups 4개의 CRD가 구성된 것을 확인할 수 있다.

 

poolers는 pgBouncer라는 커넥션풀링을 뜻하며

backups, scheduledbackups CRD는 백업과 관련된 CRD,

clusters는 PostgreSQL CRD를 뜻한다.

 

 

 

1.3. PostgreSQL Cluster 구성

Cluster란 CR을 생성하면 Operator가 이를 Watch 한 뒤 그에 맞는 리소스를 생성해줄 것이다.

 

여기선 간단히 stroage Size를 어떻게 할 것인지, PostgreSQL Image를 무엇으로 할 것인지, instance 개수를 몇대로 할 것인지, 프라이머리 업데이트 전략을 어떻게 할 것인지(unsupervised), superuser의 접근을 허용할 것인지 등을 설정할 수 있다.

 

추가로 bootstrap으로 initDB를 설정할 것인지, monitoring을 활성화할것인지 등 또한 설정할 수 있다.

 

cat <<EOT> mycluster1.yaml
# Example of PostgreSQL cluster
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: mycluster
spec:
  imageName: ghcr.io/cloudnative-pg/postgresql:15.3
  instances: 3  
  storage:
    size: 3Gi
  postgresql:
    parameters:
      max_worker_processes: "40"
      timezone: "Asia/Seoul"
    pg_hba:
      - host all postgres all trust
  primaryUpdateStrategy: unsupervised 
  enableSuperuserAccess: true
  bootstrap:
    initdb:
      database: app
      encoding: UTF8
      localeCType: C
      localeCollate: C
      owner: app

  monitoring:
    enablePodMonitor: true
EOT

 

 

 

이렇게 하면 배포는 완료된다

Cluster CR을 바라보고 이제 CloudNativePG가 이를 바라보고있다가 파드를 알아서 생성해준다..

 

 

스크린샷을 확인해보면

1번 파드에서 Initializing(초기화)를 담당하는 Job이 뜬 뒤 종료되고 mycluster-1 파드가 생성된다.

그 이후 mycluster-2-join 이란 Job Pod가 생성되어 Join 작업을 수행한 뒤 mycluster-2 파드가 생성되는 식으로 진행된다.

 

 

 

위 과정을 거친 뒤에 클러스터 구성이 완료된다.

Pod는 3대가 구성되어 있고 PVC 또한 각 파드와 연결되어 있음을 확인할 수 있다.

그리고 특이점으로 StatefulSet이 아닌 **오퍼레이터가 직접 개별 파드를 생성하기** 때문에 Headless Service가 아니란 것이다.

StatefulSet의 단점(볼륨을 증설할 때 까다롭다거나 등등..)을 해소하고 Operator가 직접 Pod를 관리하게 되는 것이다.

Service는 R, RO, RW 총 3가지로 이루어져 있으며 R은 Primary + Read Replica를 모두 읽기 패스로 제공하여 RO는 Read Replica, RW는 Primary 파드를 연결해준다.

 

 

 

cnpg 플러그인을 사용하면 CloudNativePG를 운영하는데 좀 더 도움이 될 수 있다.

 

kubectl krew install cnpg

kubectl cnpg status mycluster

 

 

 

 

—verbose 옵션을 주면 좀 더 상세한 모니터링이 가능해진다.

 

kubectl cnpg status mycluster --verbose

 

 

 

 

1.4. 기본 사용 방법

기본적인 구성은 확인했으니 client pod를 생성하여 접속해볼 예정임.

 

secret을 통해 username, password를 얻은 뒤 myClient Pod를 생성하여 접근할 예정이다.

 

 

 

접속 테스트를 위해 이제 PostgreSQL에 접속하기 위해 Client Pod를 생성한다.

 

curl -s https://raw.githubusercontent.com/gasida/DOIK/main/5/myclient-new.yaml -o myclient.yaml

for ((i=1; i<=3; i++)); do PODNAME=myclient$i VERSION=15.3.0 envsubst < myclient.yaml | kubectl apply -f - ; done

pod/myclient1 created
pod/myclient2 created
pod/myclient3 created


kubectl exec -it myclient1 -- psql -U postgres -h mycluster-rw -p 5432 --variable=HISTFILE=/tmp/.psql_history

 

 

 

 

접속하여 현재 커넥션 정보라던가, DB 리스트라던가 타임존 등을 간단하게 확인해볼 수 있다.

 

 

 

 

2. 간단한 장애 테스트

2.1. Primary 인스턴스의 PVC, POD 동시에 제거

  • 첫 번째 화면 : 인스턴스 3대가 운영중
  • 두 번째 화면 : while 문으로 1초마다 한번씩 데이터의 개수를 확인중
  • 세 번째 화면 : 1초마다 한번씩 DB에 데이터를 Insert

 

 

mycluster-3 파드가 현재 Primary 노드이기 때문에 mycluster-3 파드를 죽여보자.

 

 

 

 

일시적으로 쓰기 작업이 안됐다가 곧바로 복구됨을 확인할 수 있다.

→ 장애가 발생된 순간 Insert가 끊겼다가 바로 복구

 

 

 

mycluster-3가 사라지고 mycluster-6이 생성되었다.

 

 

 

mycluster-4가 Primary로 승격되었음을 확인할 수 있다.

 

 

 

여기까지 간단하게 PostgreSQL을 Kubernetes에서 Cloud Native 하게 운영할 수 있도록 도와주는 오퍼레이터인 CloudNativePG에 대해서 간단히 알아보았고

 

Operator 배포와 Operator에 의해 만들어진 CR, CRD를 통해 Cluster 리소스를 배포하여 PostgreSQL 파드 3대를 띄워보았다.

 

또한 간단한 Failover 테스트를 수행하여 Primary 파드가 죽었을 때 INSERT Query가 어떻게 수행되는지까지 확인해보았다.

반응형