https://lupython.gitee.io/2018/05/05/CMDB%E4%BB%8B%E7%BB%8D/ 尚泽凯博客地址css
传统运维:html
一、项目 上线:前端
a.产品经理前期调研(需求分析)python
b.和开发进行评审jquery
c.开发进行开发git
d.测试进行测试github
e.交给运维人员进行上线web
上线:ajax
直接将代给运维人员,让业务运维人员把代码放到服务器上数据库
痛点:
曾加运维人员的成本
改进:
搞一个自动分发代码 的系统
必须的条件:
一、服务器的信息(ip,hostname等 )
二、 能不能把报警自动化
三、 装机系统:
传统的装机和布线:
idc运维:
用大量的人力和物力,来进行装机
自动化运维:
collober 自动发送命令装机
四、收集服务器的元信息:
a. Excel表格 缺点:1.认为干预太严重2.统计的时候也会有问题 b.搞一个系统 做用:自动的帮咱们收集服务器的信息,而且自动的记录咱们的变动信息
一、用户管理,记录 测试,开发运维人员的用户表 二、业务线管理,须要记录业务的详情 三、项目管理,指定此项目属于那条业务线,以及项目详情 四、应用管理,指定此应用的开发人员,属于哪一个项目,和代码地址,部署目录,部署集群,依赖的应用,软件等信息 五、主机管理,包括云主机,物理机,主机属于哪一个集群,运行着那个软件,主机管理员,链接哪些网络设备,云主机的资源地,存储等相关信息 六、主机变动管理主机的一些信息变动,例如管理员,所属集群等信息更改,链接的网络变动等 七、网络设备管理,只要记录网路设备的详细信息,及网络设备链接的上级设备 八、IP管理,IP属于哪一个主机,哪一个网段,是否被占用
cmdb:
做用:自动的帮咱们收集服务器的信息,而且自动的记录咱们的变动信息
愿望:解放双手,让全部的东西都自动化
你为何要使用cmdb?
由于咱们公司在初期的时候,统计资产使用的的是Excel表格,刚开始的时候数据少,使用起来没有以为不方便,可是随着业务的增长,一些问题便凸显出来了,特别是当资产信息出现变动的时候,数据修改麻烦,可能愈来愈乱,所以,公司为了让资产信息的收集简单化,自动化,因而使用了CMDB。关于cmdb的实现通过咱们公司的同事一块儿研究探讨,一共有三种实现方法,第一种是agent方法,首先咱们看到的是这些服务器,它们中有用Python语言编写Agent脚本,服务器经过执行subprocess模块的命令,服务器将获得的未采集的信息通过 执行subprocess模块的命令后将获得的结果经过requests模块发送给API,API再将数据写入数据库中而后经过web界面将数据展示给用户,咱们公司一开始准备使用Agent方式,结果发现Agent方法,须要为每一台服务器部署一个Agent 程序,实现起来麻烦,并且成本较高,不适合咱们公司,因而咱们又研究了SSH类的方法,
你负责什么?
收集资产信息的程序
django里的一些配置
遇到的困难是什么?是怎么解决的?
困难:惟一标识问题
开始收集服务器的元数据:(4种方案)
其本质上就是在各个服务器上执行subprocess.getoutput()命令,而后将每台机器上执行的结果,经过request模块返回给主机API,而后主机API收到这些数据以后,放入到数据库中,而后经过web界面将数据展示给用户
若是在服务器较少的状况下,能够应用此方法
import paramiko # 建立SSH对象 ssh = paramiko.SSHClient() # 容许链接不在know_hosts文件中的主机 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 链接服务器 ssh.connect(hostname='10.0.0.130', port=22, username='root', password='1') # 执行命令 stdin, stdout, stderr = ssh.exec_command('ifconfig') # 获取命令结果 result = stdout.read() print(result) # 关闭链接 ssh.close()
中控机经过parmiko(py模块)登陆到各个服务器上,而后执行命令的方式去获取各个服务器上的信息
API从数据库中获取到未采集的机器列表后发送到中控机服务器中,中控机服务器经过parmiko模块登陆到服务器上,进行信息的采集,服务器采集完后再将结果返回给中控机,仍后将从服务器中获得 的信息经过 request模块发送给API,API经过主机名和SN做为惟一标识,将信息录入到数据中,而后经过web界面将数据展示给用户
中控机从API中获取未采集的资产信息后经过队列发送命令给服务器执行。服务器执行完后将结果放到入另外一个队列中,中控机将获取到的服务信息结果发送到API进而录入数据库。而后经过web界面将数据展示给用户
master端: """ 1.安装salt-master yum install salt-master 2.修改配置文件: vim /etc/salt/master interface:10.0.0128 表示Master的ip 3.启动 service salt-master start """ slave端: """ 一、安装salt-minion yum install salt-minion 二、修改配置文件 :vim /etc/salt/minion master:10.0.0.128 #master的地址 三、启动:service salt-minion start """
salt-key -L #查看已受权和未受权的slave salt-key -A salve_id #接受指定的id的salve salt-key -r salve_id #拒绝指定id的salve salt-key -d salve_id #删除指定id的salve
在master服务器上对salve 进行 远程操做
salt 'c2.salt.com' cmd.run 'ifconfig' salt "*" cmd.run 'ifconfig'
基于API的方式
import salt.client local=salt.client.localClient() result=local.cmd('c2.salt.com','cmd.run'['ifconfig'])
收集服务器信息的代码:
代码出现的问题: 代码出现冗余:a.能够写一个公共的方法;b.能够写一个父类方法 代码高内聚:指一个方法就干一件事,剩下的无论,将相关的功能都汇集在一块儿,不相关的都不要 解耦合:
收集到的信息:
可插拔式的插件 收集上述信息:
配置信息
PLUGINS_DICT = { 'basic': 'src.plugins.basic.Basic', 'cpu': 'src.plugins.cpu.Cpu', 'disk': 'src.plugins.disk.Disk', 'memory': 'src.plugins.memory.Memory', 'nic': 'src.plugins.nic.Nic', }
插件的两种解决方案:
一、写一个公共类,让其余的全部类取继承Base这个基类
二、高精度 进行抽象封装
惟一标识问题
问题:实体机的SN号和咱们的虚拟机的SN号公用一个
解决:若是公司不采用虚拟机的信息,能够用SN做惟一标识,来进行更新
不然若是公司要采集虚拟机的信息,SN号此时不能使用
使用 进程池和线程池 解决并发的问题:
from concurrent.futures import ThreadPoolExecutor p = ThreadPoolExecutor(10) for hostname in hostnames: p.submit(self.run, hostname)
下载PyCrypto https://github.com/sfbahr/PyCrypto-Wheels pip3 install wheel 进入目录: pip3 install pycrypto-2.6.1-cp35-none-win32.whl
from Crypto.Cipher import AES def encrypt(message): key = b'dfdsdfsasdfdsdfs' cipher = AES.new(key, AES.MODE_CBC, key) ba_data = bytearray(message,encoding='utf-8') v1 = len(ba_data) v2 = v1 % 16 if v2 == 0: v3 = 16 else: v3 = 16 - v2 for i in range(v3): ba_data.append(v3) final_data = ba_data msg = cipher.encrypt(final_data) # 要加密的字符串,必须是16个字节或16个字节的倍数 return msg # ############################## 解密 ############################## def decrypt(msg): from Crypto.Cipher import AES key = b'dfdsdfsasdfdsdfs' cipher = AES.new(key, AES.MODE_CBC, key) result = cipher.decrypt(msg) # result = b'\xe8\xa6\x81\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0\xe5\xaf\x86\xe5\x8a\xa0sdfsd\t\t\t\t\t\t\t\t\t' data = result[0:-result[-1]] return str(data,encoding='utf-8') msg = encrypt('dsadbshabdnsabjdsa') res = decrypt(msg) print(res)
from django.db import models class UserProfile(models.Model): """ 用户信息 """ name = models.CharField(u'姓名', max_length=32) email = models.EmailField(u'邮箱') phone = models.CharField(u'座机', max_length=32) mobile = models.CharField(u'手机', max_length=32) password = models.CharField(u'密码', max_length=64) class Meta: verbose_name_plural = "用户表" def __str__(self): return self.name # class AdminInfo(models.Model): # """ # 用户登录相关信息 # """ # user_info = models.OneToOneField("UserProfile") # username = models.CharField(u'用户名', max_length=64) # password = models.CharField(u'密码', max_length=64) # # class Meta: # verbose_name_plural = "管理员表" # # def __str__(self): # return self.user_info.name class UserGroup(models.Model): """ 用户组 """ name = models.CharField(max_length=32, unique=True) users = models.ManyToManyField('UserProfile') class Meta: verbose_name_plural = "用户组表" def __str__(self): return self.name class BusinessUnit(models.Model): """ 业务线 """ name = models.CharField('业务线', max_length=64, unique=True) contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c') manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m') class Meta: verbose_name_plural = "业务线表" def __str__(self): return self.name class IDC(models.Model): """ 机房信息 """ name = models.CharField('机房', max_length=32) floor = models.IntegerField('楼层', default=1) class Meta: verbose_name_plural = "机房表" def __str__(self): return self.name class Tag(models.Model): """ 资产标签 """ name = models.CharField('标签', max_length=32, unique=True) class Meta: verbose_name_plural = "标签表" def __str__(self): return self.name class Asset(models.Model): """ 资产信息表,全部资产公共信息(交换机,服务器,防火墙等) """ device_type_choices = ( (1, '服务器'), (2, '交换机'), (3, '防火墙'), ) device_status_choices = ( (1, '上架'), (2, '在线'), (3, '离线'), (4, '下架'), ) device_type_id = models.IntegerField(choices=device_type_choices, default=1) device_status_id = models.IntegerField(choices=device_status_choices, default=1) cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True) cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True) idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True) business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True) tag = models.ManyToManyField('Tag') latest_date = models.DateField(null=True) create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "资产表" def __str__(self): return "%s-%s-%s" % (self.idc.name, self.cabinet_num, self.cabinet_order) class NetworkDevice(models.Model): """ 网络设备信息表 """ asset = models.OneToOneField('Asset') management_ip = models.CharField('管理IP', max_length=64, blank=True, null=True) vlan_ip = models.CharField('VlanIP', max_length=64, blank=True, null=True) intranet_ip = models.CharField('内网IP', max_length=128, blank=True, null=True) sn = models.CharField('SN号', max_length=64, unique=True) manufacture = models.CharField(verbose_name=u'制造商', max_length=128, null=True, blank=True) model = models.CharField('型号', max_length=128, null=True, blank=True) port_num = models.SmallIntegerField('端口个数', null=True, blank=True) device_detail = models.CharField('设置详细配置', max_length=255, null=True, blank=True) class Meta: verbose_name_plural = "网络设备" class Server(models.Model): """ 服务器信息 """ asset = models.OneToOneField('Asset') hostname = models.CharField(max_length=128, unique=True) sn = models.CharField('SN号', max_length=64, db_index=True) manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True) model = models.CharField('型号', max_length=64, null=True, blank=True) manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True) os_platform = models.CharField('系统', max_length=16, null=True, blank=True) os_version = models.CharField('系统版本', max_length=16, null=True, blank=True) cpu_count = models.IntegerField('CPU个数', null=True, blank=True) cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True) cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True) create_at = models.DateTimeField(auto_now_add=True, blank=True) class Meta: verbose_name_plural = "服务器表" def __str__(self): return self.hostname class Disk(models.Model): """ 硬盘信息 """ slot = models.CharField('插槽位', max_length=8) model = models.CharField('磁盘型号', max_length=32) capacity = models.CharField('磁盘容量GB', max_length=32) pd_type = models.CharField('磁盘类型', max_length=32) server_obj = models.ForeignKey('Server', related_name='disk') class Meta: verbose_name_plural = "硬盘表" def __str__(self): return self.slot class NIC(models.Model): """ 网卡信息 """ name = models.CharField('网卡名称', max_length=128) hwaddr = models.CharField('网卡mac地址', max_length=64) netmask = models.CharField(max_length=64) ipaddrs = models.CharField('ip地址', max_length=256) up = models.BooleanField(default=False) server_obj = models.ForeignKey('Server', related_name='nic') class Meta: verbose_name_plural = "网卡表" def __str__(self): return self.name class Memory(models.Model): """ 内存信息 """ slot = models.CharField('插槽位', max_length=32) manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True) model = models.CharField('型号', max_length=64) capacity = models.FloatField('容量', null=True, blank=True) sn = models.CharField('内存SN号', max_length=64, null=True, blank=True) speed = models.CharField('速度', max_length=16, null=True, blank=True) server_obj = models.ForeignKey('Server', related_name='memory') class Meta: verbose_name_plural = "内存表" def __str__(self): return self.slot class AssetRecord(models.Model): """ 资产变动记录,creator为空时,表示是资产汇报的数据。 """ asset_obj = models.ForeignKey('Asset', related_name='ar') content = models.TextField(null=True) # 新增硬盘 creator = models.ForeignKey('UserProfile', null=True, blank=True) # create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "资产记录表" def __str__(self): return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order) class ErrorLog(models.Model): """ 错误日志,如:agent采集数据错误 或 运行错误 """ asset_obj = models.ForeignKey('Asset', null=True, blank=True) title = models.CharField(max_length=16) content = models.TextField() create_at = models.DateTimeField(auto_now_add=True) class Meta: verbose_name_plural = "错误日志表" def __str__(self): return self.title
highcharts(图表库)
https://www.hcharts.cn/demo/highcharts/dark-unica
echarts(图表库)
Datatables(表格插件)
layui-经典模块化前端UI框架
https://www.layui.com/demo/admin.html
<link rel="stylesheet" href="/static/bs/dist/css/bootstrap.css"> <link rel="stylesheet" href="/static/bstable/src/extensions/editable/bootstrap-editable.css"> <link rel="stylesheet" href="/static/bstable/dist/bootstrap-table.css"> <script src="/static/jquery-3.3.1.js"></script> <script src="/static/bs/dist/js/bootstrap.js"></script> <script src="/static/bstable/dist/bootstrap-table.js"></script> <script src="/static/bstable/dist/locale/bootstrap-table-zh-CN.js"></script> <script src="/static/bstable/dist/extensions/editable/bootstrap-table-editable.js"></script> <script src="/static/bootstrap-editable.min.js"></script>
<body> <div class="panel-body" style="padding-bottom:0px;"> <div class="panel panel-default"> <div class="panel-heading">查询条件</div> <div class="panel-body"> <form id="formSearch" class="form-horizontal"> <div class="form-group" style="margin-top:15px"> <label class="control-label col-sm-1" for="txt_search_departmentname">部门名称</label> <div class="col-sm-3"> <input type="text" class="form-control" id="txt_search_departmentname"> </div> <label class="control-label col-sm-1" for="txt_search_statu">状态</label> <div class="col-sm-3"> <input type="text" class="form-control" id="txt_search_statu"> </div> <div class="col-sm-4" style="text-align:left;"> <button type="button" style="margin-left:50px" id="btn_query" class="btn btn-primary">查询</button> </div> </div> </form> </div> </div> <div id="toolbar" class="btn-group"> <button id="btn_add" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button id="btn_edit" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改 </button> <button id="btn_delete" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>删除 </button> </div> <table id="idc"></table> </div> </body>
$.fn.editable.defaults.mode = 'inline'; $('#'+tableid).bootstrapTable({ url: url, //请求后台的URL(*) method: 'get', //请求方式(*) toolbar: '#toolbar', //工具按钮用哪一个容器 striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,因此通常状况下须要设置一下这个属性(*) pagination: true, //是否显示分页(*) sortable: false, //是否启用排序 sortOrder: "asc", //排序方式 sidePagination: "client", //分页方式:client客户端分页,server服务端分页(*) pageNumber:1, //初始化加载第一页,默认第一页 pageSize: 10, //每页的记录行数(*) pageList: [10, 25, 50, 100], //可供选择的每页的行数(*) //search: true, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,因此,我的感受意义不大 strictSearch: true, showPaginationSwitch: true, showColumns: true, //是否显示全部的列 showRefresh: true, //是否显示刷新按钮 clickToSelect: true, //是否启用点击选中行 uniqueId: "id", //每一行的惟一标识,通常为主键列 showToggle:true, //是否显示详细视图和列表视图的切换按钮 cardView: false, //是否显示详细视图 detailView: false, //是否显示父子表 showExport: true, //是否显示导出 exportDataType: "basic", //basic', 'all', 'selected'. onEditableSave: function (field, row, oldValue, $el) { // delete row[0]; updata = {}; updata[field] = row[field]; updata['id'] = row['id']; $.ajax({ type: "POST", url: "/backend/modify/", data: { postdata: JSON.stringify(updata), 'action':'edit' }, success: function (data, status) { if (status == "success") { alert("编辑成功"); } }, error: function () { alert("Error"); }, complete: function () { } }); }, columns: [{ checkbox: true }, { field: 'one', title: '列1', editable: { type: 'text', title: '用户名', validate: function (v) { if (!v) return '用户名不能为空'; } } //验证数字 //editable: { // type: 'text', // title: '年龄', // validate: function (v) { // if (isNaN(v)) return '年龄必须是数字'; // var age = parseInt(v); // if (age <= 0) return '年龄必须是正整数'; // } //} //时间框 //editable: { // type: 'datetime', // title: '时间' //} //选择框 //editable: { // type: 'select', // title: '部门', // source: [{ value: "1", text: "研发部" }, { value: "2", text: "销售部" }, { value: "3", text: "行政部" }] //} //复选框 //editable: { //type: "checklist", //separator:",", //source: [{ value: 'bsb', text: '篮球' }, // { value: 'ftb', text: '足球' }, // { value: 'wsm', text: '游泳' }], //} //select2 //暂未使用到 //取后台数据 //editable: { // type: 'select', // title: '部门', // source: function () { // var result = []; // $.ajax({ // url: '/Editable/GetDepartments', // async: false, // type: "get", // data: {}, // success: function (data, status) { // $.each(data, function (key, value) { // result.push({ value: value.ID, text: value.Name }); // }); // } // }); // return result; // } //} }] });