CMDB介紹

CMDB

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.搞一個系統
做用:自動的幫咱們收集服務器的信息,而且自動的記錄咱們的變動信息

CMDB包含的功能

一、用戶管理,記錄 測試,開發運維人員的用戶表
二、業務線管理,須要記錄業務的詳情
三、項目管理,指定此項目屬於那條業務線,以及項目詳情
四、應用管理,指定此應用的開發人員,屬於哪一個項目,和代碼地址,部署目錄,部署集羣,依賴的應用,軟件等信息
五、主機管理,包括雲主機,物理機,主機屬於哪一個集羣,運行着那個軟件,主機管理員,鏈接哪些網絡設備,雲主機的資源地,存儲等相關信息
六、主機變動管理主機的一些信息變動,例如管理員,所屬集羣等信息更改,鏈接的網絡變動等
七、網絡設備管理,只要記錄網路設備的詳細信息,及網絡設備鏈接的上級設備
八、IP管理,IP屬於哪一個主機,哪一個網段,是否被佔用

cmdb:

做用:自動的幫咱們收集服務器的信息,而且自動的記錄咱們的變動信息

願望:解放雙手,讓全部的東西都自動化

你爲何要使用cmdb?

由於咱們公司在初期的時候,統計資產使用的的是Excel表格,剛開始的時候數據少,使用起來沒有以爲不方便,可是隨着業務的增長,一些問題便凸顯出來了,特別是當資產信息出現變動的時候,數據修改麻煩,可能愈來愈亂,所以,公司爲了讓資產信息的收集簡單化,自動化,因而使用了CMDB。關於cmdb的實現通過咱們公司的同事一塊兒研究探討,一共有三種實現方法,第一種是agent方法,首先咱們看到的是這些服務器,它們中有用Python語言編寫Agent腳本,服務器經過執行subprocess模塊的命令,服務器將獲得的未採集的信息通過 執行subprocess模塊的命令後將獲得的結果經過requests模塊發送給API,API再將數據寫入數據庫中而後經過web界面將數據展示給用戶,咱們公司一開始準備使用Agent方式,結果發現Agent方法,須要爲每一臺服務器部署一個Agent 程序,實現起來麻煩,並且成本較高,不適合咱們公司,因而咱們又研究了SSH類的方法,

你負責什麼?

收集資產信息的程序

django裏的一些配置

遇到的困難是什麼?是怎麼解決的?

困難:惟一標識問題

開始收集服務器的元數據:(4種方案)

CMDB實現的四種方式

一、agent方式:

其本質上就是在各個服務器上執行subprocess.getoutput()命令,而後將每臺機器上執行的結果,經過request模塊返回給主機API,而後主機API收到這些數據以後,放入到數據庫中,而後經過web界面將數據展示給用戶

img

  • 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()

二、 ssh類(parmiko, frbric,ansible)

中控機經過parmiko(py模塊)登陸到各個服務器上,而後執行命令的方式去獲取各個服務器上的信息

img

API從數據庫中獲取到未採集的機器列表後發送到中控機服務器中,中控機服務器經過parmiko模塊登陸到服務器上,進行信息的採集,服務器採集完後再將結果返回給中控機,仍後將從服務器中獲得 的信息經過 request模塊發送給API,API經過主機名和SN做爲惟一標識,將信息錄入到數據中,而後經過web界面將數據展示給用戶
  • parmiko模塊(獲取主機名)
  • API
  • web界面
  • 場景:服務器較少
  • 缺點:依賴於網絡,速度慢
  • 優勢:沒有Agent,不須要爲每一臺服務器部署一個Agent 程序

三、saltstack方式

img

中控機從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端:
"""
一、安裝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.能夠寫一個父類方法

代碼高內聚:指一個方法就幹一件事,剩下的無論,將相關的功能都彙集在一塊兒,不相關的都不要

解耦合:

收集到的信息:

  • 主板信息(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',
                }

插件的兩種解決方案:

​ 一、寫一個公共類,讓其餘的全部類取繼承Base這個基類

​ 二、高精度 進行抽象封裝

惟一標識問題

問題:實體機的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(圖表庫)

https://echarts.baidu.com/

Datatables(表格插件)

http://www.datatables.club/

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>

三、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;
                    //    }
                    //}
                }]
            });

相關文章
相關標籤/搜索