keepalived 做mysql的主从切换
适用于mysql master-slave的主从架构
一、过程简介:
1、通过keepalived配置VIP高可用,keepalived均设置为BACKUP ,nopreempt非抢占模式。 2、master上监控mysql 3306端口的状态,当检测到3306 端口停止后,停止keepalived,vip自动转移到slave上。 3、slave获取到vip升级为master后,执行changemasterdb脚本,将本地mysql库升级为master库,同时将其他从库的主库修改为本库的从库。
二、主从架构图
2.1、切换前的
2.2、切换后的
三、主要配置文件
主要配置文件及作用说明
3.1 keepalived.conf.master 用于当前为master的数据库上的keepalived的配置文件
[root@t156 keepalived]# more keepalived.conf.master ! Configuration File for Keepalived ! --------------------------------------------------------------------------- ! GLOBAL ! --------------------------------------------------------------------------- global_defs { ! this is who emails will go to on alerts notification_email { wanghengzhi@hw801.com ! add a few more email addresses here if you would like } notification_email_from wanghengzhi@hw801.com ! mail relay server smtp_server 127.0.0.1 smtp_connect_timeout 30 ! each load balancer should have a different ID ! this will be used in SMTP alerts, so you should make ! each router easily identifiable router_id LVS_170 vrrp_mcast_group4 224.0.0.18 lvs_sync_daemon eth1 VI1_LVS_DB script_user root } vrrp_script check_mysql { script "/etc/keepalived/mysql_check.sh" interval 10 } vrrp_instance VI1_LVS_DB { !state MASTER state BACKUP interface eth1 track_interface { eth1 } ! interface to run LVS sync daemon on ! lvs_sync_daemon_interface eth1 !mcast_src_ip 192.168.1.156 ! each virtual router id must be unique per instance name! virtual_router_id 170 ! MASTER and BACKUP state are determined by the priority ! even if you specify MASTER as the state, the state will ! be voted on by priority (so if your state is MASTER but your ! priority is lower than the router with BACKUP, you will lose ! the MASTER state) ! I make it a habit to set priorities at least 50 points apart ! note that a lower number is lesser priority - lower gets less vote priority 100 ! how often should we vote, in seconds? advert_int 1 ! send an alert when this instance changes state from MASTER to BACKUP smtp_alert ! this authentication is for syncing between failover servers ! keepalived supports PASS, which is simple password ! authentication or AH, which is the IPSec authentication header. ! Don't use AH yet as many people have reported problems with it authentication { auth_type PASS auth_pass 111111 } ! these are the IP addresses that keepalived will setup on this ! machine. Later in the config we will specify which real ! servers are behind these IPs without this block, keepalived ! will not setup and takedown any IP addresses virtual_ipaddress { 192.168.1.170/24 dev eth1 } nopreempt !preempt_delay 2 track_script { check_mysql } !notify_master "/etc/keepalived/changemasterdb.sh" }
注:
state BACKUP #state 全部为BACKUP priority 100 #当前是master的优先级高 vrrp_script check_mysql { script "/etc/keepalived/mysql_check.sh" #vrrp_script 检测脚本 interval 10 } nopreempt # vip 设置为非抢占模式 track_script { check_mysql # 调用 vrrp_script检测脚本 }
3.2 keepalived.conf.slave 用于当前为slave的数据库上的keepalived的配置文件
[root@t168 keepalived]# more keepalived.conf.backup ! Configuration File for Keepalived ! --------------------------------------------------------------------------- ! GLOBAL ! --------------------------------------------------------------------------- global_defs { ! this is who emails will go to on alerts notification_email { wanghengzhi@hw801.com ! add a few more email addresses here if you would like } notification_email_from wanghengzhi@hw801.com ! mail relay server smtp_server 127.0.0.1 smtp_connect_timeout 30 ! each load balancer should have a different ID ! this will be used in SMTP alerts, so you should make ! each router easily identifiable router_id LVS_170 vrrp_mcast_group4 224.0.0.18 lvs_sync_daemon eth1 VI1_LVS_DB script_user root } vrrp_instance VI1_LVS_DB { state BACKUP interface eth1 track_interface { eth1 } ! interface to run LVS sync daemon on ! lvs_sync_daemon_interface eth1 !mcast_src_ip 192.168.1.168 ! each virtual router id must be unique per instance name! virtual_router_id 170 ! MASTER and BACKUP state are determined by the priority ! even if you specify MASTER as the state, the state will ! be voted on by priority (so if your state is MASTER but your ! priority is lower than the router with BACKUP, you will lose ! the MASTER state) ! I make it a habit to set priorities at least 50 points apart ! note that a lower number is lesser priority - lower gets less vote priority 90 ! how often should we vote, in seconds? advert_int 1 ! send an alert when this instance changes state from MASTER to BACKUP smtp_alert ! this authentication is for syncing between failover servers ! keepalived supports PASS, which is simple password ! authentication or AH, which is the IPSec authentication header. ! Don't use AH yet as many people have reported problems with it authentication { auth_type PASS auth_pass 111111 } ! these are the IP addresses that keepalived will setup on this ! machine. Later in the config we will specify which real ! servers are behind these IPs without this block, keepalived ! will not setup and takedown any IP addresses virtual_ipaddress { 192.168.1.170/24 dev eth1 } nopreempt notify_master "/etc/keepalived/changemasterdb.sh" }
注:
state BACKUP #state 全部为BACKUP priority 90 #当前是master的优先级高 nopreempt # vip 设置为非抢占模式 notify_master "/etc/keepalived/changemasterdb.sh" # 切换为master后执行的脚本
3.3 mysql_check.sh 用于主库上mysql 3306端口的检测
[root@t168 keepalived]# more mysql_check.sh #!/bin/bash set -o nounset #@Author : wanghz #@Time : 2021/9/1 12:05 # define restricted path PATH="/bin:/usr/bin:/sbin:/usr/sbin" # adirname - return absolute dirname of given file adirname() { odir=`pwd`; cd `dirname $1`; pwd; cd "${odir}"; } PSW="123456" PORT='3306' MYSQL_BIN=`which mysql` MYSQL_MASTER_BIN="${MYSQL_BIN} -uroot -p${PSW} -S /tmp/mysql${PORT}.sock" count=1 while true do ${MYSQL_MASTER_BIN} -e "show status\G;" >/dev/null 2>&1 Status=$? ps aux|grep mysqld|grep -v grep >/dev/null 2>&1 Grep=$? if [ ${Status} = 0 ] && [ ${Grep} = 0 ] then exit 0 else if [ ${Status} = 1 ] && [ ${Grep} = 0 ] then exit 0 else if [ ${count} -gt 5 ] then echo ${count} break fi let count+=1 continue fi fi done `which service` keepalived stop
3.4 changemasterdb.sh 用于到vip 切换到从库上,进行的一系列操作
[root@t168 keepalived]# more changemasterdb.sh #!/bin/bash set -o nounset #数据库的端口 PORTS=( 3306 3308 ) PSW="123456" REPL_USER="repl" REPL_USER_PSW="repl123456" #ANSIBLE_HOST_NAMES=( "168" ) #同 SLAVE_HOST_IP ,需要在/etc/ansible/hosts里配置 ANSIBLE_HOST_NAMES=( "159" ) #同 SLAVE_HOST_IP ,需要在/etc/ansible/hosts里配置 #[root@t168 ~]# more /etc/ansible/hosts #[159] #192.168.1.159 ############################################## # # # 配置修改开始 # # # ############################################## LOCAL_HOST_IP="192.168.1.168" ##升级为主库的现有IP地址 MASTER_HOST_IP='192.168.1.170' ## 主库的IP地址 SLAVE_HOST_IP="192.168.1.157" ##原另一个从库的IP MYSQL_BIN=`which mysql` MYSQL_MASTER_BIN='${MYSQL_BIN} -uroot -p${PSW}' ############################################## # # # 配置修改结束 # # # ############################################## # define restricted path PATH="/usr/local/mysql/bin:/bin:/usr/bin:/sbin:/usr/sbin" # adirname - return absolute dirname of given file adirname() { odir=`pwd`; cd `dirname $1`; pwd; cd "${odir}"; } MYNAM=`basename "$0"` MYDIR=`adirname "$0"` MYLOG_PATH="${MYDIR}/logs" MYLOG="${MYLOG_PATH}/${MYNAM}_`date +%F`.log" for D in ${MYLOG_PATH} do if [ ! -d ${D} ] ; then mkdir -p ${D} echo -e "Mkdir ${D}" >> ${MYLOG} fi done # --------- # functions # --------- #日志函数 function L(){ message="$(date -d today +"%Y-%m-%d %H:%M:%S") - $1" echo -e "\033[34m $message \033[0m" && echo $message >> ${MYLOG} } #主库挂,从库升级为主库 #1、keepalived 切vip到从库上 #2、脚本停止slave同步 #3、重看当前从库的pos和bin-logs记录 #4、修改从库为读写状态 #5、修改另一个从库来连接新的主库并查看同步状态 function CheckMasterIP(){ #1、keepalived 切vip到主库上。keepalived为非抢占模式,切回来不自动切回去。 #获取本地是否有vip VIP=`/bin/hostname -I|grep "${MASTER_HOST_IP}"` if [ "x${VIP}" == "x" ]; then L "vip${MASTER_HOST_IP}没有切到这个服务器上。" exit 1 fi L "vip${MASTER_HOST_IP}切换到该服务器上成功" return 0 } function StopSlave(){ #2、停止当前slave同步 ${MYSQL_MASTER_BIN} -e "stop slave" ${MYSQL_MASTER_BIN} -e "reset slave" L "${MYSQL_MASTER_BIN} -e \"stop slave\"" SLAVESTATUS=`${MYSQL_MASTER_BIN} -e "show slave status\G"|grep "Slave_SQL_Running"|grep -v "State"|awk '{print $NF}'` L "${SLAVESTATUS} ${MYSQL_MASTER_BIN} -e \"show slave status\G\"|grep \"Slave_SQL_Running\"|grep -v \"State\"|awk '{print \$NF}'" if [ "${SLAVESTATUS}" != "No" ];then L "当前db${IPORT}停止slave失败。" exit 1 fi L "停止当前DB${IPORT}的slave同步成功" return 0 } function GetNewMasterPosNum(){ #3、重看当前升级为主库的posnum记录 #posnum Pos=`${MYSQL_MASTER_BIN} -e "show master status\G"|grep "Position"|awk -F " " '{print $2}'` L "新的master库${IPORT}的pos是${Pos}" return ${Pos} } function AlterReadOnlyStatus(){ #4、修改当前库为读写状态 ${MYSQL_MASTER_BIN} -e "set global read_only=0" #修改网卡配置文件,但不重启(防止该服务器重启后临时IP地址丢失) sed -i "s/read_only = 1/read_only = 0/g" /etc/my${IPORT}.cnf L "sed -i \"s/read_only = 1/read_only = 0/g\" /etc/my${IPORT}.cnf" ReadOnlyStatus=`${MYSQL_MASTER_BIN} -e "show variables like \"read_only\""|awk '{print $NF}'|sed -n '$p'` ReadOnlyConfig=`grep "read_only" /etc/my${IPORT}.cnf |awk '{print $NF}'` #echo "${ReadOnlyStatus}" if [ "${ReadOnlyStatus}" != "OFF" ] && [ "${ReadOnlyConfig}" != "0" ];then L "修改当前数据库${IPORT}的读写状态失败。" exit 1 fi L "修改当前数据库${IPORT}的读写状态成功。" return 0 } function AnsibleSlaveConnect(){ #5、修改另一个从库来连接新的主库并查看同步状态 for ANSIBLE_HOST_NAME in ${ANSIBLE_HOST_NAMES[@]} do ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"stop slave\"" L "ansible ${ANSIBLE_HOST_NAME} -m shell -a \"${MYSQL_MASTER_BIN} -e \"stop slave\"\"" L "另一个从库${IPORT}停止原主从同步成功" ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"reset slave\"" L "另一个从库${IPORT}重置原主从同步成功" ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"set global read_only=1\"" L "获取新主库${IPORT}的bin-logs记录" LogFile=`${MYSQL_MASTER_BIN} -e "show master status\G"|egrep "File"|awk -F " " '{print $2}'` L "获取新主库${IPORT}的Pos记录" GetNewMasterPosNum ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"change master to master_host='${MASTER_HOST_IP}',master_port=${IPORT},master_user='${REPL_USER}',master_password='${REPL_USER_PSW}',MASTER_LOG_FILE='${LogFile}',MASTER_LOG_POS=${Pos}\"" L "另一个从库${IPORT}指定新的主库" L "ansible ${ANSIBLE_HOST_NAME} -m shell -a \"${MYSQL_MASTER_BIN} -e \"change master to master_host='${MASTER_HOST_IP}',master_port=${IPORT},master_user='${REPL_USER}',master_password='${REPL_USER_PSW}',MASTER_LOG_FILE=${LogFile},MASTER_LOG_POS=${Pos}\"\"" ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"start slave\"" L "另一个从库${IPORT}开启同步" SlaveRsyncIp=`ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"show slave status\G\""|grep "Master_Host"|awk '{print $NF}'` L "ansible ${ANSIBLE_HOST_NAME} -m shell -a "${MYSQL_MASTER_BIN} -e \"show slave status\G\""|grep "Master_Host"|awk '{print \$NF}'" L "SlaveRsyncIp ${SlaveRsyncIp} ,${MASTER_HOST_IP}" if [ "${SlaveRsyncIp}" != "${MASTER_HOST_IP}" ];then L "同步主库${IPORT}的IP错误" exit 1 fi L "另一个从库${IPORT}配置完成,并开始同步" return 0 done } ############################################## # # # 始设置本机为master db # # # ############################################## function MasterDB(){ for IPORT in ${PORTS[@]} do MYSQL_MASTER_BIN="`which mysql` -uroot -p${PSW} -P${IPORT} -S /tmp/mysql${IPORT}.sock" CheckMasterIP if [ $? != 0 ];then L "增加临时IP或修改IP配置文件失败." exit 1 fi StopSlave if [ $? != 0 ];then L "停止${IPORT}的slave同步失败." exit 1 fi AlterReadOnlyStatus if [ $? != 0 ];then L "设置master${IPORT}库为读写库失败." exit 1 fi AnsibleSlaveConnect if [ $? != 0 ];then L "修改另一从库${IPORT}的master库为新的master库失败." exit 1 fi L "${IPORT}设置为新的主库完成!" done } ############################################## # # # 开始执行脚本 # # # ############################################## MasterDB
至此,实现了mysql vip的自动切换,同时其他从库从新的master库上同步