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这个包,三个节点都要安装python

python安装见我另一篇博客:
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不可以,需要手动把老的主库作为备库加入集群,所以选哪个看自己需求
posted @ 2024-11-07 16:31  有形无形  阅读(7)  评论(0编辑  收藏  举报