posgresql数据库高可用方案-patroni
一、简介
pg常用高可用方案有repmgr,patroni等,本文介绍patroni方案。Patroni,是专门为PostgreSQL数据库设计的一款以Python语言实现的高可用软件。其使用外部共享存储软件(kubernetes、etcd、etcd3、zookeeper、aws等)来存储patroni监控到的pg集群状态信息,实现 PostgreSQL 集群的自动故障恢复、自动故障转移等能力。
etcd是一个分布式键值对存储,设计用来可靠而快速的保存关键数据并提供访问,Etcd按照Raft算法和协议开发的,是一个强一致性的、分布式的key-value数据库。它为分布式系统提供了可靠的数据存储访问机制,patroni配合etcd一起使用来保障pg高可用。
本文通过patroni+etcd方式实现postgresql的高可用方案,以下是个软件版本。
python | patroni | etcd |
---|---|---|
3.7.4 | 2.0.2 | 3.3.11 |
二、部署etcd集群
1、yum方式部署etcd集群
#安装依赖包
yum install -y gcc python-devel epel-release libyaml
#安装etcd
yum install -y etcd libyaml
安装完成后etcd目录:
[root@xl001 ~]# ll /usr/bin/etcd*
-rwxr-xr-x 1 root root 25581944 Feb 14 2019 /usr/bin/etcd
-rwxr-xr-x 1 root root 21223848 Feb 14 2019 /usr/bin/etcdctl
2、二进制包安装
wget https://github.com/etcd-io/etcd/releases/download/v3.2.32/etcd-v3.2.32-linux-amd64.tar.gz
解压即可
tar -zxvf etcd-v3.2.32-linux-amd64.tar.gz
然后将 etcd etcdctl 拷贝到 /usr/local/bin 下
etcd --version #etcd 为服务端执行文件
etcdctl --version #etcdctl 为客户端执行文件
以上两种安装方式任选一种即可,pg集群三个节点上都要安装。
3、创建目录并授权
mkdir -p /opt/etcd
chown -R postgres:postgres /opt/etcd
4、编辑etcd配置文件
chown -R postgres:postgres /etc/etcd/etcd.conf
vim /etc/etcd/etcd.conf
ETCD_NAME="etcd-1" #节点名称,随便起个名字,三个节点不同即可,我这里三个节点叫做etcd-1,etcd-2,etcd-3
ETCD_DATA_DIR="/opt/etcd" #数据目录
#监听客户端请求的地址列表url,可以监听多个,多个用逗号分割,或者写成0.0.0.0,监听所有请求地址。
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"
#建议使用的客户端通信 url,该值用于 etcd 代理或 etcd 成员与 etcd 节点通信,192.168.167.11是本节点ip。
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.167.11:2379"
监听的用于节点之间通信的url,可监听多个,集群内部将通过这些url进行数据交互(如选举,数据同步等)
ETCD_LISTEN_PEER_URLS="http://0.0.0.0:2380"
#建议用于节点之间通信的url,节点间将以该值进行通信,192.168.167.11是本节点ip。
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.167.11:2380"
#也就是集群中所有的 initial-advertise-peer-urls 的合集。etcd启动的时候,通过这个配置找到其他etcd节点的列表。
ETCD_INITIAL_CLUSTER="etcd-1=http://192.168.167.11:2380,etcd-2=http://192.168.167.12:2380,etcd-3=http://192.168.167.13:2380"
#节点的 token 值,设置该值后集群将生成唯一 id,并为每个节点也生成唯一 id,当使用相同配置文件再启动一个集群时,只要该 token 值不一样,etcd 集群就不会相互影响。
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-token-1"
#初始化的时候,集群的状态:new 和 existing 两种状态。new代表新建的集群,existing 代表加入已经存在的集群
ETCD_INITIAL_CLUSTER_STATE="new"
# 这里只写了一个节点的配置文件作为示例,pg三个节点都要配置
5、启动etcd,在postgres账号下启动
启动之前检查一下时间是否同步,当各节点时间差大于1s时,etcd集群会报错,所以要配置时间同步服务器
这里的原则是谁先启动,谁就是etcd数据库主节点
su - postgres
etcd --config-file=/etc/etcd/etcd.conf
注意,有的版本配置文件只能yaml格式(不知道为什么),如果是这种情况,直接在启动命令的命令行上指定参数,例如下面这样:
su - postgres
nohup etcd --name etcd-1 --initial-advertise-peer-urls http://192.168.167.11:2380 --listen-peer-urls http://0.0.0.0:2380 -data-dir /opt/etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://192.168.167.11:2379 --initial-cluster-state new --initial-cluster-token etcd-cluster-token-1 --initial-cluster etcd-1=http://192.168.167.11:2380,etcd-2=http://192.168.167.12:2380,etcd-3=http://192.168.167.13:2380 &
nohup etcd --name etcd-2 --initial-advertise-peer-urls http://192.168.167.12:2380 --listen-peer-urls http://0.0.0.0:2380 -data-dir /opt/etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://192.168.167.12:2379 --initial-cluster-state new --initial-cluster-token etcd-cluster-token-1 --initial-cluster etcd-1=http://192.168.167.11:2380,etcd-2=http://192.168.167.12:2380,etcd-3=http://192.168.167.13:2380 &
nohup etcd --name etcd-3 --initial-advertise-peer-urls http://192.168.167.13:2380 --listen-peer-urls http://0.0.0.0:2380 -data-dir /opt/etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://192.168.167.13:2379 --initial-cluster-state new --initial-cluster-token etcd-cluster-token-1 --initial-cluster etcd-1=http://192.168.167.11:2380,etcd-2=http://192.168.167.12:2380,etcd-3=http://192.168.167.13:2380 &
6、etcd常用运维命令
#查看集群状态
etcdctl cluster-health
eg:
[root@xl001 etcd]# etcdctl cluster-health
member 802aec41d9fb61a6 is healthy: got healthy result from http://192.168.167.11:2379
member b79cb086020dff09 is healthy: got healthy result from http://192.168.167.12:2379
member fec7c7f897b1265c is healthy: got healthy result from http://192.168.167.13:2379
cluster is healthy
#查看集群成员
etcdctl member list
eg:
[root@xl001 etcd]# etcdctl member list
802aec41d9fb61a6: name=etcd-1 peerURLs=http://192.168.167.11:2380 clientURLs=http://192.168.167.11:2379 isLeader=true
b79cb086020dff09: name=etcd-2 peerURLs=http://192.168.167.12:2380 clientURLs=http://192.168.167.12:2379 isLeader=false
fec7c7f897b1265c: name=etcd-3 peerURLs=http://192.168.167.13:2380 clientURLs=http://192.168.167.13:2379 isLeader=false
# 停止etcd 服务
ps -ef |grep etcd
kill -9
etcdctl mkdir v #创建目录v
etcdctl mkdir v/sub_v #在目录v下创建目录sub_v
etcdctl rmdir v/sub_v #删除目录
etcdctl mk v/sub_v/key1 value1 #在目录v/sub_v下创建键key1以及对应的值value1
etcdctl ls -r #查看所有的目录及子目录以及所有目录和子目录下的键值对
etcdctl ls v/sub_v/ #查看 该目录下的所有键值对
etcdctl get v/sub_v/key1 #v/sub_v 目录下 key1键的值
etcdctl delete v/sub_v/key1 #删除键值对
7、如果中途出现了什么问题,想重新初始化etcd集群,可以这样做
三个节点都做
1、停止etcd 服务
ps -ef |grep etcd
kill -9
2、删除etcd数据目录
rm -rf /opt/etcd/*
3、启动节点重新初始化集群
etcd --config-file=/etc/etcd/etcd.conf
三、安装patroni
3.1、安装python
patroni是用python写的一个组件,需要先安装python,然后再用python安装patroni这个包,三个节点都要安装pythonpython安装见我另一篇博客:
https://www.cnblogs.com/sunjiwei/articles/18066608
3.2、下面开始安装patroni
python3 -m pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple
python3 -m pip install --upgrade setuptools -i https://mirrors.aliyun.com/pypi/simple
python3 -m pip install psycopg2_binary==2.8.6 -i https://mirrors.aliyun.com/pypi/simple
python3 -m pip install patroni[etcd]==2.0.2 -i https://mirrors.aliyun.com/pypi/simple
# 查看patroni安装版本
patroni --version #查看patroni 是否安装成功
如果报错:ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips 26 Jan 2017'. See: https://github.com/urllib3/urllib3/issues/2168
则执行 pip install urllib3==1.26.15 -i https://mirrors.aliyun.com/pypi/simple
# 查看patronictl是否可用
patronictl --help
如果报错:Traceback (most recent call last):
File "./patronictl", line 5, in <module>
from patroni.ctl import ctl
File "/usr/local/python3.7.4/lib/python3.7/site-packages/patroni/ctl.py", line 33, in <module>
from cdiff import markup_to_pager, PatchStream
ModuleNotFoundError: No module named 'cdiff'
则执行 pip install cdiff -i https://mirrors.aliyun.com/pypi/simple
3.3、编辑patroni配置文件
# patroni需要个rewind的账号,这个用户主要是用来主备切换时,timeline不一致的情况会用到
create user rewind_user encrypted password '111111';
这个账号要给相应的权限,或者直接使用postgres这个账号
mkdir /opt/patroni
chown -R postgres:postgres /opt/patroni
vim /etc/patroni.yml
scope: pg_cluster1 # 给集群起个名字
namespace: /service/ # 这个是etcd里会创建的目录,不配置的话默认就存储在/service,就用这个就行,
name: xl001 # 节点名称,可以用主机名
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.167.11:8008 # 本节点ip:8008
etcd:
#Provide host to do the initial discovery of the cluster topology:
hosts: 192.168.167.11:2379,192.168.167.12:2379,192.168.167.13:2379
bootstrap:
# this section will be written into Etcd:/<namespace>/<scope>/config after initializing new cluster
# and all other cluster members will use it as a `global configuration`
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 60000
maximum_lag_on_failover: 1048576
master_start_timeout: 300
synchronous_mode: true
postgresql:
use_pg_rewind: true
use_slots: false
parameters:
max_connections: 8000
max_worker_processes: 16
synchronous_commit: "on"
wal_level: "logical"
hot_standby: "on"
wal_keep_segments: 5120
max_wal_senders: 10
max_replication_slots: 10
wal_log_hints: "on"
archive_mode: "on"
archive_timeout: 1800s
# 初始化数据库用,也可以手动初始化好数据库,不用patroni来初始化
# initdb:
# - encoding: UTF8
# - locale: C
# - lc-ctype: zh_CN.UTF-8
# - data-checksums
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.167.11:5432
data_dir: /opt/pgdata/data
bin_dir: /usr/local/pgsql-12.6/bin
config_dir: /opt/pgdata/data
authentication:
replication:
username: repuser
password: 111111
superuser:
username: postgres
password: 111111
#rewind:
#username: rewind
#password: 111111
callbacks:
on_start: /bin/bash /opt/patroni/pg_cluster1.vip
on_restart: /bin/bash /opt/patroni/pg_cluster1.vip
on_role_change: /bin/bash /opt/patroni/pg_cluster.vip
# 这里解释下callbacks
这个叫回调函数,也就是在数据库发生了启动,重启和主库切换这些动作时,会调用配置的脚本进行其他动作,这里配置的是vip生成脚本,
当主库发生启动,重启,和切换时,都会调用这个脚本,保证主库上一直有vip用于连接。
#watchdog:
# mode: automatic # Allowed values: off, automatic, required
# device: /dev/watchdog
# safety_margin: 5
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
# 修改所属用户
chown postgres:postgres /etc/patroni.yml
三个节点都要配置,注意三个节点不同的参数是:
name: xl001
restapi里的 connect_address: 192.168.167.11:8008
postgresql 里的 connect_address: 192.168.167.11:5432
# 启动patroni
#要在 postgres 账号下 启动
su - postgres
patroni /etc/patroni.yml > /opt/patroni/patroni.log 2>&1 &
#查看pg集群状态
patronictl -c /etc/patroni.yml list pg_cluster1
# 查看某个节点状态等信息
yum install jq -y # jq,格式化输出json格式
curl -s http://192.168.167.161:8008 | jq
{
"state": "running",
"postmaster_start_time": "2023-09-27 10:44:33.542765+08:00",
"role": "master",
"server_version": 120015,
"xlog": {
"location": 167772160
},
"timeline": 1,
"replication": [
{
"usename": "replica",
"application_name": "ob11",
"client_addr": "192.168.167.162",
"state": "streaming",
"sync_state": "async",
"sync_priority": 0
},
{
"usename": "replica",
"application_name": "ob10",
"client_addr": "192.168.167.161",
"state": "streaming",
"sync_state": "sync",
"sync_priority": 1
}
],
"dcs_last_seen": 1695797837,
"database_system_identifier": "7283070492616413950",
"patroni": {
"version": "3.0.4",
"scope": "pg_cluster1"
}
}
附上vip切换脚本,callbacks 那里要用到
vim /opt/patroni/cluster1.vip
#!/bin/bash
VIP=192.168.167.10
GATEWAY=192.168.167.254
DEV=bond0 # 网卡名称
action=$1
role=$2
cluster=$3
log()
{
echo "recon: $*"|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}/24 dev ${DEV} label ${DEV}:2 >/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}/24 dev ${DEV} label ${DEV}:2 >/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 "recon 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 和 psycopg有可能出现版本不兼容的情况,导致patroni启动总是不成功,我自己测试是patroni2.0.2和psycopg2.9.9不兼容。
我测试可以的是 patroni2.0.2 和 psycopg2.8.6,patroni3.0.4 和 psycopg2.9.6这两个组合,python用的都是3.7.4版本。
四、切换测试
情况说明: 现在集群是一主两备流复制集群,在此基础上部署了高可用软件patroni,使用patroni控制了其中一个备节点是同步流复制,另一个节点是异步流复制(注意是patroni控制的,不是pg自己控制的)查看集群状态:
现在集群初始情况如下:
节点名称 | ip | 角色 |
---|---|---|
ob9 | 192.168.167.160 | 主库 |
ob10 | 192.168.167.161 | 同步流备库 |
ob11 | 192.168.167.162 | 异步流备库 |
4.1、场景一测试
停止ob10同步流备库上的patroni和pg数据库,查看ob11异步流备库会不会自动被patroni升级为同步流备库测试原因:主库配置了同步流复制参数,如果主库的同步流备库down了,没有其他的备库顶替成为同步流备库,则主库会hang住,无法写入
停止patroni和pg数据库,先停patroni,再停pg
查看集群状态:
可以看到ob10 down机后ob11自动升级为同步流复制备库了,对主库无影响。主库同步流复制参数也被修改了,不需要重启pg,pg可以用reload命令动态加载配置文件使之生效。
把ob10拉起,查看状态,只需把patroni拉起即可,patroni会自动把pg数据库拉起来的
可以看到ob10 拉起后重新加入集群,并且降级为异步流备库。
4.2、场景二
停止ob11 同步流备库节点 上的 patroni、 pg数据库、 etcd 数据库测试原因:
1、模拟机器坏了
2、Etcd是分布式数据库,停一个节点不影响使用,测试是否如此
依次停止patroni、pg、etcd,如下图
查看集状态:
可以看到有请求ob11节点上的etcd报错,但是集群状态还是正常,因为etcd现在是三节点的分布式数据库,down一个节点不影响使用。ob10现在升级为了同步流复制备库。
依次拉起ob11 上的etcd、patroni,不用手动拉起pg,patroni会自动拉起
查看集群状态,可以看到集群已恢复正常,如下图:
4.3、场景三
模拟主库down,把ob9主库patroni、pg、etcd 依次都停掉查看集群状态:
可以看到现在主库是ob10,并且ob11自动挂到新主库上了,而且是同步流复制。
TL变成了2,说明发生了1次主库切换,原来是1。
拉起ob9上面的etcd 和patroni,注意先拉起etcd
查看集群状态:
可以看到ob9 已重新自动加入集群,并且自动作为异步流备库挂到了新主库上面。
五、一些说明
1、patroni 对数据库的侵入比较大(repmgr这个软件入侵较小),它会修改pg原有的配置文件(pg_hba.conf,postgresql.conf),把它们变成以base.conf结尾的文件,然后根据patroni配置文件中的数据库参数自己生成一份新的配置文件,和老的配置文件一起组成数据库配置,新的配置文件优先级比老配置文件高。
2、patroni可以自己初始化数据库和部署流复制集群(不建议这样做),也可以自己部署好数据库和流复制集群,然后在此基础上部署patroni,这样的话启动patroni时,patroni会重启备库(不会重启主库)
3、用了patroni后,建议修改参数通过patroni 修改,因为要保持整个集群的统一,patroni 可以一次性把集群中所有节点都一起修改并reload,
如果是需要重启生效的参数需要重启pg
patronictl -c /etc/patroni.yml show-config #查看参数的命令
patronictl -c /etc/patroni.yml edit-config #编辑参数的命令,如果写的不对,会保存不成功,如yaml格式,参数名的错误等等
4、repmgr也挺好用,侵入性较小,但是智能化不如patroni,比如数据库down了,patroni会自动尝试拉起,如果主库down了,patroni会先拉起,拉不起来再进行切换,repmgr是主库down了就切换,再比如主库切换后patroni可以自动把老的主库作为备库加入集群,repmgr不可以,需要手动把老的主库作为备库加入集群,所以选哪个看自己需求