自動化運維:使用flask+mysql+highcharts搭建監控平臺

1.前言html

  原本想作一個比較完善的監控平臺,只須要作少量改動就能夠直接拿來用,可是在作的過程當中發現要實現這個目標所需的工做量太大,而當前的工做中對其需求又不是特別明顯。因此就退而求其次,作了一個相似教程系統的東西。在這個系統中,你應該能夠找到作一個監控系統所須要的大部分技術點,而它的真正意義就在於其打通了整個數據流轉的環節。python

  先上一個效果圖:mysql

       

       由於只是作了一個簡單的驗證,因此只有內存曲線有變更,CPU使用狀況沒變化。X軸的座標有時間重複,是因爲數據裏面有相同的IP地址形成的。linux

       磁盤使用狀況、硬盤/網絡IO的實現過程和內存、CPU相似,因此就沒有具體實現。須要特別說明的是: 數據採用的拉的方式,因此目標機只能執行shell命令,磁盤使用狀況的處理,尤爲是IO狀況的處理,比較麻煩。可是其好處是不須要在目標機器上安部署任何東西,只須要一個有權限執行shell命令的帳號便可。若是不介意部署麻煩問題,能夠在每一個目標機上安裝psutil包,能夠很方便的獲取系統信息,可是我看了看大部分系統自帶的python都是2.7版本的,而psutil包須要3.0版本以上的。git

2.遇到的問題github

  把開發過程當中遇到的問題放到前面說,是爲了不你們遇到相同的問題,從而走了彎路。ajax

  2.1 flask-sqlalchemysql

  這個是我最想吐槽的地方:若是一個ORM框架的使用成本和排查錯誤的成本遠遠超過了直接使用sql語句的成本,那麼你還有啥存在的意義?shell

  (1)from flask_sqlalchemy import SQLAlchemy  #python3 的寫法數據庫

  (2)安裝2.1版本,最新2.3有問題,2.2不肯定。使用pip按照的方法爲:pip install flask-sqlalchemy==2.1

  (3)2.1版本應該也有問題,由於app、models、database三個功能類文件分開的話,只有第一次讀取數據是從數據庫讀取,剩下的讀取過程好像都是從內存中讀取的。可是若是把全部的內容都放到app文件中就不存在這個問題(這就是我把全部內容都放到一個文件的緣由)。這也是目前我能找到的惟一的一個解決方式,若是有其餘方式能夠解決這個問題,請你們在下面留言告訴我,謝謝。

  針對這個問題,我也諮詢了幾位羣裏朋友的意見,他們的建議就是最好使用sql直接操做數據庫,不只方便靈活可控,還能夠減小框架自己的缺陷引發的莫名其妙的問題。這點我是深有同感,第一次是使用pip安裝的flask-sqlalchemy 2.3,一堆莫名其妙的問題,查來查去原來是版本問題引發的。由於是使用pip安裝的,因此當時沒有考慮版本的問題。

  2.2 關於測試機器IP的問題

  若是測試過程當中,目標機器就一個,IP地址也就一個,這種狀況下頻繁讀取該機器的系統信息,因爲機器的保護機制耗費的時間會階段性變長,建議多鏈接幾臺機器測試或者掛幾個VIP試試。

  2.3 關於提升的SQL腳本

       我把整個項目文件上傳到了github上,項目中包含的sql腳本若是是在windows下的mysql中執行,須要把create語句整理成一行,不然會建立失敗。linux下的mysql中沒有這個問題。

3.系統組成及主要工具包

         

4.技術點詳解

完整的項目及文件內容見:https://github.com/lichao1217/woodpecker

4.1 獲取數據部分

  (1)經過SSH的方式鏈接目標機器(完整代碼:\woodpecker\wpgd\serverconn.py)

import paramiko #引用封裝SSH鏈接方式的工具包
#返回一個鏈接
#host:ip地址  username:用戶名  pwd 密碼
def get_connection(host,username,pwd):
    try:
      client = paramiko.SSHClient()
      client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
      client.connect(host,22,username,pwd)
      return client
    except Exception as e:
      return None
View Code

      (2)使用configparser工具包讀取鏈接mysql數據庫的配置,並返回一個mysql鏈接(完整代碼:\woodpecker\wpgd\DBconn.py)

def __get_dbconn(self):
        try:
           
            _config = configparser.ConfigParser()
            _config.read(self._configPath)
            host = _config.get("dbconfig","host")  #主機IP地址
            dbusername = _config.get("dbconfig","dbusername") #數據庫用戶名
            dbuserpwd = _config.get("dbconfig","dbuserpwd") #數據庫用戶密碼
            dbname = _config.get("dbconfig","dbname") #數據庫名稱
            db = pymysql.connect(host,dbusername,dbuserpwd,dbname)
            return db
        except Exception as e:
            return None
View Code

      (3)使用pymysql讀取數據(完整代碼:\woodpecker\wpgd\DBconn.py)

 def get_serverList(self,sqltext):
        db = self.__get_dbconn()
        if db is None:
            return 0  #0表示鏈接數據庫失敗
        else :
            try:
                cursor = db.cursor()
                cursor.execute(sqltext) #執行sql語句
                results = cursor.fetchall() #讀取所有結果
                return results
            except Exception as e:
                return -1 # -1 表示讀取數據失敗,有可能SQL語句不對
            finally :
                db.close()
View Code

4.2讀取數據部分(代碼都在app.py 和index.html中)

  (1)flask-sqlalchemy鏈接mysql

from flask_sqlalchemy import SQLAlchemy  #python3 的寫法
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://woodpecker:woodpecker@localhost/woodpecker'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  #是否
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
View Code

    詳細配置參數見:http://www.pythondoc.com/flask-sqlalchemy/config.html

  (2)models定義部分

  這裏只有2點須要注意:

  第一:若是定義的類名和表名不一致,須要使用__tablename__參數說明(__是雙下劃線),例如: __tablename__ = 't_servers'

       第二:表必需要有主鍵

  (3)無參數讀取數據庫數據

         html部分:

<ul id="serverlist" class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
         {% for group in groupList  %}
             <li><a role="menuitem" href="#" >{{ group.groupname }}</a></li>
             <li class="divider"></li>
         {% else %}
             <li><a role="menuitem" href="#">讀取服務器分組失敗</a></li>
         {% endfor %}
    </ul>
View Code

       {{ group.groupname }} 這是flask表示變量的方式,而且上述代碼中的class都是在bootstrap中定義的。

         python部分:

def index():
    groupList = Group.query.all()
    return render_template('index.html', groupList=groupList)
View Code

      Group.query.all()   這是flask-sqlalchemy查詢數據的方式

    (4)頁面傳參數到後臺讀取數據

    html部分(ajax):      

var data = {'ip':ip};   //ip是傳到後臺的參數
        $.ajax({
            type:'post',
            async:false,
            url:"/get_sys_info",  //接收參數的後臺路由函數
            data:data,
            success:function (result) {
                drawchart(result,ip) //此函數是接收到返回的結果後作前臺處理的
                ;}
      });
View Code

    python部分:

@app.route('/get_sys_info', methods=['POST','GET'])
def getSysInfo():
    ip = request.form.get("ip")
    .......
View Code

    (5)動態添加li元素

     有兩種方式:

      第一種在頁面裝載過程當中動態添加li元素及點擊事件:

 //給服務器列表的下拉框每一個li元素添加點擊事件
    window.onload = function () {
         var obj_lis = document.getElementById("serverlist").getElementsByTagName("li");
         for(var i=0;i<obj_lis.length;i++)
         {
            obj_lis[i].onclick = function()
            {
                getiplist(this.innerText);
            }
         }
    }
    //根據選擇的分組設置btn的現實,並讀取當前分組下的ip列表
    function getiplist(groupname)
    {
        var btn=document.getElementById("dropdownMenu1");
        //直接給btn.innerText = groupname,表示下來的倒三角不顯示
        btn.innerHTML=groupname+"<span class=\"caret\"></span>";
        getipList(groupname)
    }
View Code

     第二種當頁面裝載完成後點擊頁面元素局部刷新添加li元素及點擊事件

 //添加ip地址到ul中
      function addIPToUL(data){
          delIPFromUL();
          var ip_str = String(data);
          var ips = ip_str.split(",");
               //alert(ips[0]);
          for(var i=0;i<ips.length;i++)
          {
              var li = document.createElement("li");
              li.setAttribute("class","list-group-item");
              li.innerText=ips[i];
              li.onclick=getSysInfo;
              document.getElementById("iplist").appendChild(li);
          }
      }
View Code

    (6)動態刪除li元素

//選擇新分組以後須要清空當前的ip地址
      function delIPFromUL() {
          var obj_ul = document.getElementById("iplist");
          var obj_lis = document.getElementById("iplist").getElementsByTagName("li");
          var cnt = obj_lis.length;
          //alert(cnt);
          if(cnt>0) {
              for (var i=cnt-1;i>=0;i--){
              var m_li=obj_lis[i];
              obj_ul.removeChild(m_li);
             }
          }

      }
View Code

   (7)定時刷新函數

 setInterval(function () {
        //alert("開始執行");
        getSysInfo();
    }, 60000);
View Code

   這裏須要說一下,定時函數中執行的函數最好緊跟着定時函數,不然容易識別不了。

    (8)時間格式轉換

     js好像沒有自帶的時間轉換格式函數,因此引入了moment.js來處理時間格式。

     (9)highcharts

     html部分:

 <div id="chart_cpu"  style="height: 50%;width: 50%;background: white;float: left"></div>
View Code

    js部分:

//CPU顯示
        var options_cpu ={
            chart:{
                type:'line'
            },
            title:{
                text:'CPU使用狀況'
            },
            subtitle:{
              text:ip
            },
            xAxis:{
                type:"datetime",
                dateTimeLabelFormat:{
                    day:'%H:%M'
                },
                labels:{
                    overflow:'justify'
                },
                categories:data["time"]
            },
            yAxis:{
                title:{
                    text:'百分比(%)'
                }
            },
            legend:{
                layout: "vertical",
                align:'left',
                verticalAlign: "middle"
            },
            credits:{
                enabled:false
            },
            series:[{
                name:'sy',
                data:data["sy"]
            },
                {
                    name:'us',
                data:data["us"]
                },
                {
                    name:'total',
                data:data["totalcpu"]
                }]
        };
        $('#chart_cpu').highcharts(options_cpu);
  
View Code

  python部分:

@app.route('/get_sys_info', methods=['POST','GET'])
def getSysInfo():
    ip = request.form.get("ip")
    #倒序排列去除最近10條數據
    sysinfos = SysInfo.query.filter_by(serverip=ip.strip()).order_by(SysInfo.id.desc()).limit(10).all()
    #從新處理成正序
    sysinfolist = []
    for i in range(len(sysinfos)-1, 0, -1):
        sysinfolist.append(sysinfos[i])
    print(sysinfolist[0].id)
    cpudic = getCPUinfoList(sysinfolist)
    memdic = getMeminfoList(sysinfolist)
    sysinfodic = dict(cpudic, **memdic)
    sysinfodic = jsonify(sysinfodic)
    return sysinfodic
View Code

    (10) div排列

<div id="mainchart" style="height: 80%;width:87%;background: #8c8c8c;position: absolute;margin-left: 13%">
        <div id="chart_cpu"  style="height: 50%;width: 50%;background: white;float: left"></div>
        <div id="chart_mem" style="height: 50%;width: 50%;background:beige;float: right" ></div>
        <div id="chart_disk"  style="height: 50%;width: 50%;background: beige;float: left;text-align: center;font-size: 30px">磁盤使用狀況</div>
        <div id="chart_io" style="height: 50%;width: 50%;background:white;float: right;text-align: center;font-size: 30px" >硬盤/網絡IO狀況</div>
    </div>
View Code

5.總結

  按照上述提到的知識點就能夠搭建出來一個本身的監控系統。須要完善的就是highcharts的顯示問題了,這個能夠參考下相關資料設置成本身滿意的顯示方式。  

  固然,因爲本人水平有限,其中不免有不足的地方,歡迎你們提出來,多作交流,共同進步。

相關文章
相關標籤/搜索