20200311 CMDB的表设计

Posted fwzzz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20200311 CMDB的表设计相关的知识,希望对你有一定的参考价值。

昨日内容

1.CMDB架构
	- agent
		待采集的服务器里执行采集的指令,利用requests模块发送至api接口,并保存到数据库,利用Django的web服务启动服务,用户获取
	- ssh
		利用统一的一个中控机进行获取服务器信息,并转发至api
		
2.CMDB方案分为几部分,(或者有几个人做的)你负责那一部分
	三部分: 	
		采集部分 (1个人)
		API数据表设计和数据分析入库  (1个人)
		web输出,数据展示  (共用1人)
		
3.采集部分怎么完成的
	- 高级配置文件,参考Django的高级配置文件完成的
		自定义整合类,利用getattr与getattr分别将设置文件中的属性遍历并保存至类名称空间中(注意自定制的在高级配置下).实例化类得到对象,这样在获取settings设置时,对象.属性自动获取配置.
		
	- 采用了高内聚低耦合的原则来完成采集代码
		例如:采集cpu信息的时候,会单独的写一个cpu.py文件,这个文件中的所有逻辑代码,都是与采集cpu的信息相关的,这样的好处就是:将来查询问题是,方便查询
	- 可插拔式的采集,参考Django的中间件(利用注释)
		利用类方法,从配置文件中读取配置,获取配置的获取信息的所有路径,利用getattr获取模块类,循环执行即可.(采集类方法的名称需一致)
		
4.遇到的问题是什么,怎么解决的
	Linux命令的不熟悉,问运维,去Google
	沟通方面的,沟通能力的提升
	
5.做这个项目中,你觉得你收获了什么
    大家注意的是,CMDB项目是一个ToB的项目,就是给公司用的项目,和办公系统(金蝶用友, erp),路飞学成就是一个Toc项目, 是给广大用户用的。ToB的用户是比ToC的用户少很多的, ToC的技术要求比ToB的技术要求大很多,但是ToB的项目架构基本上每一个公司都差不多 
    
    运维开发工程师, 更偏向开发,开发占比80%,运维占20%。 面试官是一个运维的,开发能力可能还不如你,shell
    
    运维开发优势:
        自动化运维的项目,在每一个公司中都差不多,项目经验是不断积累的,所以你下次跳槽的 时候,就可以将这些开发好的系统,90%的功能都可以搬过来复用

CMDB设计

1.完善客户端采集功能

错误异常处理

采集出错的错误信息也是需要进行上报的

使用traceback模块实现获取详细的错误信息

import traceback 
def test(): 
    try:
        int(‘asdc‘) 
    except Exception as e: 
        print(traceback.format_exc()) 
        
    print(‘hello‘) 
    
test()

traceback

该模块提供了一个标准接口,用于提取,格式化和打印Python程序的堆栈跟踪。它在打印堆栈跟踪时完全模仿了Python解释器的行为。当您想要在程序控制下打印堆栈跟踪时,这非常有用,例如在解释器周围的“包装器”中。

  • traceback.print_tb(tb [,limit [,file ] ] )
    打印以限制回溯对象tb中的堆栈跟踪条目。如果 省略limit或者None打印所有条目。如果省略文件或None输出转到sys.stderr; 否则它应该是一个打开的文件或类似文件的对象来接收输出。

  • traceback.print_exception(etype,value,tb [,limit [,file ] ] )

    ? 打印异常信息,最多限制堆栈跟踪条目从traceback tb到文件。这与print_tb()以下方式不同:(1)如果tb不是None,则打印标题; (2)在堆栈跟踪后打印异常etype和值 ; (3)如果etype是且值具有适当的格式,则打印出发生语法错误的行,其中插入符号表示错误的大致位置。Traceback (most recent call last):SyntaxError

  • traceback.print_exc([ limit [,file ] ] )
    这是一个简写。(实际上,它用于以线程安全的方式检索相同的信息,而不是使用已弃用的变量。)print_exception(sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file)sys.exc_info()

  • traceback.format_exc([ 限制] )
    这就像print_exc(limit)但返回一个字符串而不是打印到文件。

参考博客: https://blog.csdn.net/qq_31446377/article/details/89703542

使用

    def __init__(self):
        # 初始化,获取setting中的配置
        self.plugins_dict = setting.PLUGINS_DICT    # 规定获取服务器信息
        self.settings = setting.MODE    # 获取服务器信息的方式
        self.debug = setting.DEBUG      # 开发上线模式


    # 从配置文件中读取采集的插件配置,执行插件类中对应的方法
    def execute(self):
        # 1.循环获取配置中的value值
        response = {}
        for k, v in self.plugins_dict.items():
            ret = {‘status‘:None, ‘data‘:None}
            ‘‘‘
            自定义的配置,k是想要获取的信息名,v是具体执行文件路径
            ‘‘‘
            # 错误异常的捕获
            try:
                # 2.分析采集类的路径
                ‘‘‘
                获得方法的路径信息与具体类名
                ‘‘‘
                module_path, class_name = v.rsplit(‘.‘, 1)
                # 3.获取导入模块的路径 import_module: 导入字符串的模块路径
                module = importlib.import_module(module_path)
                # 4.从模块中获取导入类
                cls = getattr(module,class_name)
                # 实例化类,执行类对应的具体采集方法  (将类方法command_func传入获取方法中,减少代码冗余)
                res = cls().process(self.command_func, self.debug)
                # 执行注册文件中的每一个方法,并保存信息到字典中
                ret[‘status‘] = 10000
                ret[‘data‘] = res
            except Exception as e:
                ret[‘status‘] = 10001
                # 使用traceback模块就不需要添加e
                ret[‘data‘] = ‘错误信息是:%s‘ % (traceback.format_exc())

            response[k] = ret
        return response

ssh类方案的完善

方法需要采集服务系信息并发送给API,所以整合代码

start.py

from src.script import run

if __name__ == ‘__main__‘:
    run()

script.py

from lib.conf.config import setting
from src.client import Agent, Ssh

def run():
    # 判断设置执行的方法是什么,实例化相应的类
    # if setting.MODE == ‘agent‘:
    #     Agent().collectAndPost()
    # else:
    #     Ssh().collectAndPost()
    if setting.MODE == ‘agent‘:
        obj = Agent()
    else:
        obj = Ssh()
    obj.collectAndPost()

client.py

#-*- coding: utf-8 -*-
#!/usr/bin/env python3

‘ 获取信息,发送给api ‘

__author__ = ‘Fwzzz‘

from lib.conf.config import setting
from src.plugins import PluginsManager
import requests
import json


# agent方法类
class Agent():
    def collectAndPost(self):
        # 获取服务器信息
        ret = PluginsManager().execute()
        # 启动执行execute()方法获取服务器的数据 (定义返回的是字典)
        for k, v in ret.items():
            print(k,v)

        # requests.post(setting.API_URL, data=json.dumps(ret))  # 转换为json格式数据
        requests.post(setting.API_URL, json=ret)  # 可以直接使用json=ret转换


# ssh方法类
class Ssh():
    # 需要将分配好的主机名写入数据库 (数据库中提前录好主机名,然后ssh类获取)
    def get_hostname(self):
        # 获取后端API查询出的主机名
        hostnames = requests.get(setting.API_URl)

        return hostnames   # 后端发送的是列表形式主机名

    # 执行获取,发送任务函数
    def task(self,hostname):
        ret = PluginsManager(hostname=hostname).execute()
        # 发送给后端api入库信息
        requests.post(setting.API_URL, json=ret)

    # 线程池函数
    def collectAndPost(self):
        hostnames = self.get_hostname()
        # 线程池运行
        from concurrent.futures import ThreadPoolExecutor
        pool = ThreadPoolExecutor(10)
        # 获取主机名,一个个的服务器去登录采集信息
        for hostname in hostnames:
            pool.submit(self.task,hostname)


注意:

1.需要将分配好的主机名写入到数据库中

2.采集类 plugin_manager 初始化的时候,需要传入主机名

提高ssh类方案的采集效率

使用线程池或者进程池来去提高
	线程与进程的区别:
		线程池: py2中没有,py3有
		进程池: pt2,3都有
		
        
# python3中线程池的使用方式:

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor 

p = ThreadPoolExecutor(10) 

def test(i): 
    time.sleep(2) 
    print(i) 
    
    for i in range(100): 
        p.submit(test, i)

2. api数据分析,数据入库

先有数据表,需要数据模型model的设计

技术图片

  • model类应该写在哪里?

    • 写在第三方的一个app中,repository中
  • model类中的字段怎么设计

    • 把握一个原则,字段的设计一定是根据客户端提交过来的字段来进行设计的
  • 业务线

    • 腾讯: 
      	三条业务线,包括:微信,QQ, 王者荣耀
      	
      	QQ跑在多台服务?上,如果,一台服务?上只能跑QQ的话, 这种关系叫:一对多关系 
      	QQ跑在多台服务?上, 如果,一台服务?上还可以跑其他的业务,微信,这种关系叫:多对多的关系
      

表关系

技术图片

技术图片

server端代码

创建专用的repository APP,用于管理models类字段

from django.db import models

# Create your models here.
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 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 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 BusinessUnit(models.Model):
    """
    业务线
    """
    name = models.CharField(‘业务线‘, max_length=64, unique=True)
    contact = models.ForeignKey(‘UserGroup‘, verbose_name=‘业务联系人‘, related_name=‘c‘, on_delete=models.CASCADE)
    manager = models.ForeignKey(‘UserGroup‘, verbose_name=‘系统管理员‘, related_name=‘m‘, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = "业务线表"

    def __str__(self):
        return self.name



class Server(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, on_delete=models.CASCADE)
    business_unit = models.ForeignKey(‘BusinessUnit‘, verbose_name=‘属于的业务线‘, null=True, blank=True, on_delete=models.CASCADE)


    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‘, on_delete=models.CASCADE)

    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‘, on_delete=models.CASCADE)


    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‘, on_delete=models.CASCADE)


    class Meta:
        verbose_name_plural = "内存表"

    def __str__(self):
        return self.slot


class ErrorLog(models.Model):
    """
    错误日志,如:agent采集数据错误 或 运行错误
    """
    asset_obj = models.ForeignKey(‘Server‘, null=True, blank=True, on_delete=models.CASCADE)
    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

注册app

admin中注册后台表

from django.contrib import admin

# Register your models here.
from repository import models

admin.site.register(models.Server)
admin.site.register(models.Disk)
admin.site.register(models.UserProfile)
admin.site.register(models.UserGroup)
admin.site.register(models.Memory)
admin.site.register(models.ErrorLog)
admin.site.register(models.BusinessUnit)
admin.site.register(models.NIC)
admin.site.register(models.IDC)

以上是关于20200311 CMDB的表设计的主要内容,如果未能解决你的问题,请参考以下文章

优云CMDB经验分享之 – 剖析CMDB的设计过程

CMDB3 完善采集端代码(ssh方案的多线程采集), 异常处理, 服务端目录结构的设计(django的app), API数据分析比对入库

LuffyCity-CMDB实战

CMDB项目之监控模板template设计

python_way ,day25 CMDB_models (数据库设计)

Python 学习 第十篇 CMDB用户权限管理