Kubernetes集群部署高可用PostgreSQL DB:實作流程及說明

Albert Weng
17 min readJan 26, 2024

--

不管是在什麼時代,對於資訊系統來說,”高可用性”一直都是在建構各種服務時會優先去考量的,尤其是現在服務愈來愈複雜、無時無刻都必須要提供服務的取用,所以從前端的應用服務一直到相關的資料庫都會以高可用的前提之下進行部署設計。

本篇將說明如何利用Kubernetes特色,將PostgreSQL DB以HA的架構來提供服務,當然,也說明了相關的實作流程與相關說明。

  1. 基本架構圖
  2. 前置作業
  3. Deployment vs StatefulSet
  4. HA components
  5. Load balancing
  6. 驗證
  7. 結論

1. 基本架構圖

2. 前置作業

Git repo: https://github.com/scriptcamp/kubernetes-postgresql.git
#------------------------------------------------
# S2-1. create namespace
#------------------------------------------------
[master]# kubectl create namespace database
#------------------------------------------------
# S2-2. 建立configmap,可以在容器內直接掛載檔案
#------------------------------------------------
[master]# vim postgres-configmap.yaml
[master]# kubectl create -f postgres-configmap.yaml -n database

[master]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 29s
postgres-configmap 1 7s

以下是本檔案所執行的內容說明:
* 檔案內的"pre-stop.sh"負責在postgresqldb服務停止前所需要執行的動作
(1) 先檢查目前正在停止中的元件類型(例如master/follower)
(2) 如果master狀態是停止中,則等待到follower被提升至master之後才繼續
(3) 確保HA架構成立(至少有一個master可進行寫入)
#------------------------------------------------
# S2-3. 建立PostgreSQL SVC
# 建立svc提供pod之間的溝通,此處會有2個類型(headless serivce/Service)
#------------------------------------------------
說明:基本上service會以LB(round-robin)的方式做流量負載平衡,而headless則不會,也不會被指派clusterip
為了要建立postgresql server,此處使用headless service的方式,為了之後的PostgreSQL statefulset.

[master]# vim postgres-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres-headless-svc
spec:
type: ClusterIP
clusterIP: None
ports:
- name: postgresql
port: 5432
targetPort: postgresql
protocol: TCP
selector:
app: postgres

[master]# kubectl create -f postgres-headless-svc.yaml
[master]# kubectl get all -n database
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/postgres-headless-svc ClusterIP None <none> 5432/TCP 4s
#------------------------------------------------
# S2-4. 建立PostgreSQL server secret
# 用來存放密碼,如果是正式環境,建議使用其他secret解決方案來進行管理
#------------------------------------------------
[master]# vim postgres-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: postgres-secrets
spec:
postgresql-password: "root123"
repmgr-password: "root123"

[master]# kubectl create -f postgres-secrets.yaml
[master]# kubectl describe secret postgres-secrets

3. Deployment vs StatefulSet

一般來說,部署應用服務時,通常會使用deployment來進行部署,但在部署資料庫時,因為資料庫會將所有的table , user等資料都存放在volume內,所以會搭配一個持久性儲存,並且為了要讓Pod可以跟據負載擴展,同時還要能保持資料的一致性,以下針對二種資源類型的比較:

  • Deployment: 每次Pod在刪除或重建之後名稱都會改變,不適合拿來做Pod識別使用。
  • StatefulSet: 只要Pod取得名稱之後,不管刪除或重建都不會被更改名稱,適合用來做識別Pod使用。並且因為具備順序的特性,也能確保volume可以掛回原本的volume,大部分的StatefulSet類型的應用都是這種方式

本篇的Pod資料寫入邏輯是先寫到postgres-0 資料會往其他副本同步。

但問題來了:

  • postgres-1要怎麼知道去那邊找到postgres-0?
  • postgres-2要怎麼知道去那邊找到postgres-1?

如果使用StatefulSet類型,因為具備Pod順序關係且Pod名稱不會改變,所以每個Pod都可以依照命名原則找到所需要發現的Pod

#------------------------------------------------
# S3-1. 建立postgres statefulset
#------------------------------------------------
[master]# vim postgres-statefulset.yaml
[master]# kubectl create -f postgres-statefulset.yaml
[master]# kubectl get all

[說明]
(1) 將metadata以變數的方式注入: 將pod name, pod namespace以環境變數的方式指派給Pod
(2) 將敏感資料以變數的方式注入:例如db password以變數的方式注入到postgres container
(3) Probes: 確保流程不會卡住,如果卡住也會自動進行重啟(此處使用postgres指令來實現)
(4) VolumeClaimTemplate: 讓Statefulset可以正確的建立volume給replica使用
(5) 本lab使用nfs storageclass做data volume
(6) 以下是幾個重要參數:
﹡POSTGRESQL_VOLUME_DIR: Postgres存放設定與資料的目錄,此目錄需要掛載到pvc
﹡PGDATA: 主要的Pg資料目錄
﹡POSTGRES_USER: 安裝過程中自動建立使用者家目錄
﹡POSTGRES_PASSWORD: 預設建立的使用者密碼
﹡POSTGRES_DB: 當主程式啟動後應該自動產生的DB

4. HA components

本篇使用RegMgr來負責以下二個任務:

  • Replication: 將資料從Primary server抄寫到其他複本,可以有效降低Server loading並且將讀寫請求分散
  • Failover: 負責處理Cluster內部的容錯切換,例如將其他節點提升到主節點

所有的設定都已在postgres-statefulset.yaml,以下說明幾個重要部分:

  • REPMGR_PARTNER_NODES: 用,做分隔,列出所有主機名稱
  • REPMGR_PRIMARY_HOST: 主Server的名稱
  • REPMGR_USERNAME: 用來執行RegMgr的使用者
  • REPMGR_PASSWORD: 使用者密碼
  • REPMGR_DATABASE: RegMgr所使用的DB

以下我們先來取得每個Pod的IP

#------------------------------------------------
# S4-1. 確認透過headless svc取得的pod ip
#------------------------------------------------
[master]# kubectl get pod -o wide

5. Load balancing

此處使用Pgpool以middleware的型式在Postgres server之前,並且作為整個cluster的gatekeeper使用。

主要目的是:Load Balancing & Limiting the requests

  • Load Balancing : pg pool會取得請求與查詢的連線,透過分析這些查詢來決定那些查詢要再繼續傳送
  • 讀的請求傳送給read-only的節點,寫入的請求就只能給primary node,實現load balancing的能力
  • 限制concurrent連線數量
#------------------------------------------------
# S5-1. 建立pgpool secret
#------------------------------------------------
[master]# vim pgpool-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: pgpool-secrets
data:
admin-password: "SFRzaVZxYjdSZQ=="

[master]# kubectl create -f pgpool-secret.yaml
[master]# kubectl get secrets
#------------------------------------------------
# S5-2. 建立pgpool svc (只有內部可以存取, 如果外部可以存取就用nodeport)
#------------------------------------------------
[master]# vim pgpool-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: pgpool-svc
spec:
type: ClusterIP
sessionAffinity: None
ports:
- name: postgresql
port: 5432
targetPort: postgresql
protocol: TCP
nodePort: null
selector:
app: pgpool

[master]# kubectl create -f pgpool-svc.yaml

---------
[master]# vim pgpool-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: pgpool-svc-external
spec:
type: NodePort
ports:
- name: postgresql
port: 5432
targetPort: postgresql
protocol: TCP
nodePort: 32000
selector:
app: pgpool

[master]# kubectl create -f pgpool-svc-nodeport.yaml
[master]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/pgpool-svc-external NodePort 10.100.50.5 <none> 5432:32000/TCP 3s
service/postgres-headless-svc ClusterIP None <none> 5432/TCP 23h
#------------------------------------------------
# S5-3. 部署pgpool
#------------------------------------------------
[master]# kubectl create -f pgpool-deployment.yaml
[master]# kubectl get all

6. 驗證

#------------------------------------------------
# S6-1. 建立client pod
#------------------------------------------------
[master]# vim psql-client.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: pg-client
spec:
containers:
- image: bitnami/postgresql:11.12.0-debian-10-r13
name: postgresql
env:
- name: ALLOW_EMPTY_PASSWORD
value: "yes"

[master]# kubectl create -f psql-client.yaml
#------------------------------------------------
# S6-2. 取得密碼
#------------------------------------------------
[master]# kubectl get secret postgres-secrets -n database -o jsonpath="{.data.postgresql-password}" | base64 --decode
WbrTpN3g7q
#------------------------------------------------
# S6-3. 執行連線
#------------------------------------------------
[master]# kubectl exec -it pg-client -n database -- /bin/bash
1001@pg-client:/$ PGPASSWORD=WbrTpN3g7q psql -h pgpool-svc -p 5432 -U postgres (inside)
1001@pg-client:/$ PGPASSWORD=WbrTpN3g7q psql -h 10.107.88.16 -p 32000 -U postgres (outside)
psql (11.12)
Type "help" for help.

postgres=#
postgres=# create database db1;
postgres=# \c db1; //to connect to new database
postgres=# create table test (id int primary key not null, value text not null);
postgres=# insert into test values (1, 'value1');

postgres=# select * from test;
#------------------------------------------------
# S6-4. 確認資料抄寫
#------------------------------------------------
postgres=# select * from pg_stat_replication;
#--------------------------------------------------
# S6-5. 若將Primary刪除後,確認其他follower的狀態
#--------------------------------------------------
[master]# kubectl logs -f postgres-sts-2

[note]
因為statefulset內將primary host訂死在sts-0,如果將sts-0刪除,
RegMgr的同步功能會有問題(會卡在sts-0),可以去編輯statefulset修改REPMGR_PRIMARY_HOST
參數,並刪除sts-0重新建立即可

7. 結論

在現代化的架構下,許多應用服務都開始往容器化的平台移動,如同虛擬化的世界一樣,因為資料庫非常著重在效能上,所以往往都是最後才從獨立的實體機移進新的架構平台。

本篇實作並說明了資料庫可以很好的利用容器平台的優勢,實作出傳統實體機上需要另外設定才能實現的功能,未來將會有愈來愈多的相關應用會移轉至新的平台,但別忘了,資料庫除了可用性很重要之外,對於使用者體驗影響最大的是效能的問題,如果面對大量的查詢,在本篇這種架構之下,可以透過自動生成更多的Pod來回應這些需求,就可以很好的解決這個問題。

References:

--

--

Albert Weng

You don't have to be great to start, but you have to start to be great