1. 程式人生 > 實用技巧 >python初級實戰-----galera叢集一鍵恢復指令碼

python初級實戰-----galera叢集一鍵恢復指令碼

本指令碼實現galera叢集一鍵恢復,僅作為學習使用

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
本指令碼適用於ICOS5.X.x/FT V1.2、1.3/AK2.0等
一、使用方法
    拷貝本指令碼到任意mariadb叢集節點,執行 python galera_recover_XXX.py。
二、適用情況
    1、三節點都無法啟動,且均報錯failed to reach primary view: 110 (Connection timed out)。
二、特別說明
    1、建議優先使用kolla-ansible -i /root/multinode mariadb_recovery恢復,如法恢復再使用本指令碼。
    2、為保障資料安全,本指令碼執行過程中,如果要重新指定primary節點啟動,會對mariadb整個資料資料夾備份,具體為/etc/kolla/日期,後期可以刪除。
"""
import time
import os
import sys


#grastate.dat、gvwstate.dat檔案路徑
grastate_file = "/var/lib/docker/volumes/mariadb/_data/grastate.dat"
gvwstate_file = "/var/lib/docker/volumes/mariadb/_data/gvwstate.dat"


# 獲取當前galera的叢集的各節點的ip
node_ips_info = os.popen("cat /etc/kolla/mariadb/galera.cnf |grep '^wsrep_cluster_address'").read()
node_ips_str = node_ips_info.split('gcomm://')[1]
node_ips_str = node_ips_str.strip()
node_ips_str = node_ips_str.replace(':4567','')
node_ips_arr = node_ips_str.split(',')



def backup_dir():
    """備份_data資料夾"""
    date = time.strftime("%Y-%m-%d")
    Mariadb_dir = '/etc/kolla/' + date
    for node_ip in node_ips_arr:
        free_disk_var = os.popen('ssh ' + node_ip + " df -m / | gawk '{print $4}' | grep '^[0-9]'").read()
        free_disk_var = int(free_disk_var) - 8192
        data_disk = os.popen('ssh ' + node_ip + " du -sm /var/lib/docker/volumes/mariadb/_data  | gawk '{print $1}'").read()
        date_mkdir = os.system('ssh ' + node_ip + ' ls ' + Mariadb_dir + ' > /dev/null')
        if date_mkdir == 512:
           if int(data_disk) < int(free_disk_var):
              os.popen('ssh ' + node_ip + ' mkdir ' + Mariadb_dir).read()
              os.system('ssh ' + node_ip + ' cp -r /var/lib/docker/volumes/mariadb/_data/* ' + Mariadb_dir )
              time.sleep(2)
              print("%s_data備份成功。"%node_ip)
           else:
              print("備份空間不足,建議清理部分空間用以備份_data資料夾···")
        else:
           print("_data備份檔案已經存在於/etc/kolla/日期目錄下")


def start_mariadb_with_wsrep(ip):
    """設定叢集發起人角色"""
    bootstrap = os.popen('ssh ' + ip + ' cat /var/lib/docker/volumes/mariadb/_data/grastate.dat').read()
    if "safe_to_bootstrap" in bootstrap: 
       modify_boot = " sed -i 's/safe_to_bootstrap: 0/safe_to_bootstrap: 1/' /var/lib/docker/volumes/mariadb/_data/grastate.dat"
       os.popen("ssh " + ip + ' "' + modify_boot + '"')

    modify_conf = " sed -i 's/mysqld_safe/mysqld_safe --wsrep-new-cluster/' /etc/kolla/mariadb/config.json"
    restore_conf = " sed -i 's/mysqld_safe --wsrep-new-cluster/mysqld_safe/' /etc/kolla/mariadb/config.json"
    print("修改啟動引數中···")
    os.popen("ssh " + ip + ' "' + modify_conf + '"')
    time.sleep(1)
    print("primary節點啟動中···")
    os.popen('ssh ' + ip + ' docker restart mariadb').read()
    # 將配置檔案恢復回去
    time.sleep(10)
    print("恢復啟動引數中····")
    os.popen("ssh " + ip + ' "' + restore_conf + '"' )
    if check_mariadb_active_now(ip) is True:
        print("primary節點啟動成功。")
    else:
        print('請人工處理,指令碼無法完成修復。')
        sys.exit(0)

def check_mariadb_active_now(galera_node_ip):
    """檢測mariadb服務是否啟動"""
    """haproxy的3306埠排除,若還存在mariadb的3306表明mariadb啟動了"""
    """檢測兩次"""
    is_active1 = os.popen('ssh ' + galera_node_ip + ' netstat -tnlp | grep 3306 | grep -v haproxy').read()
    is_active1 = "3306" in is_active1
    time.sleep(3)
    is_active2 = os.popen('ssh ' + galera_node_ip + ' netstat -tnlp | grep 3306 | grep -v haproxy').read()
    is_active2 = "3306" in is_active2
    if is_active1 and is_active2:
       return True
    return False

def get_all_nodes_seqno():
    """獲取所有節點的seqno值"""
    seqno_dict = {}
    for node_ip in node_ips_arr:
        ls_grastate = os.system('ssh ' + node_ip + ' ls ' + grastate_file + ' > /dev/null')
        if ls_grastate != 512:
           node_seqno = os.popen('ssh ' + node_ip + ' cat ' + grastate_file + ' | grep seqno').read()
           node_seqno = node_seqno.replace('seqno:','')
           seqno_dict[node_ip] = node_seqno
        else:
           seqno_dict[node_ip] = int(-1)
    return(seqno_dict)

def get_node_uv_is_equal():
    """獲取相同節點的uuid值"""
    for node_ip in node_ips_arr:
        if not os.path.exists(gvwstate_file):
           print("%s節點uuid資訊不存在"%node_ip)
        else:
           view_id = os.popen('ssh ' + node_ip + ' cat ' + gvwstate_file + ' | grep view').read()
           my_uuid = os.popen('ssh ' + node_ip + ' cat ' + gvwstate_file + ' | grep my_uuid').read()
           my_uuid = my_uuid.replace('my_uuid: ','')
           if my_uuid in view_id:
              uv_equal_ip = node_ip
              return(uv_equal_ip)
   
def start_slave_mariadb(primary_ip):
    """啟動非primary的其他節點"""
    node_ips_arr2 = node_ips_arr[:]
    node_ips_arr2.remove(primary_ip)
    for slave_node in node_ips_arr2:
        os.system('ssh ' + slave_node + ' docker restart mariadb')
        time.sleep(50)
        if check_mariadb_active_now(slave_node):
           print("slave節點 %s 資料庫服務已經啟動成功!"%slave_node)
        else:
           print("slave節點 %s 資料庫服務沒有啟動成功!"%slave_node)
           

def stop_all_mariadb():
    """關閉所有節點的mariadb服務"""
    os.system('ssh ' + node_ips_arr[2] + ' docker stop mariadb')
    time.sleep(2)
    os.system('ssh ' + node_ips_arr[0] + ' docker stop mariadb')
    time.sleep(2)
    os.system('ssh ' + node_ips_arr[1] + ' docker stop mariadb')
    time.sleep(2)

def galera_recover():

    """判斷叢集內所有地址是否都線上""" 
    for node_ip in node_ips_arr:
        rep = os.popen('ping -c 3 -w 10 ' + node_ip + ' | grep time= | wc -l' ).read()
        print("測試資料庫主機%s是否線上······"%node_ip)
        if rep == '0':
            print('資料庫主機 %s 網路不可達,請檢查!'%node_ip)
            sys.exit(0)
    print('所有資料庫主機都線上!')

    """判斷叢集內是否有mariadb主機線上"""
    down_node_ip = []
    up_node_ip = []
    for node_ip in node_ips_arr:
        if check_mariadb_active_now(node_ip):
           print("資料庫 %s 服務已經是啟動狀態!"%node_ip)
           up_node_ip.append(node_ip)
           if len(up_node_ip) == 3:
              print("所有資料庫節點都啟動了,不需要恢復!")
              break
        else:       
           down_node_ip.append(node_ip)
     #如果叢集記憶體在啟動著的mariadb
    if up_node_ip and down_node_ip:
       print("%s中的資料庫服務沒有啟動"%down_node_ip)
       for down_ip in down_node_ip:
           print("重啟%s資料庫中"%down_ip)
           os.system('ssh ' + down_ip + ' docker restart mariadb')
           time.sleep(10)
           if check_mariadb_active_now(down_ip):
              print("mariadb %s 啟動成功!"%down_ip)
              if check_mariadb_active_now(node_ips_arr[0]) and check_mariadb_active_now(node_ips_arr[1]) and check_mariadb_active_now(node_ips_arr[2]):
                 print("資料庫全部啟動成功。")
                 sys.exit(0)
           else:
              print("mariadb %s 沒有啟動成功!"%down_ip) 
    
           
    """這裡針對的是存在一個初始化狀態節點的處理,先關閉這個節點,grastate中qeqno值會變為-1"""
    if len(up_node_ip) == 1 or len(down_node_ip) == 3:
       for node_ip in node_ips_arr:
           if check_mariadb_active_now(node_ip):
              print("關閉mariadb節點%s服務"%node_ip)
              os.system('ssh ' + node_ip + ' docker stop mariadb')
              os.system('ssh ' + node_ip + ' rm -rf ' + grastate_file + ' > /dev/null')
              time.sleep(10)

     #如果叢集內不存在啟動著的mariadb
    if len(down_node_ip) == 3 or len(up_node_ip) == 1:
       print("三個mariadb節點都掛了,開啟修復流程!")
       backup_dir()
       """根據seqno值選舉叢集發起人"""
       max_seqno = int(-1)
       seqno_dict = get_all_nodes_seqno()
       print("seqno資訊情況為%s"%seqno_dict)
       for key in seqno_dict:
           if int(seqno_dict[key]) > int(max_seqno):
              max_seqno = int(seqno_dict[key])
              first_boot_node = key
       if max_seqno != -1:
          print("通過seqno值%s節點將為primary節點啟動叢集!!!"%first_boot_node)
          start_mariadb_with_wsrep(first_boot_node)
          start_slave_mariadb(first_boot_node)
       """如果所有的節點seqno值都為-1"""
       if max_seqno == -1:
          print("無法通過seqno值判斷出primary節點")
          uv_equal_ip = get_node_uv_is_equal()
          if uv_equal_ip != None:
             print("通過UUId選擇%s為primay節點啟動中···"%uv_equal_ip)
             start_mariadb_with_wsrep(uv_equal_ip)
             start_slave_mariadb(uv_equal_ip)
          else:
             print("無法通過UUID判斷parimary節點,自選啟動中···")
             print("%s,作為叢集發起人啟動中"%node_ips_arr[2])
             chose_pri_ip = node_ips_arr[2]
             start_mariadb_with_wsrep(chose_pri_ip)
             start_slave_mariadb(chose_pri_ip)
             if check_mariadb_active_now(node_ips_arr[0]) and check_mariadb_active_now(node_ips_arr[1]) and check_mariadb_active_now(node_ips_arr[2]):
                print("資料庫全部啟動成功。")
             else:
                print("%s,作為叢集發起人啟動中"%node_ips_arr[0])
                stop_all_mariadb()
                chose_pri_ip = node_ips_arr[0]
                start_mariadb_with_wsrep(chose_pri_ip)
                start_slave_mariadb(chose_pri_ip)
                if check_mariadb_active_now(node_ips_arr[0]) and check_mariadb_active_now(node_ips_arr[1]) and check_mariadb_active_now(node_ips_arr[2]):
                   print("資料庫全部啟動成功。")
                else:
                   print("%s,作為叢集發起人啟動中"%node_ips_arr[1])
                   stop_all_mariadb()
                   chose_pri_ip = node_ips_arr[1]
                   start_mariadb_with_wsrep(chose_pri_ip)
                   start_slave_mariadb(chose_pri_ip)
                   if check_mariadb_active_now(node_ips_arr[0]) and check_mariadb_active_now(node_ips_arr[1]) and check_mariadb_active_now(node_ips_arr[2]):
                      print("資料庫全部啟動成功。")
                   else:
                      print("可能有檔案損壞等指令碼無法修復的錯誤,請人工參與修復")
                

if __name__ == "__main__":
    galera_recover()