1. 程式人生 > 實用技巧 >CMDB 資產管理系統

CMDB 資產管理系統

CMDB介紹

目錄

傳統運維與自動化運維的區別
CMDB包含的功能
CMDB實現的四種方式
1、agent方式:
2、 ssh類(parmiko, frbric,ansible)
3、saltstack方式
saltstack的安裝和配置
1安裝和 配置
2、授權
3、執行 命令
AES介紹:
CMDB資料表的設計:
圖表的設計
前端程式碼的實現
1、相關檔案的引入
2、程式碼初始化
3、Js程式碼

Top
傳統運維與自動化運維的區別
傳統運維:

​ 1、專案 上線:

​ a.產品經理前期調研(需求分析)

​ b.和開發進行評審

​ c.開發進行開發

​ d.測試進行測試

​ e.交給運維人員進行上線

上線:

​ 直接將代給運維人員,讓業務運維人員把程式碼放到伺服器上

痛點:

​ 曾加運維人員的成本

改進:

​ 搞一個自動分發程式碼 的系統

​ 必須的條件:

​ 1、伺服器的資訊(ip,hostname等 )

​ 2、 能不能把報警自動化

​ 3、 裝機系統:

​ 傳統的裝機和佈線:

idc運維:

​ 用大量的人力和物力,來進行裝機

自動化運維:

​ collober 自動傳送命令裝機

​ 4、收集伺服器的元資訊:

a. Excel表格
缺點:1.認為干預太嚴重2.統計的時候也會有問題
b.搞一個系統
作用:自動的幫我們收集伺服器的資訊,並且自動的記錄我們的變更資訊
Top
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種方案)

Top
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軟體
優點:速度快,開發成本低
Top
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)
Top
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)

Top
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

Top
圖表的設計
highcharts(圖表庫)

https://www.hcharts.cn/demo/highcharts/dark-unica

echarts(圖表庫)

https://echarts.baidu.com/

Datatables(表格外掛)

http://www.datatables.club/

layui-經典模組化前端UI框架

https://www.layui.com/demo/admin.html

Top
前端程式碼的實現
1、相關檔案的引入

2、程式碼初始化

查詢條件
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; // } //} }] });