Patroni是一款非常值得推荐的PostgreSQL高可用工具。不仅简单易用而且功能强大。

  • 支持自动failover和按需switchover
  • 支持一个和多个备节点
  • 支持级联复制
  • 支持同步复制,异步复制
  • 支持同步复制下备库故障时自动降级为异步复制(功效类似于MySQL的半同步,但是更加智能)
  • 支持控制指定节点是否参与选主,是否参与负载均衡以及是否可以成为同步备机
  • 支持通过pg_rewind自动修复旧主
  • 支持多种方式初始化集群和重建备机,包括pg_basebackup和支持wal_e,pgBackRest,barman等备份工具的自定义脚本
  • 支持自定义外部callback脚本
  • 支持REST API
  • 支持通过watchdog防止脑裂
  • 支持k8s,docker等容器化环境部署
  • 支持多种常见DCS(Distributed Configuration Store)存储元数据,包括etcd,ZooKeeper,Consul,Kubernetes

下面详细介绍基于patroni搭建postgresql高可用

实验环境

机器和vip资源

Postgresql

node1: 10.20.192.81
node2: 10.20.192.82

Etcd
VIP

读写vip:10.20.192.101
只读vip:10.20.192.102

环境准备

时钟同步
yum install -y ntpdate
ntpdate time.windows.com && hwclock -w

关闭防火墙

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

etcd部署

2节点etcd集群需要所有节点接受投票才能选举成功(N=2),只要有一个节点发生故障,etcd集群就会变成不可用状态,因为这时不可能选举成功。因此2节点集群的容错能力不如单节点集群,所以只在node1上部署单节点etcd。部署步骤如下:

安装需要的包:

yum install -y gcc python-devel epel-release

安装etcd:

yum install -y etcd

编辑etcd配置文件/etc/etcd/etcd.conf,参考配置如下:

[Member]
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_PEER_URLS="http://10.20.192.81:2380"
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_NAME="etcd01"
 
[Clustering]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.20.192.82:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://0.0.0.0:2379"
ETCD_INITIAL_CLUSTER="etcd01=http://10.20.192.81:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

启动etcd

systemctl start etcd
# 设置etc自启动
systemctl enable etcd

etcd不可访问影响

当Patroni无法访问etcd时,将不能确认自己所处的角色。为了防止这种状态下产生脑裂,如果本机的PG是主库,Patroni会把PG降级为备库。如果集群中所有Patroni节点都无法访问etcd,集群中将全部都是备库,业务无法写入数据。

为了防止etcd集群故障带来的严重影响,可以考虑设置超大的retry_timeout参数,比如10000天,同时通过同步复制模式防止脑裂。

retry_timeout:864000000
synchronous_mode:true

于PostgreSQL操作,目前似乎只有调用GET /patroni的REST API时会重试,而且最多只重试1次,所以把retry_timeout调大不会带来其他副作用。

patroni部署

在需要运行postgresql的实例上安装相关软件

安装PostgreSQL13

yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
​
yum install -y postgresql13-server postgresql13-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

创建Patroni 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: cn
namespace: /service/
name: pg1
​
restapi:
  listen: 0.0.0.0:8008
  connect_address: 10.20.192.81:8008
​
etcd:
  hosts: 10.20.192.81: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: 1000
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"
        max_connections: "100"
        max_prepared_transactions: "100"
        shared_preload_libraries: "citus,timescaledb"
        citus.node_conninfo: "sslmode=prefer"
        citus.replication_model: streaming
        citus.task_assignment_policy: round-robin
​
  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: 10.20.192.81:5432
  data_dir: /var/lib/pgsql/13/data
  bin_dir: /usr/pgsql-13/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

其他PG节点的patroni.yml需要相应修改下面3个参数

name

node1~node2分别设置pg1~pg2
restapi.connect_address

根据各自节点IP设置

postgresql.connect_address

根据各自节点IP设置

启动Patroni

systemctl start patroni

初次启动Patroni时,Patroni会初始创建PostgreSQL实例和用户。

[root@localhost ~]# 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 Thu 2021-03-25 22:42:39 CST; 6h ago
 Main PID: 8273 (patroni)
   CGroup: /system.slice/patroni.service
           ├─ 8273 /usr/bin/python /usr/bin/patroni /etc/patroni.yml
           ├─10067 /usr/pgsql-13/bin/postgres -D /var/lib/pgsql/13/data --config-file=/var/lib/pgsql/13/data/postgresql.conf --listen_addresses=0.0.0.0 --max_worker_processes=8 -...
           ├─10074 postgres: cn: logger
           ├─10082 postgres: cn: checkpointer
           ├─10083 postgres: cn: background writer
           ├─10084 postgres: cn: stats collector
           ├─10117 postgres: cn: postgres postgres 127.0.0.1(36696) idle
           ├─10145 postgres: cn: walwriter
           ├─10146 postgres: cn: autovacuum launcher
           ├─10147 postgres: cn: TimescaleDB Background Worker Launcher
           ├─10148 postgres: cn: logical replication launcher
           ├─10168 postgres: cn: walsender repl 10.20.192.82(34922) streaming 0/B6533E8
           ├─10271 postgres: cn: Citus Maintenance Daemon: 14174/10
           ├─10620 postgres: cn: postgres postgres 10.20.192.81(57262) idle
           ├─11018 postgres: cn: postgres postgres 10.20.192.101(39970) idle
           ├─11661 SGZg6zXX
           ├─12459 postgres: cn: postgres postgres 10.20.192.84(39256) idle
           └─13323 postgres: cn: postgres postgres 10.20.192.83(41290) idle

查看集群状态

[root@localhost ~]# patronictl -c /etc/patroni.yml list
+ Cluster: cn (6943596690876391300) --------+----+-----------+
| Member | Host         | Role    | State   | TL | Lag in MB |
+--------+--------------+---------+---------+----+-----------+
| pg1    | 10.20.192.81 | Leader  | running |  5 |           |
| pg2    | 10.20.192.82 | Replica | running |  5 |       0.0 |
+--------+--------------+---------+---------+----+-----------+

设置postgres拥有免密的sudoer权限

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

vip

Patroni支持用户配置在特定事件发生时触发回调脚本。因此我们可以配置一个回调脚本,在主备切换后动态加载VIP。

准备加载VIP的回调脚本/var/lib/pgsql/patroni_callback.sh

#!/bin/bash
​
RWVIP=10.20.192.101
RVIP=10.20.192.102
GATEWAY=10.20.192.1
DEV=ens3
​
action=$1
role=$2
cluster=$3
​
log()
{
  echo "patroni_callback: $*"|logger
}
​
load_vip()
{
ip a|grep -w ${DEV}|grep -w $1 >/dev/null
if [ $? -eq 0 ] ;then
  log "vip exists, skip load vip"
else
  sudo ip addr add $1/32 dev ${DEV} >/dev/null
  rc=$?
  if [ $rc -ne 0 ] ;then
    log "fail to add vip $1 at dev ${DEV} rc=$rc"
    exit 1
  fi
​
  log "added vip $1 at dev ${DEV}"
​
  arping -U -I ${DEV} -s $1 ${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 $1 >/dev/null
if [ $? -eq 0 ] ;then
  sudo ip addr del $1/32 dev ${DEV} >/dev/null
  rc=$?
  if [ $rc -ne 0 ] ;then
    log "fail to delete vip $1 at dev ${DEV} rc=$rc"
    exit 1
  fi
​
  log "deleted vip $1 at dev ${DEV}"
else
  log "vip not exists, skip delete vip"
fi
}
​
log "patroni_callback start args:'$*'"
case $action in
  on_start|on_restart|on_role_change)
    case $role in
      master)
        unload_vip ${RVIP}
        load_vip ${RWVIP}
        ;;
      replica)
        unload_vip ${RWVIP}
        load_vip ${RVIP}
        ;;
      *)
        log "wrong role '$role'"
        exit 1
        ;;
    esac
    ;;
  *)
    log "wrong action '$action'"
    exit 1
    ;;
esac

修改Patroni配置文件/etc/patroni.yml,配置回调函数

postgresql:
...
  callbacks:
    on_start: /bin/bash /var/lib/pgsql/patroni_callback.sh
    on_restart: /bin/bash /var/lib/pgsql/patroni_callback.sh
    on_role_change: /bin/bash /var/lib/pgsql/patroni_callback.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 10.20.192.101 at dev ens3
Sep  5 21:32:25 localvm patroni: 2021-03-25 21:32:25,415 INFO: Lock owner: pg1; I am pg1
Sep  5 21:32:25 localvm patroni: 2021-03-25 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 10.20.192.101