通过Python收集MySQL MHA 部署及运行状态信息的功能实现
阅读原文时间:2021年10月01日阅读:1

当集团的MySQL数据库实例数达到2000+、MHA集群规模数百个时,对MHA的及时、高效管理是DBA必须面对的一个挑战。MHA 集群 节点信息 和 运行状态 是管理的基础。本篇幅主要介绍如何通过Python实现收集MHA 集群 节点信息 和 运行状态的功能。这些信息将是CMDB信息的重要组成部分。

MHA集群数百个,MHA Manager 节点 十几个,一个MHA Manager 节点管理着50-60个集群。 我们希望开发的程序,只在着十几个MHA Manager  部署运行,就可以收集到所需的 MHA Server 节点信息、VIP 信息、运行状态信息及其他信息,并且将收集到的数据保存到MySQL 数据库中。

2.1 程序调用的MHA工具程序或文件

工具程序或文件

功能

 mha_appxxx.cnf 配置文件

1.从这个文件中 提取 Server 信息(Server IP);

2.提取 FailOver Script 和 Online Change Script的文件。

 appxxx_master_ip_failover 脚本文件

 提取定义的VIP,和其他处收集到的VIP,进行横向比较,防止配置出错。

 appxxx_master_ip_online_change 脚本文件

 提取定义的VIP,横向比较防止配置出错。

 masterha_check_repl 工具程序

1.检查MySQL复制状况;

2.解析当前主节点IP;

3.解析 slave 节点IP;

4.解析出VIP。

masterha_check_status

检测当前MHA运行状态(运行OK还是stop)。

为便于理解,我们贴上 mha_appxxx.cnf 的内容。

[server default]
manager_workdir=/var/log/masterha/app1.log //设置manager的工作目录
manager_log=/var/log/masterha/app1/manager.log //设置manager的日志
master_binlog_dir=/data/mysql //设置master 保存binlog的位置,以便MHA可以找到master的日志,我这里的也就是mysql的数据目录
master_ip_failover_script= /usr/local/bin/appxxx_master_ip_failover //设置自动failover时候的切换脚本
master_ip_online_change_script= /usr/local/bin/appxxx_master_ip_online_change //设置手动切换时候的切换脚本
password=用户密码 //设置mysql中root用户的密码,这个密码是前文中创建监控用户的那个密码
user=root 设置监控用户root
ping_interval=1 //设置监控主库,发送ping包的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行railover
remote_workdir=/tmp //设置远端mysql在发生切换时binlog的保存位置
repl_password=用户密码 //设置复制用户的密码
repl_user=repl //设置复制环境中的复制用户名
report_script=/usr/local/send_report //设置发生切换后发送的报警的脚本
shutdown_script="" //设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机放在发生脑裂,这里没有使用)
ssh_user=root //设置ssh的登录用户名

[server1]
hostname=110.110.110.50
port=3306

[server2]
hostname=110.110.110.60
port=3306
candidate_master=1 //设置为候选master,如果设置该参数以后,发生主从切换以后将会将此从库提升为主库,即使这个主库不是集群中事件最新的slave
check_repl_delay=0 //默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master,因为对于这个slave的恢复需要花费很长时间,通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时,这个参数对于设置了candidate_master=1的主机非常有用,因为这个候选主在切换的过程中一定是新的master

[server3]
hostname=110.110.110.70
port=3306

2.程序简单的流程图

因是简单流程图,其中判断及异常未在图中标明。

3.1.创建保存收集信息的表

表命名为mysqldb_mha_info,其create 脚本如下:

create table `mysqldb_mha_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
mha_manager_ip varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA管理节点所在集群的IP',
mha_name varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA的名字,类似于副本集',
mha_file_name varchar(250) NOT NULL DEFAULT '' COMMENT 'MHA .cnf 配置文件名字',
mha_name_path varchar(250) NOT NULL DEFAULT '' COMMENT 'MHA .cnf 配置文件路径和名字',
`cnf_server1_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA cnf 配置文件中的节点1',
`cnf_server2_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA cnf 配置文件中的节点2',
`cnf_server3_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA cnf 配置文件中的节点3',
failover_script varchar(250) NOT NULL DEFAULT '' COMMENT 'MHA failover scripts的文件',
failover_script_vip varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA failover scripts 中定义的VIP',
online_script varchar(250) NOT NULL DEFAULT '' COMMENT 'MHA online change scripts的文件',
online_script_vip varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA online change scripts 中定义的VIP',
script_remark varchar(1500) NOT NULL DEFAULT '' COMMENT 'MHA scripts VIP 检查结果',
masterha_status varchar(10) NOT NULL DEFAULT '' COMMENT 'MHA 检查是否开启,来自于 masterha_check_status 检查结果',
master_serverip varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA 检查是否开启,来自于 masterha_check_status 检查结果',
`current_master_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA 当前主节点,来自check_repl',
`mha_current_vip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA 当前VIP ,来自check_repl',
`slave1_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA 当前从节点1,来自check_repl',
`slave2_ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'MHA 当前从节点2 ,来自check_repl',
mha_cnf_remark varchar(1500) NOT NULL DEFAULT '' COMMENT 'MHA check conf/cnf 检查结果',
check_repl_remark varchar(1500) NOT NULL DEFAULT '' COMMENT 'MHA check repl检查结果',
remark varchar(1500) NOT NULL DEFAULT '' COMMENT 'MHA 检查结果',
`creator` varchar(50) NOT NULL DEFAULT '',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`operator` varchar(50) NOT NULL DEFAULT '',
`modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

2. 连接DB的模块

模块命名为db_conn.py,在此模块中,使用 mysql-connector替代了MySQLdb。安装更加简便。

#!/usr/bin/python3

-*- coding: UTF-8 -*-

##import MySQLdb 安装模块麻烦
import mysql.connector
db = mysql.connector.connect(user='nideuid', password='nidepwd',host='nideseverip',database='DBname',port=XXXX)

3.功能实现模块

文件为collect_mysqldbmha_info.py,其代码如下:

#!/usr/bin/python

-*- coding: UTF-8 -*-

import os
import io
import re
import ConfigParser
import socket

import db_conn
mysqldb = db_conn.db
cursor = mysqldb.cursor()

第1部分 获取本机IP

try:
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.connect(('8.8.8.8',80))
mha_manager_ip=s.getsockname()[0]
print('mha manager 所在主机的IP如下:')
print(mha_manager_ip)
finally:
s.close()
###

##第2部分: 循环遍历mha cnf 所在的文件夹,取出 cnf 进行判断和检查
Path='/date/funcation/python/mha_conf'
#fout=open('输出文件名','w')
for Name in os.listdir(Path) :
Pathname= os.path.join(Path,Name)
## print(Pathname)
## print(Name)
mha_name = Name.replace(".cnf", "").replace(".conf", "") ###为MHA集群启个名字
##print(mha_name)
##注意此处为r,不能为w,否则报错:IOError: File not open for reading
with open(Pathname,'r') as f:
filecontent=f.read()
#print(filecontent)
remark = ''
####调整为ConfigParser,注意python2 和 python 的模块名字是不一样的.ConfigParser与configparser
config =ConfigParser.ConfigParser()
try:
config.read(Pathname)
server_item = config.sections()
##print(server_item)
### start 获取 MHA 切换时的scripts 文件名字
mha_failover_script = ''
mha_online_change_script =''
mha_cnf_remark =''
if 'server default' in server_item:
mha_failover_script = config.get('server default','master_ip_failover_script')
###
mha_failover_script=mha_failover_script.replace(" --ssh_user=root", "")
##print(mha_failover_script)
else:
mha_cnf_remark = mha_cnf_remark + 'mha_failover_script 未配置;'
if 'server default' in server_item:
mha_online_change_script = config.get('server default','master_ip_online_change_script')
##print(mha_online_change_script)
else:
mha_cnf_remark = mha_cnf_remark + 'mha_online_change_script 未配置;'
###1.1 end 获取结束
##1.2 start 获取MHA配置文件中的节点信息
server1_host = '' ##MHA cnf 配置文件中的节点1
server2_host = '' ##MHA cnf 配置文件中的节点2
server3_host = '' ##MHA cnf 配置文件中的节点3
if 'server1' in server_item:
server1_host = config.get('server1','hostname')
print(server1_host)
else:
server1_host = ''
mha_cnf_remark = mha_cnf_remark + 'Server1未配置;'
print(server1_host)
if 'server2' in server_item:
server2_host = config.get('server2','hostname')
print(server2_host)
else:
server2_host = ''
mha_cnf_remark = mha_cnf_remark + 'Server2未配置;'
print(server2_host)
if 'server3' in server_item:
server3_host = config.get('server3','hostname')
print(server3_host)
##else:
##server3_host = ''
##mha_cnf_remark = mha_cnf_remark + 'Server3未配置;'
##print(server3_host)
##1.2 获取server节点信息结束
print(mha_cnf_remark)
except Exception as e:
print(e)

#####第3部分 start 从 mha scripts 中提取 配置的VIP  
mha\_remark = ''  
mha\_failover\_my\_vip = ''  
mha\_failover\_flush\_vip = ''  
mha\_onlinechange\_my\_vip = ''  
mha\_onlinechange\_flush\_vip =''  
if len(mha\_failover\_script) <> 0 and len(mha\_online\_change\_script) <> 0 :  
  ##3.1 先来处置 failover\_script,解析其中的VIP  
  with open(mha\_failover\_script,'r') as f:  
    failscript\_lines=f.readlines()  
    for failscript\_line in failscript\_lines:  
      failscript\_ip=re.findall(r"\\b(?:(?:25\[0-5\]|2\[0-4\]\[0-9\]|\[01\]?\[0-9\]\[0-9\]?)\\.){3}(?:25\[0-5\]|2\[0-4\]\[0-9\]|\[01\]?\[0-9\]\[0-9\]?)\\b", failscript\_line)  
      if failscript\_ip:  
        if 'my $vip =' in failscript\_line:  
          mha\_failover\_my\_vip = failscript\_ip\[0\]  
          print('解析出mha\_failover\_my\_vip:')  
          print(mha\_failover\_my\_vip)  
        if  'my $ssh\_flush\_vip' in failscript\_line:  
          mha\_failover\_flush\_vip = failscript\_ip\[0\]  
          print('解析出mha\_failover\_flush\_vip:')  
          print(mha\_failover\_flush\_vip)

    ##文件读取完毕,对读取结果进行判断,判断两种情况  
    ## 一种是否未定义  
    if mha\_failover\_my\_vip =='':  
      mha\_remark = mha\_remark + 'MHA failover  未提取到VIP的设置,请检查;'  
    ## 另外一种,,定义了,但是值不相等  
    if mha\_failover\_my\_vip <> mha\_failover\_flush\_vip:  
      mha\_remark = mha\_remark + 'MHA failover scripts文件中设置的两处VIP不一致,请检查;'

  ## 3.2 处理online change scripts ,解析提取其中的VIP信息  
  with open(mha\_online\_change\_script,'r') as f:  
    onlinescript\_lines=f.readlines()  
    for onlinescript\_line in onlinescript\_lines:  
      onlinescript\_ip=re.findall(r"\\b(?:(?:25\[0-5\]|2\[0-4\]\[0-9\]|\[01\]?\[0-9\]\[0-9\]?)\\.){3}(?:25\[0-5\]|2\[0-4\]\[0-9\]|\[01\]?\[0-9\]\[0-9\]?)\\b", onlinescript\_line)  
      if onlinescript\_ip:  
        if 'my $vip =' in onlinescript\_line:  
          mha\_onlinechange\_my\_vip = onlinescript\_ip\[0\]  
          print('解析出mha\_onlinechange\_my\_vip:')  
          print(mha\_onlinechange\_my\_vip)  
        if  'my $ssh\_flush\_vip' in onlinescript\_line:  
          mha\_onlinechange\_flush\_vip = onlinescript\_ip\[0\]  
          print('解析出mha\_onlinechange\_flush\_vip:')  
          print(mha\_onlinechange\_flush\_vip)  
    #### online change 文件读完了,判断定义的VIP是否符合要求  
    if mha\_onlinechange\_my\_vip =='':  
      mha\_remark = mha\_remark + 'MHA online change scripts未提取到VIP的设置,请检查;'  
    if mha\_onlinechange\_my\_vip <> mha\_onlinechange\_flush\_vip:  
      mha\_remark = mha\_remark + 'MHA online change scripts文件中设置的两处VIP不一致,请检查;'

  #### 两个文件都读取了,判断两个文件中定义的VIP是否一致  
  if mha\_onlinechange\_my\_vip <> mha\_failover\_my\_vip:  
    mha\_remark = mha\_remark + 'MHA online change script  和 failover script 中的VIP不一致,请检查。'

else:  
  mha\_remark = mha\_remark + 'MHA init 的配置文件未正确定义切换的scripts,请检查。'  
  #print('MHA init 的配置文件未正确定义 切换的scripts,请检查。')  
  print(mha\_remark)  
#####第2部分 end 从 mha scripts 中提取 配置的VIP

#### 第4部分,从masterha\_check\_status执行结果中判断mha进程状态  
## 从 执行masterha\_check\_status结果中解析出的  masterha\_status 和 master\_serverip 的数据  
masterha\_status =''  
master\_serverip =''  
## 从 执行masterha\_check\_repl结果中解析出的 current\_master 、current\_slave1、current\_slave2 和 mha\_current\_vip 的数据  
current\_master = ''  
current\_slave1 = ''  
current\_slave2 = ''  
mha\_current\_vip =''  
##判断下文件是否是MHA的配置文件,判断方式就是文件中 必须有 server default\\server1的sections  
if 'server default' in server\_item and 'server1' in server\_item :  
  ##cmd\_mha\_status ='/usr/local/bin/masterha\_check\_status --conf=/etc/mha/opszabbix.cnf'  
  cmd\_mha\_status ='/usr/local/bin/masterha\_check\_status --conf='+Pathname  
  try:  
    mha\_status=os.popen(cmd\_mha\_status)  
    mha\_status\_result = mha\_status.read()  
    print(mha\_status\_result)  ##返回样式为 XXXX (pid:------) is running(0:PING\_OK), master:XXX.XXX.XXX.XXX  
    ### 判断状态是否为运行中  
    if 'running(0:PING\_OK)' in mha\_status\_result:  
      masterha\_status='Running'  
      ##抓取MHA的Master 节点  
      ##master\_serverip = mha\_status\_result\[mha\_status\_result.rfind('master:'):\]  
      master\_serverip = mha\_status\_result.split('master:')\[1\]  
      print(master\_serverip)  
      print('MHA启动运行正常')  
    elif 'stopped(2:NOT\_RUNNING)' in mha\_status\_result:  
      masterha\_status='stopped'  
      print('MHA未启动!!!')  
  finally:  
     if mha\_status:  
        mha\_status.close()  
  #### 第5部分,从masterha\_check\_repl的执行结果中,判断解析 主、从节点、VIP节点  
  ##  判断 副本集 的状况  
  cmd\_repl\_status ='/usr/local/bin/masterha\_check\_repl --conf='+Pathname  
  try:  
    ##### 添加 2> error 参数,不需要打印出调试信息。  
    cmd\_repl\_status\_result = cmd\_repl\_status + '     2> checkrepl.log'  
    repl\_status=os.popen(cmd\_repl\_status\_result)  
    repl\_status\_result = repl\_status.read()  
    ##print(repl\_status\_result)  
    if 'MySQL Replication Health is OK' in repl\_status\_result:  
      print('MHA集群的主从正常')  
      ###获取ServerIP  
      #######调试信息是输出到2号流中的,所以一定 添加 2>&1,否则抓取不到节点信息,只能抓到一个VIP。  
      cmd\_repl\_status\_info = cmd\_repl\_status + '     2>&1'  
      with os.popen(cmd\_repl\_status\_info,'r') as repl\_status\_check2:  
        #repl\_status\_lines=repl\_status\_check2.readlines()  
        repl\_status\_lines=repl\_status\_check2.readlines()  
        ##print(len(repl\_status\_lines))  ####打印出list的元素个数  
        for repl\_status\_line in repl\_status\_lines:  
          ##print('##################  start   ###########################')  
          ##print(str(repl\_status\_line).replace("\\n", "").replace("\\t", ""))  
          ##repl\_status\_line ='Current Alive Master: 10.200.58.63(10.200.58.63:55988)'  
          serverip\_result=re.findall(r"\\b(?:(?:25\[0-5\]|2\[0-4\]\[0-9\]|\[01\]?\[0-9\]\[0-9\]?)\\.){3}(?:25\[0-5\]|2\[0-4\]\[0-9\]|\[01\]?\[0-9\]\[0-9\]?)\\b", repl\_status\_line)  
          if serverip\_result:  
            if 'Current Alive Master:' in repl\_status\_line:  
              current\_master = serverip\_result\[0\]  
              print('已解析到主节点IP')  
              print(current\_master)  
            elif 'Checking replication health on' in repl\_status\_line and current\_slave1 == '':   ###有可能有2个从节点,此处为第1个从节点  
              current\_slave1 = serverip\_result\[0\]  
              print('已解析到从节点1')  
              print(current\_slave1)  
            elif 'Checking replication health on' in repl\_status\_line and current\_slave1 <> '':  ###有可能有2个从节点,此处为第2个从节点  
              current\_slave2 = serverip\_result\[0\]  
              print('已解析到从节点2')  
              print(current\_slave2)  
            elif 'Checking replication health on' in repl\_status\_line and current\_slave1 <> '':  ###有可能有2个从节点,此处为第2个从节点  
              print('集群有3个或更多的从节点,请确认。')  
            if 'down==/sbin/ifconfig ' in repl\_status\_line:  
              mha\_current\_vip = serverip\_result\[0\]  
              print('已解析到MHA集群的VIP')  
              print(mha\_current\_vip)

            ##print('包含serverip')  
            ##print(serverip\_result)  
          #else:  
            #print('不包含ServerIP')  
        ##else:  
          ##print(repl\_status\_line)  
          ##print('##################  end   ###########################')  
      ####获取IP部分结束  
    else:  
      print('MHA集群的主从异常,请及时检查')

  finally:  
     if repl\_status:  
        repl\_status.close()

else:  
   remark = Pathname + '...... 不是MHA的配置文件,请检查!'  
   print(remark)

##### 第6部分,将数据保存到表中  
sql\_insert = "insert into mysqldb\_mha\_info(mha\_manager\_ip,mha\_name,mha\_file\_name,mha\_name\_path,cnf\_server1\_ip,cnf\_server2\_ip,cnf\_server3\_ip,failover\_script,failover\_script\_vip,online\_script,online\_script\_vip,masterha\_status,master\_serverip,current\_master\_ip,mha\_current\_vip,slave1\_ip,slave2\_ip,mha\_cnf\_remark,script\_remark,remark) " \\  
                  "values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')" % \\  
                  (mha\_manager\_ip,mha\_name,Name,Pathname,server1\_host,server2\_host,server3\_host,mha\_failover\_script,mha\_failover\_my\_vip,mha\_online\_change\_script,mha\_onlinechange\_my\_vip,masterha\_status,current\_master,master\_serverip,mha\_current\_vip,current\_slave1,current\_slave2,mha\_cnf\_remark,mha\_remark,remark)  
##print(sql\_insert)  
cursor.execute(sql\_insert)  
mysqldb.commit()

#####

关闭游标

cursor.close()

关闭数据库连接

mysqldb.close()

4.代码运行

Python 运行环境为:Python 2.7.5

执行命令:

python /data/XXXX路径/collect_mysqldbmha_info.py

定期收集,请根据需要设置cron.