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库上同步

 

posted @ 2021-09-02 17:16  MR__Wang  阅读(1787)  评论(0编辑  收藏  举报