Overview
CloudNet@ 에서 운영하고 있는 DOIK (Database Operator In Kubernetes) 3주차 스터디에서 PostgreSQL을 쿠버네티스에서 운영하는 실습을 진행하고 학습한 내용을 정리하였습니다.
CloudNativePG 소개 및 설치
https://cloudnative-pg.io/
CloudNativePG는 PostgreSQL의 기본 스트리밍 레플리케이션 기능을 사용하는 Primary/Standby 아키텍쳐를 갖춘 고가용성 PostgreSQL 데이터베이스 클러스터의 전체 수명 주기를 관장하는 Kubernetes 오퍼레이터이다.
CloudNativePG 설치 및 실습
1. OLM 설치 (Operator Lifecycle Manager) 설치
curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.25.0/install.sh | bash -s v0.25.0
다음 커맨드를 통해 어떤 리소스가 생성되어 있는지 확인한다.
kubectl get ns
OLM 설치 이후 olm, operators 라는 두개의 네임스페이스가 생성되었다.
kubectl get all -n operators
현재 OLM 만 설치된 상태이므로 operators 네임스페이스에는 리소스가 없는 상태이다.
2. PostgreSQL 오퍼레이터 설치
아래의 커맨드를 통해 PostgreSQL 오퍼레이터인 CloudNativePG를 설치한다.
curl -s -O https://operatorhub.io/install/cloudnative-pg.yaml
kubectl create -f cloudnative-pg.yaml
kubectl get all -n operators
CloudNativePG 배포이후 operators 네임스페이스를 확인해보면 사진처럼 cnpg 컨트롤러 매니저가 설치된것을 확인할 수 있다.
3. 실습! PostgreSQL 클러스터 배포
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
c.f. spec 에 대한 간략 설명
-
pg_hba
: 파드에서 사용하는 pg_hba.conf 를 생성하는데 사용되는 PostgreSQL 호스트 기반 인증 규칙의 목록이다. -
primaryUpdateStrategy
:unsupervised
설정되면 롤링 업데이트 프로세스가 쿠버네티스에 의해 관리되며 완전히 자동화된다. 레플리카가 업그레이드되면, 선택한 primaryUpdateMethod 작업이 프라이머리에서 시작된다.
c.f. 설치 순서 확인
kubectl apply -f mycluster1.yaml && kubectl get pod -w
cluster.postgresql.cnpg.io/mycluster created
NAME READY STATUS RESTARTS AGE
mycluster-1-initdb-9pfpg 0/1 Pending 0 1s
mycluster-1-initdb-9pfpg 0/1 Init:0/1 0 5s
mycluster-1-initdb-9pfpg 0/1 PodInitializing 0 9s
mycluster-1-initdb-9pfpg 1/1 Running 0 55s
mycluster-1-initdb-9pfpg 0/1 Completed 0 62s
mycluster-1 0/1 Pending 0 0s
mycluster-1 0/1 Init:0/1 0 5s
mycluster-1 0/1 PodInitializing 0 6s
mycluster-1 1/1 Running 0 15s
mycluster-2-join-kpblh 0/1 Pending 0 4s
mycluster-2-join-kpblh 0/1 Init:0/1 0 16s
mycluster-2-join-kpblh 0/1 PodInitializing 0 17s
mycluster-2-join-kpblh 1/1 Running 0 29s
mycluster-2-join-kpblh 0/1 Completed 0 38s
mycluster-2 0/1 Pending 0 0s
mycluster-2 0/1 Init:0/1 0 11s
mycluster-2 0/1 PodInitializing 0 12s
mycluster-2 1/1 Running 0 21s
mycluster-3-join-hxh4v 0/1 Pending 0 4s
mycluster-3-join-hxh4v 0/1 Init:0/1 0 4s
mycluster-3-join-hxh4v 0/1 PodInitializing 0 16s
mycluster-3-join-hxh4v 1/1 Running 0 44s
mycluster-3-join-hxh4v 0/1 Completed 0 56s
mycluster-3 0/1 Pending 0 0s
mycluster-3 0/1 Init:0/1 0 3s
mycluster-3 0/1 PodInitializing 0 4s
mycluster-3 1/1 Running 0 14s
mycluster-1-initdb-9pfpg 0/1 Terminating 0 3m28s
mycluster-2-join-kpblh 0/1 Terminating 0 2m9s
mycluster-3-join-hxh4v 0/1 Terminating 0 70s
- initdb Job 리소스 생성
- Primary 서버 생성
- Secondary 서버가 Primary 서버에 Join 하는 Job 리소스 생성
c.f. 아래 커맨드를 통해 생성된 리소스들을 확인해보자
kubectl get pod,pvc,pv,svc,ep
kubectl df-pv
kubectl describe pod -l cnpg.io/cluster=mycluster # TCP 9187 메트릭 제공
kubectl get pod -l cnpg.io/cluster=mycluster -owide
curl -s <파드IP>:9187/metrics
curl -s 192.168.1.84:9187/metrics
c.f. 그라파나 대시보드 설치하기
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/docs/src/samples/monitoring/grafana-configmap.yaml
c.f. krew 커맨드를 통해 CloudNativePG 오퍼레이터 관리툴 설치하기
CRD를 통해 쿠버네티스 내에 설치되어있는 클러스터를 아래 커맨드를 통해 확인할 수 있다.
kubectl get cluster
NAME AGE INSTANCES READY STATUS PRIMARY
mycluster 7m30s 3 3 Cluster in healthy state mycluster-1
cnpg 관리 툴 설치하기
kubectl krew install cnpg
아래 커맨드를 통해 설치된 pg 클러스터의 상태를 쉽게 조회할 수 있다.
kubectl cnpg status mycluster
Cluster Summary
Name: mycluster
Namespace: default
System ID: 7295339077458657299
PostgreSQL Image: ghcr.io/cloudnative-pg/postgresql:15.3
Primary instance: mycluster-1
Primary start time: 2023-10-29 11:21:52 +0000 UTC (uptime 7m35s)
Status: Cluster in healthy state
Instances: 3
Ready instances: 3
Current Write LSN: 0/7000060 (Timeline: 1 - WAL File: 000000010000000000000007)
Certificates Status
Certificate Name Expiration Date Days Left Until Expiration
---------------- --------------- --------------------------
mycluster-ca 2024-01-27 11:16:11 +0000 UTC 89.99
mycluster-replication 2024-01-27 11:16:11 +0000 UTC 89.99
mycluster-server 2024-01-27 11:16:11 +0000 UTC 89.99
Continuous Backup status
Not configured
Streaming Replication status
Replication Slots Enabled
Name Sent LSN Write LSN Flush LSN Replay LSN Write Lag Flush Lag Replay Lag State Sync State Sync Priority Replication Slot
---- -------- --------- --------- ---------- --------- --------- ---------- ----- ---------- ------------- ----------------
mycluster-2 0/7000060 0/7000060 0/7000060 0/7000060 00:00:00 00:00:00 00:00:00 streaming async 0 active
mycluster-3 0/7000060 0/7000060 0/7000060 0/7000060 00:00:00 00:00:00 00:00:00 streaming async 0 active
Unmanaged Replication Slot Status
No unmanaged replication slots found
Instances status
Name Database Size Current LSN Replication role Status QoS Manager Version Node
---- ------------- ----------- ---------------- ------ --- --------------- ----
mycluster-1 29 MB 0/7000060 Primary OK BestEffort 1.21.0 ip-192-168-3-223.ap-northeast-2.compute.internal
mycluster-2 29 MB 0/7000060 Standby (async) OK BestEffort 1.21.0 ip-192-168-1-9.ap-northeast-2.compute.internal
mycluster-3 29 MB 0/7000060 Standby (async) OK BestEffort 1.21.0 ip-192-168-2-128.ap-northeast-2.compute.internal
4. 실습! 장애 테스트 - Primary Pod 삭제
장애 테스트 준비 사항
현재 Primary 가 어떤 Pod 인지 찾는다.
kubectl cnpg status mycluster
# POD ip 환경 변수 셋팅
POD1=$(kubectl get pod mycluster-1 -o jsonpath={.status.podIP})
POD2=$(kubectl get pod mycluster-2 -o jsonpath={.status.podIP})
POD3=$(kubectl get pod mycluster-3 -o jsonpath={.status.podIP})
아래 커맨드를 통해 대량의 데이터를 삽입 한다.
for ((i=301; i<=10000; i++)); do kubectl exec -it myclient2 -- psql -U postgres -h mycluster-rw -p 5432 -d test -c "INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done
대량의 데이터 삽입 중 Primary Pod 를 삭제한다.
kubectl delete pvc/mycluster-1 pod/mycluster-1
클러스터의 Primary Pod 는 mycluster2 로 즉시 변경되는 것을 확인할 수 있다.
kubectl cnpg status mycluster
5. 실습! 장애 테스트 - Primary Pod 가 위치한 Node 삭제
클러스터의 Primary Pod 가 배포되어 있는 Node 를 찾는다.
아래 명령을 순차적으로 입력한다.
kubectl cnpg status mycluster
kubectl get nodes
터미널 한개에 데이터 삽입 커맨드를 입력한다.
for ((i=10001; i<=20000; i++)); do kubectl exec -it myclient2 -- psql -U postgres -h mycluster-rw -p 5432 -d test -c "INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done
또 다른 터미널에는 데이터 조회 커맨드를 입력한다.
while true; do kubectl exec -it myclient2 -- psql -U postgres -h mycluster-ro -p 5432 -d test -c "SELECT COUNT(*) FROM t1"; date;sleep 1; done
아래 명령을 통해 Primary Pod 가 배포되어 있는 Node 를 Drain 시킨다.
kubectl drain $NODE --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w
Primary Pod 가 Pending 상태로 변경되었다.
kubectl cnpg status mycluster
커맨드로 클러스터 상태를 조회해보면
mycluster-3 이라는 pod 가 Primary로 변경된 것을 확인할 수 있다.
4. 실습! cnpg 플러그인 커맨드
krew 플러그인인 cnpg 는 단순히 상태를 조회하는 것 이상으로 다양한 커맨드를 지원한다.
status
클러스터의 현재 상태를 조회할 수 있음.
promote
클러스터의 pod를 primary 로 승격할 수 있음.
아래 커맨드를 입력하여 mycluster-2 pod 를 primary 로 승격 시도한다.
kubectl cnpg promote mycluster mycluster-2
그 결과 mycluster-2 가 primary pod 로 승격되었음을 확인할 수 있다.
report
status 명령을 통해 현재 클러스터 상태를 단편적이고, 즉시 얻을 수도 있지만,
배포 상태, 이벤트, webhook, logs 등의 클러스터에 대한 여러 방면의 정보 또는 특정 시간대에 대한 상세한 클러스터 상태 정보를 획득하고자 할때 사용된다.
주로 아래와 같은 정보를 얻을 수 있다.
- cluster resources: 클러스터 정보
- cluster pods: 클러스터에서 사용되는 Pod 정보
- cluster jobs: 클러스터에서 사용된 jobs 정보
- events: 클러스터가 배포된 네임스페이스의 이벤트 정보
- pod logs: 클러스터 pod의 log
- job logs: 클러스터 jobs pod의 log
아래와 같이 입력하면 'mycluster' 클러스터에 대한 자세한 정보를 모아서 zip 파일로 말아준다.
kubectl cnpg report cluster mycluster
logs
PostgreSQL 클러스터의 로그 혹은 클러스터 pod의 로그를 확인할 수 있는 커맨드.
pgbench
데이터베이스 클러스터의 성능을 밴치마킹 할 수 있는 커맨드
fio
데이터베이스 클러스터가 사용하는 스토리지 클래스를 밴치마킹할 수 있는 커맨드
psql
postgresql 클라이언트를 실행하는 커맨드, 실제 Pod 에서 실행하는 것처럼 cnpg 플러그인을 통해 postgresql 클라이언트를 실행하는 커맨드를 지원한다.
kubectl cnpg psql mycluster
\conninfo
You are connected to database "postgres" as user "postgres" via socket in "/controller/run" at port "5432".
backup
PostgreSQL 클러스터의 일관된 스냅샷을 생성한다.
postgresql 클러스터에 대한 스냅샷은 아래와 같은 순서로 이루어진다.
- 작업 대상 replica Pod를 선택하기.
- replica를 펜싱하기.
- 스냅샷 생성하기
- replica 펜싱 해제하기
해당 명령은 하나 이상의 replica가 있는 클러스터에만 사용할 수 있으며, 해당 replica는 스냅샷의 일관성을 보장하기 위해 펜실 절차에 의해 종료된다. (이를 Cold Backup 이라함)
이 기능은 개발 중인 쿠버네티스의 VolumeSnapshot API에 대한 지원을 따라간다.
Wrap-Up
현재 현업에서 개발하고 있는 서비스의 백엔드 데이터베이스는 PostgreSQL로 개발되어 있다. AWS RDS Cluster 를 사용하여 개발하고 있으나, 서비스 오픈 이전에 많은 데이터베이스 비용이 발생하여 이에 대해 많은 고민을 하고 있었다.
처음 생각한 대안은 EC2 한대에 PostgreSQL을 기동시키는 것을 고민했다. 가장 비용적으로 절감할 수 있는 대안이었으나, 개발자로만 이루어진 팀에서 온프렘 DB의 운영 대응을 하기엔 구축시간 또는 운영대응에서 좋은 대안이 아니라고 판단하여 비싼 비용을 감내하고 AWS RDS Cluster 로 구축하여 개발을 진행하고 있다.
이 와중에 CloudNativePG를 학습하게 되면서 현재 우리 서비스의 데이터베이스로 고려해볼만한 솔루션이라고 생각한다. CloudNativePG 오퍼레이터를 설치하고, yaml에 원하는 스팩의 클러스터만 정의하면 PostgreSQL 클러스터가 기동되니, 이보다 편할 수 가 없다!
추가로 krew를 통해 지원되는 cnpg 플러그인에 대해 자세하게 살펴보면서, 장애 발생상황에서 리포트 추출이나, 갖가지 운영성 업무들을 커맨드를 통해 지원해주니, 개발팀만으로 이루어진 우리팀에서 사용하기 간편할 것으로 생각된다.
Top comments (0)