후니의 IT인프라 사전
5주차 - Cloud Native PostgreSQL 오퍼레이터 본문
1. PostgreSQL의 개요 (블로그)
- 오픈소스 RDBMS로 무료 라이센스를 사용하며, 오래된 오픈소스의 안정성 등의 다양한 특징을 담고 있다.
2. CloudNativePG(CNPG) 소개
- 쿠버네티스 환경에서 PostgreSQL 워크로드를 관리해주는 오퍼레이터
2.1 구조와 아키텍처
- 비동기 혹은 동기 스트리밍 복제 구성의 클러스터 지원
- 하나의 Primary 노드와, 다수의 standby replicas로 동작합니다.
- RW : 요청 작업을 Primary에서만 처리합니다.
- RO : standby replicas와만 소통합니다.
- R : Read-Only작업을 모든 노드와 소통하며 처리합니다.
3. CNPG 배포
helm을 통해서 cnpg 오퍼레이터를 설치할 수 있습니다.
# PostgreSQL helm repo 추가
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm show values cnpg/cloudnative-pg
# helm으로 오퍼레이터 설치
helm install cnpg cnpg/cloudnative-pg -f ~/DOIK/5/values.yaml
# values.yaml 파일
nodeSelector: {kubernetes.io/hostname: k8s-m}
tolerations: [{key: node-role.kubernetes.io/master, operator: Exists, effect: NoSchedule}]
# CRD 조회
kubectl get crd
NAME CREATED AT
backups.postgresql.cnpg.io 2022-06-20T05:49:43Z
clusters.postgresql.cnpg.io 2022-06-20T05:49:43Z
poolers.postgresql.cnpg.io 2022-06-20T05:49:43Z
scheduledbackups.postgresql.cnpg.io 2022-06-20T05:49:43Z
# Cluster 배포
kubectl apply -f ~/DOIK/5/mycluster1.yaml && kubectl get pod -w
# mycluster1.yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: mycluster
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:14.2 # 이미지를 통해 버전정보 설정가능
instances: 3 # 3대 파드 (Primary 1대, Standby 2대가 기본)
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
3.1 배포확인 (CNPG 플러그인)
관리의 편리를 위해서 CNPG 플러그인을 설치하고, kubectl cnpg 명령어를 사용해 배포가 잘되었는지 확인합니다.
아래처럼 아주 깔끔하게 현재 상태와 role까지 확인되며 Streaming 상태 체크도 가능합니다.
# CNPG 플러그인 설치
curl -sSfL https://github.com/cloudnative-pg/cloudnative-pg/raw/main/hack/install-cnpg-plugin.sh | sudo sh -s -- -b /usr/local/bin
# 배포 클러스터 확인
kubectl cnpg status mycluster
3.2 any 접속 확인
myclient라는 파드를 생성하고, 해당 pod에서 mycluster에 접근을 시도하면 적절하게 분산접속이 가능한지 테스트할 수 있습니다. 저는 아래와 같은 결과 값을 획득하였습니다.
for i in {1..30}; do kubectl exec -it myclient1 -- psql -U postgres -h mycluster-any -p 5432 -c "select inet_server_addr();"; done | sort | uniq -c | sort -nr | grep 172
3.3 Primary pod 변경
아래 명령어를 통해서 Primary pod를 손쉽게 변경할 수 있습니다.
kubectl cnpg promote mycluster <Number>
# 현재 클러스터 상태
Name Database Size Current LSN Replication role Status QoS Manager Version
---- ------------- ----------- ---------------- ------ --- ---------------
mycluster-5 42 MB 0/150130A0 Primary OK BestEffort 1.15.1
mycluster-6 42 MB 0/15013158 Standby (async) OK BestEffort 1.15.1
mycluster-7 42 MB 0/15013210 Standby (async) OK BestEffort 1.15.1
# kubectl cnpg promote mycluster 6 명령어 입력 후
Name Database Size Current LSN Replication role Status QoS Manager Version
---- ------------- ----------- ---------------- ------ --- ---------------
mycluster-5 42 MB 0/16006BC8 Standby (async) OK BestEffort 1.15.1
mycluster-6 42 MB 0/16006C80 Primary OK BestEffort 1.15.1
mycluster-7 42 MB 0/16006D38 Standby (async) OK BestEffort 1.15.1
3.4 Scale Test
pod 숫자를 손쉽게 늘리거나 줄일 수 있습니다.
# 정보 확인
kubectl cnpg status mycluster
kubectl get cluster mycluster
NAME AGE INSTANCES READY STATUS PRIMARY
mycluster 167m 3 3 Cluster in healthy state mycluster-6
# 5대로 증가
kubectl patch cluster mycluster --type=merge -p '{"spec":{"instances":5}}' && kubectl get pod -l postgresql=mycluster -w
# 3대로 감소
kubectl patch cluster mycluster --type=merge -p '{"spec":{"instances":3}}' && kubectl get pod -l postgresql=mycluster -w
4. 장애테스트 (실습)
4.1 데이터 주입
# 파드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})
# query.sql
cat ~/DOIK/5/query.sql
CREATE DATABASE test;
\c test;
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT NOT NULL);
INSERT INTO t1 VALUES (1, 'Luis');
# SQL 파일 query 실행
kubectl cp ~/DOIK/5/query.sql myclient1:/tmp
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-rw -p 5432 -f /tmp/query.sql
# [터미널2] 모니터링(ro cluster에 대해서 모니터링 진행)
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
# 확인
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-ro -p 5432 -d test -c "SELECT COUNT(*) FROM t1"
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-ro -p 5432 -d test -c "SELECT * FROM t1"
# INSERT
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-rw -p 5432 -d test -c "INSERT INTO t1 VALUES (2, 'Luis2');"
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-ro -p 5432 -d test -c "SELECT * FROM t1"
# test 데이터베이스에 97개의 데이터 INSERT
#for ((i=3; i<=100; i++)); do psql -U postgres -h $POD1 -p 5432 -d test -c "INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done
for ((i=3; i<=100; i++)); do kubectl exec -it myclient1 -- psql -U postgres -h mycluster-rw -p 5432 -d test -c "INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-ro -p 5432 -d test -c "SELECT COUNT(*) FROM t1"
4.2 장애 1상황 : 프라이머리 파드(인스턴스) 1대 강제 삭제 및 동작 확인
# 파드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})
# query.sql
cat ~/DOIK/5/query.sql
CREATE DATABASE test;
\c test;
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT NOT NULL);
INSERT INTO t1 VALUES (1, 'Luis');
# SQL 파일 query 실행
kubectl cp ~/DOIK/5/query.sql myclient1:/tmp
kubectl exec -it myclient1 -- psql -U postgres -h mycluster-rw -p 5432 -f /tmp/query.sql
# 프라이머리 파드 정보 확인
kubectl cnpg status mycluster
# [터미널1] 모니터링
watch kubectl get pod -l cnpg.io/cluster=mycluster
# [터미널2] 모니터링
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
# [터미널3] test 데이터베이스에 다량의 데이터 INSERT
for ((i=301; i<=10000; i++)); do kubectl exec -it myclient1 -- psql -U postgres -h mycluster-rw -p 5432 -d test -c "INSERT INTO t1 VALUES ($i, 'Luis$i');";echo; done
# [터미널4] 파드 삭제
kubectl delete pvc/mycluster-1 pod/mycluster-1
kubectl cnpg status mycluster
# 파드 정보 확인
kubectl get pod -l cnpg.io/cluster=mycluster
결과
1) Primary가 삭제됨에 따라서, Standby 노드가 Primary로 승격되고 클러스터가 안정화 될 때까지 잠시 자료 입력에 오류가 생깁니다.
2) 그 후 클러스터가 안정화되면 일단 기존에 있는 노드를 통해서 자료 입력이 지속되고, 새로운 노드가 Standby됩니다.
4.3 [장애2] 프라이머리 파드(인스턴스) 가 배포된 노드 1대 drain 설정 및 동작 확인 → 추가로 워커 노드 1대를 더 drain ⇒ 이후 모든 워커 노드 원복 uncordon
# 워커노드 drain
kubectl drain k8s-w1 --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w
혹은
kubectl drain k8s-w2 --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w
혹은
kubectl drain k8s-w3 --delete-emptydir-data --force --ignore-daemonsets && kubectl get node -w
# 클러스터 정보 확인
kubectl cnpg status mycluster
# 동작 확인 후 uncordon 설정
kubectl uncordon k8s-w1 ; kubectl uncordon k8s-w2 ; kubectl uncordon k8s-w3
결과
1) 기본적인 정책상 1개 이상의 Secondary와 Primary가 필요하기 때문에 node의 drain 상태라도 cnpg가 evict 되지 않는다.
이상으로 CNPG 오퍼레이터 실습을 마치도록 하겠습니다. 오퍼레이터를 사용함으로써 확실하게 DB 관리에 대한 부담을 줄이고, 안정성은 높이고, 다양한 기능을 활용하기 좋은 것 같습니다.
'프로젝트&&스터디 > DOIK 스터디' 카테고리의 다른 글
스터디 회고 (1) | 2022.06.25 |
---|---|
5주차 - 백업 (AWS S3) & PITR 복원(도전과제) (0) | 2022.06.25 |
4주차 - Percona Distribution for MongoDB 오퍼레이터 (0) | 2022.06.25 |
3주차 - Kafka & Strimzi 오퍼레이터 (0) | 2022.06.25 |
2주차 - 오퍼레이터 & MySQL 오퍼레이터 (설치 및 장애테스트) (0) | 2022.06.25 |