• CMDB介绍


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

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

  • 相关阅读:
    超详细JSON解析步骤
    HTTP请求头和响应头总结
    Oracle 数据库常用操作语句大全
    关于HTTP协议,一篇就够了
    PowerDesigner工具建表步骤
    求助:ACM剑气算法
    一道题
    个人Java学习中的遇到的几个模糊的地方
    Java编程:数学黑洞6174
    高级查询
  • 原文地址:https://www.cnblogs.com/xuecaichang/p/10265936.html
Copyright © 2020-2023  润新知