CMDB
https://lupython.gitee.io/2018/05/05/CMDB介紹/ 尚澤凱博客地址
傳統運維與自動化運維的區別
傳統運維:
1、項目 上線:
a.產品經理前期調研(需求分析)
b.和開發進行評審
c.開發進行開發
d.測試進行測試
e.交給運維人員進行上線
上線:
直接將代給運維人員,讓業務運維人員把代碼放到服務器上
痛點:
曾加運維人員的成本
改進:
搞一個自動分發代碼 的系統
必須的條件:
1、服務器的信息(ip,hostname等 )
2、 能不能把報警自動化
3、 裝機系統:
傳統的裝機和布線:
idc運維:
用大量的人力和物力,來進行裝機
自動化運維:
collober 自動發送命令裝機
4、收集服務器的元信息:
a. Excel表格
缺點:1.認為干預太嚴重2.統計的時候也會有問題
b.搞一個系統
作用:自動的幫我們收集服務器的信息,並且自動的記錄我們的變更信息
CMDB包含的功能
1、用戶管理,記錄 測試,開發運維人員的用戶表
2、業務線管理,需要記錄業務的詳情
3、項目管理,指定此項目屬於那條業務線,以及項目詳情
4、應用管理,指定此應用的開發人員,屬於哪個項目,和代碼地址,部署目錄,部署集群,依賴的應用,軟件等信息
5、主機管理,包括雲主機,物理機,主機屬於哪個集群,運行着那個軟件,主機管理員,連接哪些網絡設備,雲主機的資源地,存儲等相關信息
6、主機變更管理主機的一些信息變更,例如管理員,所屬集群等信息更改,連接的網絡變更等
7、網絡設備管理,只要記錄網路設備的詳細信息,及網絡設備連接的上級設備
8、IP管理,IP屬於哪個主機,哪個網段,是否被占用
cmdb:
作用:自動的幫我們收集服務器的信息,並且自動的記錄我們的變更信息
願望:解放雙手,讓所有的東西都自動化
你為什么要使用cmdb?
因為我們公司在初期的時候,統計資產使用的的是Excel表格,剛開始的時候數據少,使用起來沒有覺得不方便,但是隨着業務的增加,一些問題便凸顯出來了,特別是當資產信息出現變更的時候,數據修改麻煩,可能越來越亂,因此,公司為了讓資產信息的收集簡單化,自動化,於是使用了CMDB。關於cmdb的實現經過我們公司的同事一起研究探討,一共有三種實現方法,第一種是agent方法,首先我們看到的是這些服務器,它們中有用Python語言編寫Agent腳本,服務器通過執行subprocess模塊的命令,服務器將得到的未采集的信息經過 執行subprocess模塊的命令后將得到的結果通過requests模塊發送給API,API再將數據寫入數據庫中然后通過web界面將數據展現給用戶,我們公司一開始准備使用Agent方式,結果發現Agent方法,需要為每一台服務器部署一個Agent 程序,實現起來麻煩,而且成本較高,不適合我們公司,於是我們又研究了SSH類的方法,
你負責什么?
收集資產信息的程序
django里的一些配置
遇到的困難是什么?是怎么解決的?
困難:唯一標識問題
開始收集服務器的元數據:(4種方案)
CMDB實現的四種方式
1、agent方式:
其本質上就是在各個服務器上執行subprocess.getoutput()命令,然后將每台機器上執行的結果,通過request模塊返回給主機API,然后主機API收到這些數據之后,放入到數據庫中,然后通過web界面將數據展現給用戶
- agent 腳本,python語言編輯
- API,經過一系列的操作 之后將數據傳給數據庫
- web界面
- 場景:服務器 較多
- 優點:速度快
- 缺點:需要為每一台服務器部署一個Agent 程序
如果在服務器較少的情況下,可以應用此方法
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()
2、 ssh類(parmiko, frbric,ansible)
中控機通過parmiko(py模塊)登錄到各個服務器上,然后執行命令的方式去獲取各個服務器上的信息
API從數據庫中獲取到未采集的機器列表后發送到中控機服務器中,中控機服務器通過parmiko模塊登錄到服務器上,進行信息的采集,服務器采集完后再將結果返回給中控機,仍后將從服務器中得到 的信息通過 request模塊發送給API,API通過主機名和SN作為唯一標識,將信息錄入到數據中,然后通過web界面將數據展現給用戶
- parmiko模塊(獲取主機名)
- API
- web界面
- 場景:服務器較少
- 缺點:依賴於網絡,速度慢
- 優點:沒有Agent,不需要為每一台服務器部署一個Agent 程序
3、saltstack方式
中控機從API中獲取未采集的資產信息后通過隊列發送命令給服務器執行。服務器執行完后將結果放到入另一個隊列中,中控機將獲取到的服務信息結果發送到API進而錄入數據庫。然后通過web界面將數據展現給用戶
- 場景:企業 之前已經在用
- 缺點:依賴於saltstack軟件
- 優點:速度快,開發成本低
saltstack的安裝和配置
1安裝和 配置
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端:
"""
1、安裝salt-minion
yum install salt-minion
2、修改配置文件 :vim /etc/salt/minion
master:10.0.0.128 #master的地址
3、啟動:service salt-minion start
"""
2、授權
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
3、執行 命令
在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.可以寫一個父類方法
代碼高內聚:指一個方法就干一件事,剩下的不管,將相關的功能都聚集在一起,不相關的都不要
解耦合:
收集到的信息:
- 主板信息(hostname,sn號)
- cpu信息(型號,幾個cpu等)
- disk磁盤信息(大小,幾塊)
- 內存memory信息
- 網卡信息
可插拔式的插件 收集上述信息:
配置信息
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',
}
插件的兩種解決方案:
1、寫一個公共類,讓其他的所有類取繼承Base這個基類
2、高精度 進行抽象封裝
唯一標識問題
問題:實體機的SN號和我們的虛擬機的SN號公用一個
解決:如果公司不采用虛擬機的信息,可以用SN作唯一標識,來進行更新
否則如果公司要采集虛擬機的信息,SN號此時不能使用
使用 進程池和線程池 解決並發的問題:
from concurrent.futures import ThreadPoolExecutor
p = ThreadPoolExecutor(10)
for hostname in hostnames:
p.submit(self.run, hostname)
AES介紹:
下載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)
CMDB數據表的設計:
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
前端代碼的實現
1、相關文件的引入
<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>
2、代碼初始化
<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>
3、Js代碼
$.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;
// }
//}
}]
});