本文主要讲解一种Postgresql高可用实现方案。由于项目需要,我们需要在短时间实现底层Postgresql DB的高可用,而网络上大多数的解决方案比较复杂,需要深入了解Postgresql。
背景
我们原先只是使用Postgresql DB来存放一些kong的配置信息,作为单实例以k8s pod的形式进行部署使用。这样,在项目中postgresql DB就存在单点故障的隐患。为了解决这个问题,我们需要实现高可用的Postgresql。本文中,利用PostDock来实现master-slave架构的Postgresql高可用集群。
下面是实现后的架构图。
PostDock
PostDock项目的代码参见GitHub项目PostDock,该项目在云和docker环境中提供高可用和自愈作用的Postgresql集群。
项目中使用PostDock提供的k8s中的样例example2-single-statefulset并对其进行了简化。简化内容如下:
- 使用hostPath来替换样例中的PVC;
- 去除样例中的configMap和secret,将其直接配置在SatatefulSet和Deployment中;
- 将所有k8s资源创建在default namespace,不单独创建其他namespace;
简化后的PostDock样例包含2个service,一个具有3个Pod的Postgresql StatefulSet和一个具有2个Pod的Pgpool Deployment。postgresql service为headless service,对应一个postgresql statefuleset,其中包含3个Postgresql Pod,1个master DB,2个slave DB; pgpool service对应一个pgpool deploy,其中包含2个pgpool pod。
-Postgresql Service
apiVersion
-Postgresql SatetfulSet
apiVersion
-pgpool service
apiVersion
-pgpool Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: postgresql-database-pgpool
labels:
name: database-balancer
node: pgpool
system: postgresql
app: postgresql
spec:
replicas: 2
revisionHistoryLimit: 5
selector:
matchLabels:
name: database-balancer
node: pgpool
system: postgresql
app: postgresql
template:
metadata:
name: database-pgpool
labels:
name: database-balancer
node: pgpool
system: postgresql
app: postgresql
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
name: database-balancer
node: pgpool
system: postgresql
app: postgresql
namespaces:
- default
topologyKey: kubernetes.io/hostname
containers:
- name: database-pgpool
image: postdock/pgpool:latest-pgpool37-postgres11
livenessProbe:
exec:
command: ['bash', '-c', '/usr/local/bin/pgpool/has_write_node.sh && /usr/local/bin/pgpool/has_enough_backends.sh']
initialDelaySeconds: 600
timeoutSeconds: 10
periodSeconds: 30
successThreshold: 1
failureThreshold: 3
imagePullPolicy: Always
resources:
requests:
memory: "100Mi"
cpu: "100m"
ports:
- containerPort: 5432
env:
- name: "CONFIGS"
value: "num_init_children:60,max_pool:4,client_idle_limit:900,connection_life_time:300"
- name: "PCP_USER"
value: "pcp_user"
- name: "PCP_PASSWORD"
value: "pcp_pass"
- name: "CHECK_USER"
value: "rain"
- name: "CHECK_PASSWORD"
value: "passwd"
- name: "DB_USERS"
value: "wide:pass"
- name: "BACKENDS"
value: "0:postgresql-db-node-0.postgresql-db-service:5432:1:/var/lib/postgresql/data:ALLOW_TO_FAILOVER,1:postgresql-db-node-1.postgresql-db-service:5432:1:/var/lib/postgresql/data:ALLOW_TO_FAILOVER,2:postgresql-db-node-2.postgresql-db-service:5432:1:/var/lib/postgresql/data:ALLOW_TO_FAILOVER"
将上面的yaml文件使用kubectl命令执行后生成像一个的k8s resource。
kubectl apply -f *.yaml
连接Postgresql高可用集群
创建高可用集群后,我们需要验证高可用集群的可用性,即在master, slave DB挂掉后,Postgresql集群是否能正确竞选出新的master DB并继续提供DB服务。
-安装Postgresql client
为了验证Postgresql高可用集群,我们需要赞centos7上安装postgresql client来连接DB。
centos7上安装postgresql: 详情参见安装Postgresql。注意:需要安装相同版本的Postgresql Client才能访问DB。
yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm -y
yum install postgresql11-server -y
注意:若安装过程中出现证书问题,请检查机器的系统时间date,因为证书有时效性,系统时间不对可能导致证书验证不通过。关于如何修改和同步集群上的时间,请参考之前的文章服务集群时间同步 - Chrony。
卸载Postgresql DB,使用下面的命令。
yum remove postgresql*
-获取DB及Service信息
为了访问创建的Postgresql集群,需要获得我们之前创建的k8s资源信息。访问Postgresql集群,可以通过Service postgresql-pgpool-service来访问,也可以直接访问postgresql-db-node pod来访问,只是直接访问pod会因为访问的不是master DB时而不能执行写操作。
| grep postgresql
postgresql-db-service-sophondeps2 ClusterIP None <none> 5432/TCP 20h
postgresql-pgpool-service-sophondeps2 ClusterIP 10.10.10.129 <none> 5432/TCP 20h
$ kubectl get po -o wide
通过上面信息,我们可以通过下面命令来连接Postgresql集群。在输入密码passwd后以user rain的身份连接到DB rain。当然,也可以通过pod的IP作为这里的host来分别访问集群下的指定DB,master DB和slave DB,只是访问slave DB时不能执行写操作。
$ psql -h 10.10.10.129 -p 5432 -U rain rain
用户 sophon 的口令:passwd
注意:Postgresql DB通常都会创建user postgres及其相应DB,有时需要使用下面命令切换到该用户来连接DB。
sudo -i -u postgres
创建Postgresql cluster后,我们需要连接DB后执行下面的命令来获得高可用cluster的架构信息。由下面的信息可知,3个Postgresql Pod正在执行,其中postgresql-db-node-sophondeps2-0 为master DB,其他2个为slave DB。
sophon
-使用DB
在master-slave架构中,master负责读写,而slave只负责读取。下面时操作Postgresql DB的一些常规操作,SQL跟其他DB一样。
查看所有DB:
list
查看当前连接的DB信息:
conninfo
切换当前DB到DBNAME:
c DBNAME
查看DB中table:
d
查看表结构,相当于desc tblname,show columns from tbname
d tblname
di 查看索引
退出 q
验证Postgresql Cluster高可用
高可用验证当master DB或slave DB挂掉时,cluster仍然能够继续使用。
-slave DB挂掉
我们使用下面命令来停止slave DB. 我们发现slave DB所在Pod挂掉后,马上会被重新创建(k8s中statefulset中的pod会被重建),新创建的Pod具有相同的名称,并被作为slave DB重新添加进cluster。而且,在slave DB挂掉重启的整个过程,Postgresql cluster仍然能够正常访问。
kubectl delete po postgresql-db-node-sophondeps2-2
-master DB挂掉
我们使用下面命令来停止master DB.
kubectl delete po postgresql-db-node-sophondeps2-0
master DB挂掉后,Postgresql Cluster会在剩下的2个slave DB中选取master DB, 新master DB来负责读写,而且pgpool service会自动使用新master。旧master DB会因为重启失败进入Error状态,不会重新回到Postgresql Cluster。
此时,Postgresql cluster的架构如下。发现postgresql-db-node-0为down状态,cluster只有2个DB,其中postgresql-db-node-1为master DB,而postgresql-db-node-2为slave DB.
sophon=# select * from repmgr.nodes;
node_id | upstream_node_id | active | node_name | type | location | priority | conninfo
| repluser | slot_name | config_file
---------+------------------+--------+----------------------+---------+----------+----------+---------------------------------------------------------------------------------------------------------------
--------+----------+------------------+------------------
1001 | | t | postgresql-db-node-1 | primary | default | 100 | user=sophon password=passwd host=postgresql-db-node-1.postgresql-db-service dbname=sophon port=5432 connect_ti
meout=2 | sophon | repmgr_slot_1001 | /etc/repmgr.conf
1002 | 1001 | t | postgresql-db-node-2 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-2.postgresql-db-service dbname=sophon port=5432 connect_ti
meout=2 | sophon | repmgr_slot_1002 | /etc/repmgr.conf
sophon=# select * from pg_stat_replication;
pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn | replay_ls
n | write_lag | flush_lag | replay_lag | sync_priority | sync_state
-----+----------+---------+----------------------+----------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------+----------
--+-----------+-----------+------------+---------------+------------
682 | 16386 | sophon | postgresql-db-node-2 | 100.111.40.224 | | 57974 | 2020-06-23 08:45:59.437912+00 | | streaming | 0/50415E0 | 0/50415E0 | 0/50415E0 | 0/50415E0
| | | | 0 | async
sophon=# show pool_nodes;
node_id | hostname | port | status | lb_weight | role | select_cnt | load_balance_node | replication_delay
---------+--------------------------------------------+------+--------+-----------+---------+------------+-------------------+-------------------
0 | postgresql-db-node-0.postgresql-db-service | 5432 | down | 0.333333 | standby | 3 | false | 0
1 | postgresql-db-node-1.postgresql-db-service | 5432 | up | 0.333333 | primary | 6 | true | 0
2 | postgresql-db-node-2.postgresql-db-service | 5432 | up | 0.333333 | standby | 3 | false | 0
-将旧master DB重新添加进cluster
将旧master DB重新添加进cluster需要人工干预。干预过程如下:
- 将另一个slave所在节点的/var/project-deps/postgresql-data中所有文件使用ssh拷贝到旧master DB所在host对应的位置(先删除原来所有文件)。
下面命令在另一个slave DB所在host上执行,其中10.11.50.15为旧master DB所在host的IP。
$ cd /var/project_deps/postgresql-data/
$ scp -r ./* root@10.11.50.15:/var/project_deps/postgresql-data/
- 修改拷贝过来的recovery.conf文件
Master DB下recovery.conf会被修改为recovery.done,recovery.conf文件只出现在slave DB中。将该文件中的primary_slot_name配置删除,并修改primary_conninfo中 的application_name=''postgresql-db-node-0'' 为合适的node name。
修改完,重启该节点的postgresql pod即可。
- 验证旧master DB作为slave DB添加进cluster
连接新的master DB并执行下面的命令来验证旧master DB已经添加进集群。可知,postgresql-db-node-sophondeps2-0作为slave DB重新添加进cluster。
psql -h 10.11.24.18 -p 5432 -U rain rain 连接主node
sophon=# select * from pg_stat_replication;
pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | backend_xmin | state | sent_lsn | write_lsn | flush_lsn
| replay_lsn | write_lag | flush_lag | replay_lag | sync_priority | sync_state
-------+----------+---------+----------------------------------+-------------+-----------------+-------------+-------------------------------+--------------+-----------+-----------+-----------+-----------
+------------+-----------+-----------+------------+---------------+------------
19046 | 16386 | sophon | postgresql-db-node-sophondeps2-0 | 10.11.50.12 | | 50384 | 2020-07-08 07:34:06.353302+00 | 630 | streaming | 0/50480D0 | 0/50480D0 | 0/50480D0
| 0/50480D0 | | | | 0 | async
13678 | 16386 | sophon | postgresql-db-node-sophondeps2-2 | 10.11.24.12 | | 38052 | 2020-07-08 07:07:40.862402+00 | | streaming | 0/50480D0 | 0/50480D0 | 0/50480D0
| 0/50480D0 | | | | 0 | async
(2 行记录)
sophon=# select * from repmgr.nodes;
node_id | upstream_node_id | active | node_name | type | location | priority | conninfo
| repluser | slot_name | config_file
---------+------------------+--------+----------------------------------+---------+----------+----------+---------------------------------------------------------------------------------------------------
--------------------------------------------+----------+------------------+------------------
1001 | | t | postgresql-db-node-sophondeps2-1 | primary | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-1.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1001 | /etc/repmgr.conf
1002 | 1001 | t | postgresql-db-node-sophondeps2-2 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-2.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1002 | /etc/repmgr.conf
1000 | 1001 | t | postgresql-db-node-sophondeps2-0 | standby | default | 100 | user=sophon password=passwd host=postgresql-db-node-sophondeps2-0.postgresql-db-service-sophondeps
2 dbname=sophon port=5432 connect_timeout=2 | sophon | repmgr_slot_1000 | /etc/repmgr.conf
(3 行记录)
数据迁移
高可用的Postgresql cluster创建好之后,我们需要将之前的单个postgresql DB中的老数据迁移到新创建的Postgresql Cluster中。
数据迁移使用pg_dump和psql命令来完成。使用pg_dump命令来将旧数据库中的老数据dump到文件中,然后在新数据库中使用psql来导入老数据。
注意:pg_dump需要使用相同版本,否则不能dump。同时,数据导入应该使用postgresql cluster的master DB进行导入,slave DB不能执行写操作。
-老数据库的数据导出
下面示例,连接到老数据库并导出数据库rain中的所有数据到文件rain.sql中。
"host=100.120.8.201 port=5432 user=rain password=passwd dbname=rain" -f rain.sql
-新数据库导入数据
我们需要连接新数据库并导入所有数据。
$ psql -h 10.96.91.35 -p 5432 -U rain rain < rain.sql
至此,我们完成了Postgresql DB的高可用架构并完成老数据迁移。