zoukankan      html  css  js  c++  java
  • (转)CMDB介绍

    原文:https://www.cnblogs.com/xuecaichang/p/10265936.html

    CMDB开发---https://blog.csdn.net/bbwangj/article/details/80658497


    CMDB

    https://lupython.gitee.io/2018/05/05/CMDB%E4%BB%8B%E7%BB%8D/ 尚泽凯博客地址

    传统运维与自动化运维的区别

    传统运维:

    ​ 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界面将数据展现给用户

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

    2、 ssh类(parmiko, frbric,ansible)

    中控机通过parmiko(py模块)登录到各个服务器上,然后执行命令的方式去获取各个服务器上的信息

    img

    API从数据库中获取到未采集的机器列表后发送到中控机服务器中,中控机服务器通过parmiko模块登录到服务器上,进行信息的采集,服务器采集完后再将结果返回给中控机,仍后将从服务器中得到 的信息通过 request模块发送给API,API通过主机名和SN作为唯一标识,将信息录入到数据中,然后通过web界面将数据展现给用户
    • parmiko模块(获取主机名)
    • API
    • web界面
    • 场景:服务器较少
    • 缺点:依赖于网络,速度慢
    • 优点:没有Agent,不需要为每一台服务器部署一个Agent 程序

    3、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端:
    """
    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'xe8xa6x81xe5x8axa0xe5xafx86xe5x8axa0xe5xafx86xe5x8axa0sdfsd									'
        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

    前端代码的实现

    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;
                        //    }
                        //}
                    }]
                });
  • 相关阅读:
    _ 下划线 Underscores __init__
    Page not found (404) 不被Django的exception中间件捕捉 中间件
    从装修儿童房时的门锁说起
    欧拉定理 费马小定理的推广
    线性运算 非线性运算
    Optimistic concurrency control 死锁 悲观锁 乐观锁 自旋锁
    Avoiding Full Table Scans
    批量的单向的ssh 认证
    批量的单向的ssh 认证
    Corrupted MAC on input at /usr/local/perl/lib/site_perl/5.22.1/x86_64-linux/Net/SSH/Perl/Packet.pm l
  • 原文地址:https://www.cnblogs.com/liujiacai/p/11735849.html
Copyright © 2011-2022 走看看