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)