自动化运维:使用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的显示问题了,这个能够参考下相关资料设置成本身满意的显示方式。  

  固然,因为本人水平有限,其中不免有不足的地方,欢迎你们提出来,多作交流,共同进步。

相关文章
相关标签/搜索