PostgreSQL Keepalived 配置

1 Keepalived 服务配置

1.1 node1 配置

1、编辑keepalived.conf

su - root
 vi /etc/keepalived/keepalived.conf

2、配置如下

! Configuration File for keepalived
global_defs {
    # 路由器标识,一般不用改,也可以写成每个主机自己的主机名
   router_id LVS_DEVEL
   # 严格执行VRRP 协议。
   vrrp_skip_check_adv_addr
   # 使用 FIFO 先进先出算法的优先级
   vrrp_strict
   # Master发送 NA 信息的延迟
   vrrp_garp_interval 0
   # Master发送 ARP/NA 消息延迟
   vrrp_gna_interval 0
}

# 定义用于实例执行的脚本内容,比如可以在线降低优先级,用于强制切换
vrrp_script check_pg_alived { 
script "/soft/scripts/pg_monitor.sh" 
interval 10 
fall 3
}

# 一个vrrp_instance就是定义一个虚拟路由器的,实例名称
vrrp_instance VI_1 {
    # 定义初始状态,可以是MASTER或者BACKUP
    state MASTER
    #非抢占模式
    nopreempt
    # 工作接口,通告选举使用哪个接口进行
    interface ens192
    # 虚拟路由ID,如果是一组虚拟路由就定义一个ID,如果是多组就要定义多个,而且这个虚拟
    # ID还是虚拟MAC最后一段地址的信息,取值范围0-255
    virtual_router_id 51
    #权重 如果你上面定义了MASTER,这里的优先级就需要定义的比其他的高
    priority 100
    #通告频率 单位s
    advert_int 1
    # 通信认证机制,这里是明文认证还有一种是加密认证
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    # 设置虚拟VIP地址
    virtual_ipaddress {
        192.168.10.172
    }
    # 追踪脚本,通常用于去执行上面的vrrp_script定义的脚本内容
    track_script {
      check_pg_alived
    }
   # 如果主机状态变成`Master|Backup|Fault`之后会去执行的通知脚本,脚本要自己写
    notify_master "/soft/scripts/failover.sh"
    notify_fault  "/soft/scripts/fault.sh"
}

以上是Keepalived主节点的配置。

1.2 node2 配置

1、priority参数改成90 。
2、state改为BACKUP
3、其余参数配置一样。

! Configuration File for keepalived
global_defs {
   # 路由器标识,一般不用改,也可以写成每个主机自己的主机名
   router_id LVS_DEVEL
   # 严格执行VRRP 协议。
   vrrp_skip_check_adv_addr
   # 使用 FIFO 先进先出算法的优先级
   vrrp_strict
   # Master发送 NA 信息的延迟
   vrrp_garp_interval 0
   # Master发送 ARP/NA 消息延迟
   vrrp_gna_interval 0
}

# 定义用于实例执行的脚本内容,比如可以在线降低优先级,用于强制切换
vrrp_script check_pg_alived {
script "/soft/scripts/pg_monitor.sh"
interval 10
fall 3
}

# 一个vrrp_instance就是定义一个虚拟路由器的,实例名称
vrrp_instance VI_1 {
    # 定义初始状态,可以是MASTER或者BACKUP
    state BACKUP
    #非抢占模式
    nopreempt
    # 工作接口,通告选举使用哪个接口进行
    interface ens192
    # 虚拟路由ID,如果是一组虚拟路由就定义一个ID,如果是多组就要定义多个,而且这个虚拟
    # ID还是虚拟MAC最后一段地址的信息,取值范围0-255
    virtual_router_id 51
    #权重 如果你上面定义了MASTER,这里的优先级就需要定义的比其他的高
    priority 90
    #通告频率 单位s
    advert_int 1
    # 通信认证机制,这里是明文认证还有一种是加密认证
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    # 设置虚拟VIP地址
    virtual_ipaddress {
        192.168.10.172
    }
    # 追踪脚本,通常用于去执行上面的vrrp_script定义的脚本内容
    track_script {
      check_pg_alived
    }
   # 如果主机状态变成`Master|Backup|Fault`之后会去执行的通知脚本,脚本要自己写
    notify_master "/soft/scripts/failover.sh"
    notify_fault  "/soft/scripts/fault.sh"
}

2 keepalived 脚本配置

Note

以下操作在node1 上执行,使用scp 方式拷贝到所有节点上。

2.1 数据库监控脚本(pg_monitor.sh)

此脚本每隔10秒执行一次,执行频率由 keepalived.conf 配置文件中 interval 参数设置,脚本主要作用为:

1、检测主库是否存活。
2、更新sr_delay表last_alive字段为当前探测时间。
3、具体内容如下:

su - root
vi /soft/scripts/pg_monitor.sh 

--------------------input------------------------------
#!/bin/bash 
# 配置环境变量 
PGPORT=5432 
PGUSER=keepalived 
PGDB=keepalived 
PGPWD='keepalived'
Lang=en_US.utf8 
PGHOME=/usr/local/pgsql/ 
LD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib 
PATH=$PGHOME/bin:$PATH:.
MONITOR_LOG="/soft/scripts/pg_monitor.log" 
SQL1="UPDATE sr_delay SET last_alive= now();" 
SQL2='SELECT 1;' 
keeplognums=30000



#此脚本不检查备库存活状态,如果是备库库则退出 

standby_flg=`psql -p$PGPORT -U$PGUSER  -At -c "SELECT pg_is_in_recovery();"` 
if [ ${standby_flg} == 't' ]; 
then 
 echo ‐e "`date +%F\ %T`: This is a standby database, exit!\n" > $MONITOR_LOG 
 exit 0 
fi
export PGPASSWORD=$PGPWD
#主库更新sr_delay 表 
echo $SQL1 | psql -At -p $PGPORT -U $PGUSER -d $PGDB >> $MONITOR_LOG 
#判断主库是否可用
echo $SQL2 | psql -At -p $PGPORT -U $PGUSER -d $PGDB 
if [ $? -eq 0 ] ;
then 
 echo -e "`date +%F\ %T`:Primary db is health." >> $MONITOR_LOG 
 exit 0 
else 
 echo -e "`date +%F\ %T`:Attention: Primary db is not health!" >> $MONITOR_LOG 
 exit 1 
fi

#日志保留 keeplognums 行
if [ ! -f ${MONITOR_LOG} ] ;then touch ${MONITOR_LOG};fi 
lognums=`cat ${MONITOR_LOG} |wc -l`
catnum=$((${lognums} -${keeplognums}))
if [[ $lognums -gt ${keeplognums} ]] ; then sed -i "1,${catnum}d" ${MONITOR_LOG}; fi

此脚本当主库没有存活,进行主备切换。脚本主要作用为:

1、检测主库是否存活。
2、当 sr_allowed_delay_time 超过100秒后。
3、执行主备切换。

vi /soft/scripts/failover.sh

--------------------input------------------------------
#!/bin/bash
export PGPORT=5432
export PGUSER=keepalived
export PG_OS_USER=postgres
export PGDBNAME=keepalived
export LANG=en_US.UTF-8
export PGPATH=/usr/local/pgsql/bin/
export PATH=$PATH:$PGPATH
export PGMIP=127.0.0.1
LOGFILE='/soft/scripts/failover.log'
# 主备数据库同步时延,单位为秒
sr_allowed_delay_time=100

SQL1='SELECT pg_is_in_recovery FROM pg_is_in_recovery();'
SQL2="SELECT count(1) as delay_time FROM sr_delay WHERE now()<(last_alive + interval '${sr_allowed_delay_time} seconds');"
#SQL2="select count(1) as delay_time from sr_delay where now()<(last_alive + interval '100 seconds');"
sleep $sr_allowed_delay_time
db_role=`echo $SQL1 | psql -At -p $PGPORT -U $PGUSER -d $PGDBNAME -w`
db_sr_delaytime=`echo $SQL2 | psql -p $PGPORT -d $PGDBNAME -U $PGUSER -At -w`
SWITCH_COMMAND='pg_ctl promote -D /usr/local/pgsql/data'
# 如果为备库,且延迟大于指定时间则切换为主库

if [ ${db_role} == 'f' ];
then
echo -e `date +"%F %T"` "Attention:The current postgreSQL DB is master database,cannot switched!" >> $LOGFILE
exit 0
fi

if [ $db_sr_delaytime -gt 0 ];
then
echo -e `date +"%F %T"` "Attention:The current master database is health,the standby DB cannot switched!" >> $LOGFILE
exit 0
fi

if [ !$db_sr_delaytime ];
then
echo -e `date +"%F %T"` "Attention:The current database is statndby,ready to switch master database!" >> $LOGFILE
su - $PG_OS_USER -c "$SWITCH_COMMAND"
elif [ $? -eq 0 ];
then
echo -e `date +"%F %T"` "success:The current standby database successed to switched the primary PG database !" >> $LOGFILE
exit 0
else
echo -e `date +"%F %T"` "Error: the standby database failed to switch the primary PG database !,pelease checked it!" >> $LOGFILE
exit 1
fi

此脚本主要作用为:
1、检测数据库是否存活。
2、当数据库宕机时,将会关闭本节点的数据库和keepalived 服务。

vi /soft/scripts/fault.sh

--------------------input------------------------------
#!/bin/bash
GFILE=/soft/scripts/pg_db_fault.log 
PGPORT=5432 
PGMIP=127.0.0.1
echo -e `date +"%F %T"` "Error:Because of the priamry DB happend some unknown problem,So turn off the PostgreSQL Database!" >> $LOGFILE 

#PGPID="`head -n1 /usr/local/pgsql/data/postmaster.pid`" 

systemctl stop keepalived 

#kill -9 $PGPID 
if [ $? -eq 0 ] ;
then 
echo -e `date +"%F %T"` "Error:Because of the priamry DB happend some unknown problem,So turn off the PostgreSQL Database!" >> $LOGFILE
systemctl stop keepalived 
exit 1 
fi

2.4 脚本授权并拷贝到其他节点

chmod +x /soft/scripts/pg_monitor.sh 
chmod +x /soft/scripts/failover.sh 
chmod +x /soft/scripts/fault.sh
ll /soft/scripts/*
scp /soft/scripts/*  node2:/soft/scripts

2.5 启动keepalived 服务

所有节点执行。

su - root
systemctl start keepalived.service ;
systemctl enable keepalived.service ;
systemctl status keepalived.service ;

到此为止:打快照为 keepalived install

3 切换演练 (node1为主)

3.1 node1 数据库停止

su - postgres
pg_ctl -D $PGDATA stop

3.2 node1 的Keepalive 自动关闭

systemctl status keepalived.service ;

3.3 node2 成为 Master

psql -U postgres -d testdb
SELECT  pg_is_in_recovery();
ALTER SYSTEM SET synchronous_commit ='local';
SELECT pg_reload_conf();
 pg_reload_conf
----------------
 t
(1 row)
-------------------


INSERT INTO t01 VALUES (1);
INSERT 0 1

3.4 node1 数据库修复

使用pg_rewind 同步新主库的数据到原主库。

pg_ctl start 
pg_ctl stop 
pg_rewind -D $PGDATA --source-server 'host=node2 port=5432 user=postgres password=postgres dbname=testdb' -P
Note

如果这个地方咱们的wal日志已经被覆盖了了很多,那么就需要将我们的备份日志/archive/给scp过来。
scp postgres@192.168.254.129 :/data/pg_archive/00000002* /data/pg_archive/
(此时需要注意新主和备上面TimeLineID的差异。并且这种情况需要在配置文件中加上:
restore_command='cp /data/pg_archive/%f %p'

3.5 node1 重新加入到集群中

1、node1 修改postgresql.conf

vi $PGDATA/postgresql.conf
//屏幕输出:

primary_conninfo = 'user=repl password=repl host=node2 port=5432 application_name=node1'
recovery_target_timeline='latest'
primary_slot_name ='slot_node1'


3、node1 配置 standby.signal 文件

touch $PGDATA/standby.signal 

2、node2 创建复制槽

psql -h node2 -U postgres -d testdb
SELECT * FROM pg_create_physical_replication_slot('slot_node1');
SELECT * FROM pg_replication_slots;

4、node1 检查主备wal sender和receiver是否正常:

 pg_ctl -D $PGDATA  start
 ps axu |grep walreceiver

5、node1 启动Keepalived,查看Keepalived状态。

systemctl  start keepalived
systemctl  status keepalived

4 切换演练(node2为主)

在做切换前,先要检查 node1 和 node2 上面的Keepalived正常以及vip和主从正常,在 node2 上停掉数据库:

4.1 node2 数据库停止

pg_ctl -D $PGDATA stop

4.2 node1 的Keepalive 自动关闭

systemctl status keepalived.service ;

4.3 node1 成为 Master

psql -U postgres -d testdb
SELECT  pg_is_in_recovery();
ALTER SYSTEM SET synchronous_commit ='local';
SELECT pg_reload_conf();
 pg_reload_conf
----------------
 t
(1 row)
-------------------


INSERT INTO t01 VALUES (1);
INSERT 0 1

4.4 node2 数据库修复

使用pg_rewind 同步新主库的数据到原主库:

pg_ctl start 
pg_ctl stop 
pg_rewind -P -D $PGDATA --source-server 'host=node1 port=5432 user=postgres password=postgres dbname=testdb'
Note

如果这个地方咱们的wal日志已经被覆盖了了很多,那么就需要将我们的备份日志/archive/给scp过来。
scp postgres@192.168.254.129 :/data/pg_archive/00000002* /data/pg_archive/
(此时需要注意新主和备上面TimeLineID的差异。并且这种情况需要在配置文件中加上:
restore_command='cp /data/pg_archive/%f %p'

4.5 node2 重新加入到集群中

1、node2 修改postgresql.conf

vi $PGDATA/postgresql.conf
//屏幕输出:

primary_conninfo = 'user=postgres password=postgres host=node1 port=5432 application_name=node2'
recovery_target_timeline='latest'
primary_slot_name ='slot_node2'


3、node2 配置 standby.signal 文件

touch $PGDATA/standby.signal 

2、node1 创建复制槽

psql -h node1 -U postgres -d testdb
SELECT * FROM pg_create_physical_replication_slot('slot_node2');
SELECT * FROM pg_replication_slots;

4、node1 检查主备wal sender和receiver是否正常:

 pg_ctl -D $PGDATA  start
 ps axu |grep walreceiver
 //屏幕输出:
 postgres    3076  0.0  0.1 287660  9812 ?        Ss   10:33   0:00 postgres: walreceiver   streaming 0/A029E50

5、node1 启动Keepalived,查看Keepalived状态。

systemctl  start keepalived
systemctl  status keepalived

5 小结

1、可以完善failover时自己的延迟切换逻辑。
2、可以完善pg_rewind的实现,用脚本代替手动方式。
3、Keepalived较为灵活,能够在脚本上添加更多校验和自己的规则。