基於Patroni的PostgreSQL高可用環境部署

1. 前言

PostgreSQL是一款功能,性能,可靠性均可以和高端的國外商業數據庫相媲美的開源數據庫。並且PostgreSQL的許可和生態徹底開放,不被任何一個單一的公司或國家所操控,保證了使用者沒有後顧之憂。國內愈來愈多的企業開始用PostgreSQL代替原來昂貴的國外商業數據庫。php

在部署PostgreSQL到生產環境中時,選擇適合的高可用方案是一項必不可少的工做。本文介紹基於Patroni的PostgreSQL高可用的部署方法,供你們參考。html

PostgreSQL的開源HA工具備不少種,下面幾種算是比較經常使用的node

  • PAF(PostgreSQL Automatic Failomianver)
  • repmgr
  • Patroni

它們的比較能夠參考: https://scalegrid.io/blog/managing-high-availability-in-postgresql-part-1/python

其中Patroni不只簡單易用並且功能很是強大。linux

  • 支持自動failover和按需switchover
  • 支持一個和多個備節點
  • 支持級聯複製
  • 支持同步複製,異步複製
  • 支持同步複製下備庫故障時自動降級爲異步複製(功效相似於MySQL的半同步,可是更加智能)
  • 支持控制指定節點是否參與選主,是否參與負載均衡以及是否能夠成爲同步備機
  • 支持經過pg_rewind自動修復舊主
  • 支持多種方式初始化集羣和重建備機,包括pg_basebackup和支持wal_epgBackRestbarman等備份工具的自定義腳本
  • 支持自定義外部callback腳本
  • 支持REST API
  • 支持經過watchdog防止腦裂
  • 支持k8s,docker等容器化環境部署
  • 支持多種常見DCS(Distributed Configuration Store)存儲元數據,包括etcd,ZooKeeper,Consul,Kubernetes

所以,除非只有2臺機器沒有多餘機器部署DCS的狀況,Patroni是一款很是值得推薦的PostgreSQL高可用工具。下面將詳細介紹基於Patroni搭建PostgreSQL高可用環境的步驟。web

2. 實驗環境

主要軟件sql

  • CentOS 7.8
  • PostgreSQL 12
  • Patroni 1.6.5
  • etcd 3.3.25

機器和VIP資源docker

  • PostgreSQL數據庫

    • node1:192.168.234.201
    • node2:192.168.234.202
    • node3:192.168.234.203
  • etcdbootstrap

    • node4:192.168.234.204
  • VIP

    • 讀寫VIP:192.168.234.210
    • 只讀VIP:192.168.234.211

環境準備

全部節點設置時鐘同步

yum install -y ntpdate
ntpdate time.windows.com && hwclock -w

若是使用防火牆須要開放postgres,etcd和patroni的端口。

  • postgres:5432
  • patroni:8008
  • etcd:2379/2380

更簡單的作法是將防火牆關閉

setenforce 0
sed -i.bak "s/SELINUX=enforcing/SELINUX=permissive/g" /etc/selinux/config
systemctl disable firewalld.service
systemctl stop firewalld.service
iptables -F

3. etcd部署

由於本文的主題不是etcd的高可用,因此只在node4上部署單節點的etcd用於實驗。生產環境至少須要部署3個節點,可使用獨立的機器也能夠和數據庫部署在一塊兒。etcd的部署步驟以下

安裝須要的包

yum install -y gcc python-devel epel-release

安裝etcd

yum install -y etcd

編輯etcd配置文件/etc/etcd/etcd.conf, 參考配置以下

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.234.204:2380"
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379,http://192.168.234.204:2379"
ETCD_NAME="etcd0"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.234.204:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.234.204:2379"
ETCD_INITIAL_CLUSTER="etcd0=http://192.168.234.204:2380"
ETCD_INITIAL_CLUSTER_TOKEN="cluster1"
ETCD_INITIAL_CLUSTER_STATE="new"

啓動etcd

systemctl start etcd

設置etcd自啓動

systemctl enable etcd

3. PostgreSQL + Patroni HA部署

在須要運行PostgreSQL的實例上安裝相關軟件

安裝PostgreSQL 12

yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

yum install -y postgresql12-server postgresql12-contrib

安裝Patroni

yum install -y gcc epel-release
yum install -y python-pip python-psycopg2 python-devel

pip install --upgrade pip
pip install --upgrade setuptools
pip install patroni[etcd]

建立PostgreSQL數據目錄

mkdir -p /pgsql/data
chown postgres:postgres -R /pgsql
chmod -R 700 /pgsql/data

建立Partoni service配置文件/etc/systemd/system/patroni.service

[Unit]
Description=Runners to orchestrate a high-availability PostgreSQL
After=syslog.target network.target
 
[Service]
Type=simple
User=postgres
Group=postgres
#StandardOutput=syslog
ExecStart=/usr/bin/patroni /etc/patroni.yml
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process
TimeoutSec=30
Restart=no
 
[Install]
WantedBy=multi-user.target

建立Patroni配置文件/etc/patroni.yml,如下是node1的配置示例

scope: pgsql
namespace: /service/
name: pg1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 192.168.234.201:8008

etcd:
  host: 192.168.234.204:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    master_start_timeout: 300
    synchronous_mode: false
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        listen_addresses: "0.0.0.0"
        port: 5432
        wal_level: logical
        hot_standby: "on"
        wal_keep_segments: 100
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"

  initdb:
  - encoding: UTF8
  - locale: C
  - lc-ctype: zh_CN.UTF-8
  - data-checksums

  pg_hba:
  - host replication repl 0.0.0.0/0 md5
  - host all all 0.0.0.0/0 md5

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 192.168.234.201:5432
  data_dir: /pgsql/data
  bin_dir: /usr/pgsql-12/bin

  authentication:
    replication:
      username: repl
      password: "123456"
    superuser:
      username: postgres
      password: "123456"

  basebackup:
    max-rate: 100M
    checkpoint: fast

tags:
    nofailover: false
    noloadbalance: false
    clonefrom: false
    nosync: false

完整的參數含有可參考Patroni手冊中的 YAML Configuration Settings,其中PostgreSQL參數可根據須要自行補充。

其餘PG節點的patroni.yml須要相應修改下面3個參數

  • name
    • node1~node4分別設置pg1~pg4
  • restapi.connect_address
    • 根據各自節點IP設置
  • postgresql.connect_address
    • 根據各自節點IP設置

啓動Patroni

先在node1上啓動Patroni。

systemctl start patroni

初次啓動Patroni時,Patroni會初始建立PostgreSQL實例和用戶。

[root@node1 ~]# systemctl status patroni
● patroni.service - Runners to orchestrate a high-availability PostgreSQL
   Loaded: loaded (/etc/systemd/system/patroni.service; disabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-09-05 14:41:03 CST; 38min ago
 Main PID: 1673 (patroni)
   CGroup: /system.slice/patroni.service
           ├─1673 /usr/bin/python2 /usr/bin/patroni /etc/patroni.yml
           ├─1717 /usr/pgsql-12/bin/postgres -D /pgsql/data --config-file=/pgsql/data/postgresql.conf --listen_addresses=0.0.0.0 --max_worker_processe...
           ├─1719 postgres: pgsql: logger
           ├─1724 postgres: pgsql: checkpointer
           ├─1725 postgres: pgsql: background writer
           ├─1726 postgres: pgsql: walwriter
           ├─1727 postgres: pgsql: autovacuum launcher
           ├─1728 postgres: pgsql: stats collector
           ├─1729 postgres: pgsql: logical replication launcher
           └─1732 postgres: pgsql: postgres postgres 127.0.0.1(37154) idle

再在node2上啓動Patroni。node2將做爲replica加入集羣,自動從leader拷貝數據並創建複製。

[root@node2 ~]# systemctl status patroni
● patroni.service - Runners to orchestrate a high-availability PostgreSQL
   Loaded: loaded (/etc/systemd/system/patroni.service; disabled; vendor preset: disabled)
   Active: active (running) since Sat 2020-09-05 16:09:06 CST; 3min 41s ago
 Main PID: 1882 (patroni)
   CGroup: /system.slice/patroni.service
           ├─1882 /usr/bin/python2 /usr/bin/patroni /etc/patroni.yml
           ├─1898 /usr/pgsql-12/bin/postgres -D /pgsql/data --config-file=/pgsql/data/postgresql.conf --listen_addresses=0.0.0.0 --max_worker_processe...
           ├─1900 postgres: pgsql: logger
           ├─1901 postgres: pgsql: startup   recovering 000000010000000000000003
           ├─1902 postgres: pgsql: checkpointer
           ├─1903 postgres: pgsql: background writer
           ├─1904 postgres: pgsql: stats collector
           ├─1912 postgres: pgsql: postgres postgres 127.0.0.1(35924) idle
           └─1916 postgres: pgsql: walreceiver   streaming 0/3000060

查看集羣狀態

[root@node2 ~]# patronictl -c /etc/patroni.yml list
+ Cluster: pgsql (6868912301204081018) -------+----+-----------+
| Member |       Host      |  Role  |  State  | TL | Lag in MB |
+--------+-----------------+--------+---------+----+-----------+
|  pg1   | 192.168.234.201 | Leader | running |  1 |           |
|  pg2   | 192.168.234.202 |        | running |  1 |       0.0 |
+--------+-----------------+--------+---------+----+-----------+

爲了方便平常操做,設置全局環境變量PATRONICTL_CONFIG_FILE

echo 'export PATRONICTL_CONFIG_FILE=/etc/patroni.yml' >/etc/profile.d/patroni.sh

添加如下環境變量到~postgres/.bash_profile

export PGDATA=/pgsql/data
export PATH=/usr/pgsql-12/bin:$PATH

設置postgres擁有免密的sudoer權限

echo 'postgres        ALL=(ALL)       NOPASSWD: ALL'> /etc/sudoers.d/postgres

4. 自動切換和腦裂防禦

Patroni在主庫故障時會自動執行failover,確保服務的高可用。可是自動failover若是控制不當會有產生腦裂的風險。所以Patroni在保障服務的可用性和防止腦裂的雙重目標下會在特定場景下執行一些自動化動做。

故障位置 場景 Patroni的動做
備庫 備庫PG中止 中止備庫PG
備庫 中止備庫Patroni 中止備庫PG
備庫 強殺備庫Patroni(或Patroni crash) 無操做
備庫 備庫沒法鏈接etcd 無操做
備庫 非Leader角色可是PG處於生產模式 重啓PG並切換到恢復模式做爲備庫運行
主庫 主庫PG中止 重啓PG,重啓超過master_start_timeout設定時間,進行主備切換
主庫 中止主庫Patroni 中止主庫PG,並觸發failover
主庫 強殺主庫Patroni(或Patroni crash) 觸發failover,此時出現"雙主"
主庫 主庫沒法鏈接etcd 將主庫降級爲備庫,並觸發failover
- etcd集羣故障 將主庫降級爲備庫,此時集羣中所有都是備庫。
- 同步模式下無可用同步備庫 臨時切換主庫爲異步複製,在恢復爲同步複製以前自動failover暫不生效

4.1 Patroni如何防止腦裂

部署在數據庫節點上的patroni進程會執行一些保護操做,確保不會出現多個「主庫」

  1. 非Leader節點的PG處於生產模式時,重啓PG並切換到恢復模式做爲備庫運行
  2. Leader節點的patroni沒法鏈接etcd時,不能確保本身仍然是Leader,將本機的PG降級爲備庫
  3. 正常中止patroni時,patroni會順便把本機的PG進程也停掉

然而,當patroni進程自身沒法正常工做時,以上的保護措施難以獲得貫徹。好比patroni進程異常終止或主機臨時hang等。

爲了更可靠的防止腦裂,Patroni支持經過Linux的watchdog監視patroni進程的運行,當patroni進程沒法正常往watchdog設備寫入心跳時,由watchdog觸發Linux重啓。具體的配置方法以下

設置Patroni的systemd service配置文件/etc/systemd/system/patroni.service

[Unit]
Description=Runners to orchestrate a high-availability PostgreSQL
After=syslog.target network.target
 
[Service]
Type=simple
User=postgres
Group=postgres
#StandardOutput=syslog
ExecStartPre=-/usr/bin/sudo /sbin/modprobe softdog
ExecStartPre=-/usr/bin/sudo /bin/chown postgres /dev/watchdog
ExecStart=/usr/bin/patroni /etc/patroni.yml
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=process
TimeoutSec=30
Restart=no
 
[Install]
WantedBy=multi-user.target

設置Patroni自啓動

systemctl enable patroni

修改Patroni配置文件/etc/patroni.yml,添加如下內容

watchdog:
  mode: automatic # Allowed values: off, automatic, required
  device: /dev/watchdog
  safety_margin: 5

safety_margin指若是Patroni沒有及時更新watchdog,watchdog會在Leader key過時前多久觸發重啓。在本例的配置下(ttl=30,loop_wait=10,safety_margin=5)下,patroni進程每隔10秒(loop_wait)都會更新Leader key和watchdog。若是Leader節點異常致使patroni進程沒法及時更新watchdog,會在Leader key過時的前5秒觸發重啓。重啓若是在5秒以內完成,Leader節點有機會再次得到Leader鎖,不然Leader key過時後,由備庫經過選舉選出新的Leader。

這套機制基本上能夠保證不會出現"雙主",可是這個保證是依賴於watchdog的可靠性的,從生產實踐上這個保證對絕大部分場景多是足夠的,可是從理論上難以證實它100%可靠。

另外一方面,自動重啓機器的方式會不會太暴力致使"誤殺"呢?好比因爲突發的業務訪問致使機器負載太高,進而致使patroni進程不能及時分配到CPU資源,此時自動重啓機器就未必是咱們指望的行爲。

那麼,有沒有其它更可靠的防止腦裂的手段呢?

4.2 利用PostgreSQL同步複製防止腦裂

防止腦裂的另外一個手段是把PostgreSQL集羣配置成同步複製模式。利用同步複製模式下的主庫在沒有同步備庫應答日誌時寫入會阻塞的特色,在數據庫內部確保即便出現「雙主」也不會發生"雙寫"。採用這種方式防止腦裂是最可靠最安全的,代價是同步複製相對異步複製會下降一點性能。具體設置方法以下

初始運行Patroni時,在Patroni配置文件/etc/patroni.yml中設置同步模式

synchronous_mode:true

對於已部署的Patroni能夠經過patronictl命令修改配置

patronictl edit-config -s 'synchronous_mode=true'

此配置下,若是同步備庫臨時不可用,Patroni會把主庫的複製模式降級成了異步複製,確保服務不中斷。效果相似於MySQL的半同步複製,可是相比MySQL使用固定的超時時間控制複製降級,這種方式更加智能,同時還具備防腦裂的功效。

在同步模式下,只有同步備庫具備被提高爲主庫的資格。所以若是主庫被降級爲異步複製,因爲沒有同步備庫做爲候選主庫failover不會被觸發,也就不會出現「雙主」。若是主庫沒有被降級爲異步複製,那麼即便出現「雙主」,因爲舊主處於同步複製模式,數據沒法被寫入,也不會出現「雙寫」。

Patroni經過動態調整PostgreSQL參數synchronous_standby_names控制同步異步複製的切換。而且Patroni會把同步的狀態記錄到etcd中,確保同步狀態在Patroni集羣中的一致性。

正常的同步模式的元數據示例以下:

[root@node4 ~]# etcdctl get /service/cn/sync
{"leader":"pg1","sync_standby":"pg2"}

備庫故障致使主庫臨時降級爲異步複製的元數據以下:

[root@node4 ~]# etcdctl get /service/cn/sync
{"leader":"pg1","sync_standby":null}

若是集羣中包含3個以上的節點,還能夠考慮採起更嚴格的同步策略,禁止Patroni把同步模式降級爲異步。這樣能夠確保任何寫入的數據至少存在於2個以上的節點。對數據安全要求極高的業務能夠採用這種方式。

synchronous_mode:true
synchronous_mode_strict:true

若是集羣包含異地的災備節點,能夠根據須要配置該節點爲不參與選主,不參與負載均衡,也不做爲同步備庫。

tags:
    nofailover: true
    noloadbalance: true
    clonefrom: false
    nosync: true

4.2 etcd不可訪問的影響

當Patroni沒法訪問etcd時,將不能確認本身所處的角色。爲了防止這種狀態下產生腦裂,若是本機的PG是主庫,Patroni會把PG降級爲備庫。若是集羣中全部Patroni節點都沒法訪問etcd,集羣中將所有都是備庫,業務沒法寫入數據。這就要求etcd集羣具備很是高的可用性,特別是當咱們用一套中心的etcd集羣管理幾百幾千套PG集羣的時候。

當咱們使用集中式的一套etcd集羣管理不少套PG集羣時,爲了預防etcd集羣故障帶來的嚴重影響,能夠考慮設置超大的retry_timeout參數,好比1萬天,同時經過同步複製模式防止腦裂。

retry_timeout:864000000
synchronous_mode:true

retry_timeout用於控制操做DCS和PostgreSQL的重試超時。Patroni對須要重試的操做,除了時間上的限制還有重試次數的限制。對於PostgreSQL操做,目前彷佛只有調用GET /patroni的REST API時會重試,並且最多隻重試1次,因此把retry_timeout調大不會帶來其餘反作用。

5. 平常操做

平常維護時能夠經過patronictl命令控制Patroni和PostgreSQL,好比修改PotgreSQL參數。

[postgres@node2 ~]$ patronictl --help
Usage: patronictl [OPTIONS] COMMAND [ARGS]...

Options:
  -c, --config-file TEXT  Configuration file
  -d, --dcs TEXT          Use this DCS
  -k, --insecure          Allow connections to SSL sites without certs
  --help                  Show this message and exit.

Commands:
  configure    Create configuration file
  dsn          Generate a dsn for the provided member, defaults to a dsn of...
  edit-config  Edit cluster configuration
  failover     Failover to a replica
  flush        Discard scheduled events (restarts only currently)
  history      Show the history of failovers/switchovers
  list         List the Patroni members for a given Patroni
  pause        Disable auto failover
  query        Query a Patroni PostgreSQL member
  reinit       Reinitialize cluster member
  reload       Reload cluster member configuration
  remove       Remove cluster from DCS
  restart      Restart cluster member
  resume       Resume auto failover
  scaffold     Create a structure for the cluster in DCS
  show-config  Show cluster configuration
  switchover   Switchover to a replica
  version      Output version of patronictl command or a running Patroni...

5.1 修改PostgreSQL參數

修改個別節點的參數,能夠執行ALTER SYSTEM SET ... SQL命令,好比臨時打開某個節點的debug日誌。對於須要統一配置的參數應該經過patronictl edit-config設置,確保全局一致,好比修改最大鏈接數。

patronictl edit-config -p 'max_connections=300'

修改最大鏈接數後須要重啓才能生效,所以Patroni會在相關的節點狀態中設置一個Pending restart標誌。

[postgres@node2 ~]$ patronictl list
+ Cluster: pgsql (6868912301204081018) -------+----+-----------+-----------------+
| Member |       Host      |  Role  |  State  | TL | Lag in MB | Pending restart |
+--------+-----------------+--------+---------+----+-----------+-----------------+
|  pg1   | 192.168.234.201 | Leader | running | 25 |           |        *        |
|  pg2   | 192.168.234.202 |        | running | 25 |       0.0 |        *        |
+--------+-----------------+--------+---------+----+-----------+-----------------+

重啓集羣中全部PG實例後,參數生效。

patronictl restart pgsql

5.2 查看Patroni節點狀態

一般咱們能夠同patronictl list查看每一個節點的狀態。可是若是想要查看更詳細的節點狀態信息,須要調用REST API。好比在Leader鎖過時時存活節點卻沒法成爲Leader,查看詳細的節點狀態信息有助於調查緣由。

curl -s http://127.0.0.1:8008/patroni | jq

輸出示例以下:

[root@node2 ~]# curl -s http://127.0.0.1:8008/patroni | jq
{
  "database_system_identifier": "6870146304839171063",
  "postmaster_start_time": "2020-09-13 09:56:06.359 CST",
  "timeline": 23,
  "cluster_unlocked": true,
  "watchdog_failed": true,
  "patroni": {
    "scope": "cn",
    "version": "1.6.5"
  },
  "state": "running",
  "role": "replica",
  "xlog": {
    "received_location": 201326752,
    "replayed_timestamp": null,
    "paused": false,
    "replayed_location": 201326752
  },
  "server_version": 120004
}

上面的"watchdog_failed": true,表明使用了watchdog可是卻沒法訪問watchdog設備,該節點沒法被提高爲Leader。

6. 客戶端訪問配置

HA集羣的主節點是動態的,主備發生切換時,客戶端對數據庫的訪問也須要可以動態鏈接到新主上。有下面幾種常見的實現方式,下面分別。

  • 多主機URL

  • vip

  • haproxy

6.1 多主機URL

pgjdbc和libpq驅動能夠在鏈接字符串中配置多個IP,由驅動識別數據庫的主備角色,鏈接合適的節點。

JDBC

JDBC的多主機URL功能全面,支持failover,讀寫分離和負載均衡。能夠經過參數配置不一樣的鏈接策略。

libpq

libpq的多主機URL功能相對pgjdbc弱一點,只支持failover。

基於libpq實現的其餘語言的驅動相應地也能夠支持多主機URL,好比python和php。下面是python程序使用多主機URL建立鏈接的例子

import psycopg2

conn=psycopg2.connect("postgres://192.168.234.201:5432,192.168.234.202:5432/postgres?target_session_attrs=read-write&password=123456")

6.2 VIP(經過Patroni回調腳本實現VIP漂移)

多主機URL的方式部署簡單,可是不是每種語言的驅動都支持,並且若是數據庫出現意外的「雙主」,配置多主機URL的客戶端在多個主上同時寫入的機率比較高,而若是客戶端經過VIP的方式訪問則在VIP上又多了一層防禦(這種風險通常在數據庫的HA組件沒防禦好時發生,正如前面介紹的,若是咱們配置的是Patroni的同步模式,基本上沒有這個擔心)。

Patroni支持用戶配置在特定事件發生時觸發回調腳本。所以咱們能夠配置一個回調腳本,在主備切換後動態加載VIP。

準備加載VIP的回調腳本/pgsql/loadvip.sh

#!/bin/bash

VIP=192.168.234.210
GATEWAY=192.168.234.2
DEV=ens33

action=$1
role=$2
cluster=$3

log()
{
  echo "loadvip: $*"|logger
}

load_vip()
{
ip a|grep -w ${DEV}|grep -w ${VIP} >/dev/null
if [ $? -eq 0 ] ;then
  log "vip exists, skip load vip"
else
  sudo ip addr add ${VIP}/32 dev ${DEV} >/dev/null
  rc=$?
  if [ $rc -ne 0 ] ;then
    log "fail to add vip ${VIP} at dev ${DEV} rc=$rc"
    exit 1
  fi

  log "added vip ${VIP} at dev ${DEV}"

  arping -U -I ${DEV} -s ${VIP} ${GATEWAY} -c 5 >/dev/null
  rc=$?
  if [ $rc -ne 0 ] ;then
    log "fail to call arping to gateway ${GATEWAY} rc=$rc"
    exit 1
  fi
  
  log "called arping to gateway ${GATEWAY}"
fi
}

unload_vip()
{
ip a|grep -w ${DEV}|grep -w ${VIP} >/dev/null
if [ $? -eq 0 ] ;then
  sudo ip addr del ${VIP}/32 dev ${DEV} >/dev/null
  rc=$?
  if [ $rc -ne 0 ] ;then
    log "fail to delete vip ${VIP} at dev ${DEV} rc=$rc"
    exit 1
  fi

  log "deleted vip ${VIP} at dev ${DEV}"
else
  log "vip not exists, skip delete vip"
fi
}

log "loadvip start args:'$*'"

case $action in
  on_start|on_restart|on_role_change)
    case $role in
      master)
        load_vip
        ;;
      replica)
        unload_vip
        ;;
      *)
        log "wrong role '$role'"
        exit 1
        ;;
    esac
    ;;
  *)
    log "wrong action '$action'"
    exit 1
    ;;
esac

修改Patroni配置文件/etc/patroni.yml,配置回調函數

postgresql:
...
  callbacks:
    on_start: /bin/bash /pgsql/loadvip.sh
    on_restart: /bin/bash /pgsql/loadvip.sh
    on_role_change: /bin/bash /pgsql/loadvip.sh

全部節點的Patroni配置文件都修改後,從新加載Patroni配置文件

patronictl reload pgsql

執行switchover後,能夠看到VIP發生了漂移

/var/log/messages:

Sep  5 21:32:24 localvm postgres: loadvip: loadvip start args:'on_role_change master pgsql'
Sep  5 21:32:24 localvm systemd: Started Session c7 of user root.
Sep  5 21:32:24 localvm postgres: loadvip: added vip 192.168.234.210 at dev ens33
Sep  5 21:32:25 localvm patroni: 2020-09-05 21:32:25,415 INFO: Lock owner: pg1; I am pg1
Sep  5 21:32:25 localvm patroni: 2020-09-05 21:32:25,431 INFO: no action.  i am the leader with the lock
Sep  5 21:32:28 localvm postgres: loadvip: called arping to gateway 192.168.234.2

注意,若是直接中止主庫上的Patroni,上面的腳本不會摘除VIP。主庫上的Patroni被停掉後會觸發備庫failover成爲新主,此時新舊主2臺機器上都有VIP,可是因爲新主執行了arping,通常不會影響應用訪問。儘管如此,操做上仍是須要注意避免。

6.3 VIP(經過keepalived實現VIP漂移)

Patroni提供了用於健康檢查的REST API,能夠根據節點角色返回正常(200)和異常的HTTP狀態碼

  • GET /GET /leader

    運行中且是leader節點

  • GET /replica

    運行中且是replica角色,且沒有設置tag noloadbalance

  • GET /read-only

    GET /replica相似,可是包含leader節點

使用REST API,Patroni能夠和外部組件搭配使用。好比能夠配置keepalived動態在主庫或備庫上綁VIP。

關於Patroni的REST API接口詳細,參考Patroni REST API

下面的例子在一主一備集羣(node1和node2)中動態在備節點上綁只讀VIP(192.168.234.211),當備節點故障時則將只讀VIP綁在主節點上。

安裝keepalived

yum install -y keepalived

準備keepalived配置文件/etc/keepalived/keepalived.conf

global_defs {
    router_id LVS_DEVEL
}
vrrp_script check_leader {
    script "/usr/bin/curl -s http://127.0.0.1:8008/leader -v 2>&1|grep '200 OK' >/dev/null"
    interval 2
    weight 10
}
vrrp_script check_replica {
    script "/usr/bin/curl -s http://127.0.0.1:8008/replica -v 2>&1|grep '200 OK' >/dev/null"
    interval 2
    weight 5
}
vrrp_script check_can_read {
    script "/usr/bin/curl -s http://127.0.0.1:8008/read-only -v 2>&1|grep '200 OK' >/dev/null"
    interval 2
    weight 10
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens33
    virtual_router_id 211
    priority 100
    advert_int 1
    track_script {
        check_can_read
        check_replica
    }
    virtual_ipaddress {
       192.168.234.211
    }
}

啓動keepalived

systemctl start keepalived

上面的配置方法也能夠用於讀寫vip的漂移,只要把track_script中的腳本換成check_leader便可。可是在網絡抖動或其它臨時故障時keepalived管理的VIP容易飄,所以我的更推薦使用Patroni回調腳本動態綁定讀寫VIP。若是有多個備庫,也能夠在keepalived中配置LVS對全部備庫進行負載均衡,過程就不展開了。

6.4 haproxy

haproxy做爲服務代理和Patroni配套使用能夠很方便地支持failover,讀寫分離和負載均衡,也是Patroni社區做爲Demo的方案。缺點是haproxy自己也會佔用資源,全部數據流量都通過haproxy,性能上會有必定損耗。

下面配置經過haproxy訪問一主兩備PG集羣的例子。

安裝haproxy

yum install -y haproxy

編輯haproxy配置文件/etc/haproxy/haproxy.cfg

global
    maxconn 100
    log     127.0.0.1 local2

defaults
    log global
    mode tcp
    retries 2
    timeout client 30m
    timeout connect 4s
    timeout server 30m
    timeout check 5s

listen stats
    mode http
    bind *:7000
    stats enable
    stats uri /

listen pgsql
    bind *:5000
    option httpchk
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_192.168.234.201_5432 192.168.234.201:5432 maxconn 100 check port 8008
    server postgresql_192.168.234.202_5432 192.168.234.202:5432 maxconn 100 check port 8008
    server postgresql_192.168.234.203_5432 192.168.234.203:5432 maxconn 100 check port 8008

listen pgsql_read
    bind *:6000
    option httpchk GET /replica
    http-check expect status 200
    default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
    server postgresql_192.168.234.201_5432 192.168.234.201:5432 maxconn 100 check port 8008
    server postgresql_192.168.234.202_5432 192.168.234.202:5432 maxconn 100 check port 8008
    server postgresql_192.168.234.203_5432 192.168.234.203:5432 maxconn 100 check port 8008

若是隻有2個節點,上面的GET /replica 須要改爲GET /read-only,不然備庫故障時就沒法提供只讀訪問了,可是這樣配置主庫也會參與讀,不能徹底分離主庫的讀負載。

啓動haproxy

systemctl start haproxy

haproxy自身也須要高可用,能夠把haproxy部署在node1和node2 2臺機器上,經過keepalived控制VIP(192.168.234.210)在node1和node2上漂移。

準備keepalived配置文件/etc/keepalived/keepalived.conf

global_defs {
    router_id LVS_DEVEL
}
vrrp_script check_haproxy {
    script "pgrep -x haproxy"
    interval 2
    weight 10
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens33
    virtual_router_id 210
    priority 100
    advert_int 1
    track_script {
        check_haproxy
    }
    virtual_ipaddress {
       192.168.234.210
    }
}

啓動keepalived

systemctl start keepalived

下面作個簡單的測試。從node4上經過haproxy的5000端口訪問PG,會連到主庫上

[postgres@node4 ~]$ psql "host=192.168.234.210 port=5000 password=123456" -c 'select inet_server_addr(),pg_is_in_recovery()'
 inet_server_addr | pg_is_in_recovery
------------------+-------------------
 192.168.234.201  | f
(1 row)

經過haproxy的6000端口訪問PG,會輪詢鏈接2個備庫

[postgres@node4 ~]$ psql "host=192.168.234.210 port=6000 password=123456" -c 'select inet_server_addr(),pg_is_in_recovery()'
 inet_server_addr | pg_is_in_recovery
------------------+-------------------
 192.168.234.202  | t
(1 row)


[postgres@node4 ~]$ psql "host=192.168.234.210 port=6000 password=123456" -c 'select inet_server_addr(),pg_is_in_recovery()'
 inet_server_addr | pg_is_in_recovery
------------------+-------------------
 192.168.234.203  | t
(1 row)

haproxy部署後,能夠經過它的web接口 http://192.168.234.210:7000/查看統計數據

7. 級聯複製

一般集羣中全部的備庫都從主庫複製數據,可是特定的場景下咱們可能須要部署級聯複製。基於Patroni搭建的PG集羣支持2種形式的級聯複製。

7. 1 集羣內部的級聯複製

能夠指定某個備庫優先從指定成員而不是Leader節點複製數據。相應的配置示例以下:

tags:
    replicatefrom: pg2

replicatefrom只對節點處於Replica角色時有效,並不影響該節點參與Leader選舉併成爲Leader。當replicatefrom指定的複製源節點故障時,Patroni會自動修改PG切換到從Leader節點複製。

7.2 集羣間的級聯複製

咱們還能夠建立一個只讀的備集羣,從另外一個指定的PostgreSQL實例複製數據。這能夠用於建立跨數據中心的災備集羣。相應的配置示例以下:

初始建立一個備集羣,能夠在Patroni配置文件/etc/patroni.yml中加入如下配置

bootstrap:
  dcs:
    standby_cluster:
      host: 192.168.234.210
      port: 5432
      primary_slot_name: slot1
      create_replica_methods:
      - basebackup

上面的hostport是上游複製源的主機和端口號,若是上游數據庫是配置了讀寫VIP的PG集羣,能夠將讀寫VIP做爲host避免主集羣主備切換時影響備集羣。

複製槽選項primary_slot_name是可選的,若是配置了複製槽,須要同時在主集羣上配置持久slot,確保在新主上始終保持slot。

slots:
  slot1:
    type: physical

對於已配置好的級聯集羣,可使用patronictl edit-config命令動態添加standby_cluster設置把主集羣變成備集羣;以及刪除standby_cluster設置把備集羣變成主集羣。

standby_cluster:
  host: 192.168.234.210
  port: 5432
  primary_slot_name: slot1
  create_replica_methods:
  - basebackup

8. 參考

相關文章
相關標籤/搜索