CMDB整体项目梳理(1)

client端

简述整体数据采集流程

代码编写:

settings部分

通常,脚本的settings就是配置文件,用户如果需要对脚本进行自定义的一些操作(例如,添加自己的ssh账号密码,选择调试debug模式等)需要在配置文件中进行修改。
但是脚本端一般也有自己私有的配置文件,用于存储一些程序固定的变量,一些隐藏的信息,或者是不想让用户随意修改的配置。这就又需要一个default_settings。

用户可操作settings(外配置)

通常这里我们用来存放一些,之后需要经常使用的固定变量。例如程序的基础路径,对外通信的账号密码以及秘钥,调试模式的默认参数……

在这里我们还要额外针对本程序定义一个关于加载模块的路径和类对应的字典:

PLUGINS_DICT = {
    'basic': "src.plugins.basic.Basic",
    'board': "src.plugins.board.Board",
    'cpu': "src.plugins.cpu.Cpu",
    'disk': "src.plugins.disk.Disk",
    'memory': "src.plugins.memory.Memory",
    'nic': "src.plugins.nic.Nic",
}

用户不可操作的settings(内配置)

随便些什么吧,暂时好像还没用到,不过感觉可以把密钥封装在这里面,在server端也一样隐藏保存一个密钥。

串联两个settings

import os
import importlib
from . import global_settings
# from config import settings

class Settings(object):
    def __init__(self):

        # ######## 找到默认配置 ########
        for name in dir(global_settings):
            if name.isupper():
                value = getattr(global_settings,name)
                setattr(self,name,value)

        # ######## 找到自定义配置 ########
        # 根据字符串导入模块
        settings_module = os.environ.get('USER_SETTINGS')
        if not settings_module:
            return
  m = importlib.import_module(settings_module)
  # 注意这句话,这种创建settings的方法是借鉴了Django模块,利用参数的大写,来筛选获取必要的配置值。
        for name in dir(m):
            if name.isupper():
                value = getattr(m,name)
                setattr(self,name,value)

  • dir()内置函数的作用:
    用于获取目标(文件)中有哪些方法或可调用参数。
    同时也可以用来查询某种类型的数据,有哪些可调用方法,例如查询str类型就可以dir(''),list类型dir(['a','b'])

  • importlib模块----动态导入模块

>>> v='src.plugins.basic.Basic'
>>> v.rsplit('.')
['src', 'plugins', 'basic', 'Basic']
>>> v.rsplit('.',1)
['src.plugins.basic', 'Basic']

利用字符串切割获取类的路径(字符串形式用importlib获取)
以及类名,通过getattr方法获取目标类对象

plugin manage部分

针对每种不同的硬件,资产采集应该有不同的采集方式以及对应的数据结构化方式。
最开始的时候我很疑惑为什么老师用的方式不是直接使用模块来进行采集,因为在很多相关的博客推送上看到,用于linux监控的模块很多,也很强大。
仔细查询资料之后,我发现“硬件信息采集”和“linux信息监控”是完全两个概念。前者的确没有非常好的解决方案,只能通过subprocess模块向linux主机发送命令并且采集/proc/文件夹下信息才可以完成。

所以这个plugin_manage其实就是负责:

  1. 获取单个硬件信息模块的位置并且运行;
  2. 针对不同的采集方式,装饰输出对应的命令;

由于client端有三种采集方式,传递到API后端的方式有两种,做以下图示意:

代码部分

  1. 利用之前在settings中设置的plugins_dict,导出并且运行单个硬件模块采集的信息,并且存储到result_dict
response={}
for k,v in self.plugin_dict.items():
    # 'basic':'src.plugins.basic.Basic'
    ret={"status":True,'data':None}
    try:
    # 朋友,记住这个反射的用法,在server端数据库更新的时候还会用到
        module_path,class_name=v.rsplit('.', 1)
        m=import_module(module_path)
        cls=getattr(m,class_name)
        if hasattr(cls,'initial'):
            obj=cls.initial()
            # 这个initial是为了给对象实例化的时候添加一点自定的初始化操作
        else:
            # 当然没有的话,就是简单的实例化了
            obj=cls()

        result=cls().process(self.command,self.debug)
        ret['data']=result
    except Exception as e:
        ret['status']=False
        ret['data']="[%s][%s] 采集数据出现错误 : %s" %(self.hostname if self.hostname else "AGENT",k,traceback.format_exc())

    response[k]=ret
  1. 规范化三种不同采集方式的命令输出:

原先老师的写法是把每种调用方式写成私有方法再进行调用的。但是这边为了贴代码方便所以就整合到一个command方法里了。并不影响上面函数的调用。

def command(self,cmd):
    if self.mode== "AGENT":
        output = subprocess.getoutput(cmd)
        return output
    elif self.mode=="SALT":
        salt_cmd = "salt '%s' cmd.run '%s'" % (self.hostname, cmd)
        output = subprocess.getoutput(salt_cmd)
        return output
    elif self.mode=="SSH":
        import paramiko
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, password=self.ssh_pwd)
        stdin, stdout, stderr = ssh.exec_command(cmd)
        result = stdout.read()
        ssh.close()
        return result
    else:
        raise Exception('no such mode (agent/ssh/salt is allowed)')

扩展的知识部分:

为什么要用cmd命令,而不是利用模块?

这个是我最最最开始纠结的点,明明是一个python程序,为什么不贯彻到底用python来解决这个资产采集,既可以很好的控制数据结构,还可以借用别人的模块来实现平台通用。

刚开始我查询了一些模块资料,发现python作为自动化运维利器,在获取以及监控linux数据这点上真的不是一般的优秀。下面简单的记录几个,在以后遇到类似项目的时候,可以想的起来用:

  • platform:
    这个模块给予的信息,应该是最直观的。
>>> platform.uname()
uname_result(system='Windows', node='LAPTOP-AV9UPVQJ', release='10', version='10.0.14393', machine='AMD64', processor='Intel64 Family 6 Model 158 Stepping 9, GenuineIntel')
>>> platform.python_build()
('v3.6.1:69c0db5', 'Mar 21 2017 18:41:36')
>>> platform.python_compiler()
'MSC v.1900 64 bit (AMD64)'

但是这个模块,目前我看到的资料显示,除了上面的主机信息可以通过一个uname方法直接完全取出(也可以单个单个取),以及还有一些我没放的检查主机python版本的方法之外,没有其他功能了。这个模块的版本目前比较低,只有0.1还是0.2,放在python标准库里,可以直接import。
所以他的应用场景我觉得比较局限,用于检测目标机器上的python版本和运行环境比较合适。

  • wmi:
    针对windows平台的一个模块,尚未深入研究,但是考虑到未来对windows平台的一些监控,还是贴一下网址吧:

转载自伯乐在线:http://python.jobbole.com/86349/

  • psutil:
    这个是,本来让我产生错觉“哇,在下已经找到了最优解”的一个模块。
    相比platform的文档少,功能少,wmi的平台支持单一。这个psutil简直太可爱了……

官方文档:
https://pypi.python.org/pypi/psutil
http://pythonhosted.org/psutil/
操作实例:
http://blog.csdn.net/bubu8633/article/details/8258342
还有多平台支持,单个方法获取单个数据(硬盘,网卡IO,存储占用)……真的是清纯不做作,踏实又肯干

但是最后让我醒悟过来的还是,这个cmdb的功能目标是:获取硬件信息而不是获取硬件运行指标。利用psutil能获得硬盘占用信息,存储占用信息,但是不能获得硬盘型号,网卡型号……

所以获取硬件信息(硬盘大小,网卡ip,存储类型)还是需要通过命令去获得。

怎么把命令从python程序中递交出去?

subprocess大法好!(这是个坑,晚点再填吧)

递交什么命令,查询什么文件?

dmidecode大法好!(这是个坑,晚点再填吧)

http://www.iyunv.com/thread-291062-1-1.html
上面的连接中的帖子,提供了一个尝试使用dmidecode这个工具的方法。并不能说很好,但是给我入门尝试使用并且查看dmidecode提供的信息已经很不错了。
首先这个工具是在linux上的,查看的时候需要root权限。因为我的测试环境是用户级别的Ubuntu,不能直接用root用户登录,所以sudo之后还要输入密码(但是其实是可以在配置文件里面设置特殊用户组sudo不使用密码的,之前试了一下直接把机子弄得没法用了所以这台就先晚点再试吧)。在centos等发行版中,还是支持root用户不需要输入密码的。

刚才在查看老师board模块代码的时候,看到老师使用了特别的命令

scott@scott-virtual-machine:~$ sudo dmidecode -t1
[sudo] scott 的密码: 
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.

Handle 0x0001, DMI type 1, 27 bytes
System Information
    Manufacturer: VMware, Inc.
    Product Name: VMware Virtual Platform
    Version: None
    Serial Number: VMware-56 4d b1 e1 37 53 d3 c3-41 4c dc 75 07 6f 32 b5
    UUID: E1B14D56-5337-C3D3-414C-DC75076F32B5
    Wake-up Type: Power Switch
    SKU Number: Not Specified
    Family: Not Specified

之前看帖子里的例子,用的是sudo dmidecode |less,发现这个返回回来的信息真的是奇长无比……而老师的命令好像是可以直接拿到一个类的信息。所以机智的我以此类推:

scott@scott-virtual-machine:~$ sudo dmidecode -t2
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.

Handle 0x0002, DMI type 2, 15 bytes
Base Board Information
    Manufacturer: Intel Corporation
    Product Name: 440BX Desktop Reference Platform
    Version: None
    Serial Number: None
    Asset Tag: Not Specified
    Features: None
    Location In Chassis: Not Specified
    Chassis Handle: 0x0000
    Type: Unknown
    Contained Object Handles: 0

同理这个dmidecode后面跟的-t确实有划分获取dmidecode信息的功能。掌握了这个特别的技巧之后,我不禁又要去搜搜这个dmidecode的进阶技巧了

http://msiyuetian.blog.51cto.com/8637744/1787978
我们通过 dmidecode 命令可以获取厂商、产品型号、序列号等、但是 dmidecode 命令输出的信息太多,我们只需要 System Information 下的 Manufacturer、Product Name、Serial Number 三个信息.

上面的这篇文章采用的仍然是startwith匹配的方式去获取,其中有一个利用空行为空来排除空行对信息列表的影响的列表生成式,有较好的参考作用,mark一下。

在网上搜索了一下,果然有收获,连接如下:http://linux-wiki.cn/wiki/dmidecode
老师使用的这个-t其实可以用表达更加明确的方式:

命令后缀 作用
-q or --quiet 不显示太多信息,比如某条数据是从哪里读来的等等,为了得到简洁的信息,此条很有用。
-t or --type TYPE 指定要哪方面的内容
-s or --string KEYWORD 显示特定的关键字
-u or --dump 直接显示DMI表信息而不接吗,以16进制的文本方式显示

-t 后的参数

后缀 作用
bios bios的各项信息
system 系统信息,在我的笔记本上可以看到版本、型号、序号等信息。
baseboard 主板信息
chassis 机架信息,包括制造商,机架类型,机架高度,SN号,电源模块数
processor CPU信息
memory 内存信息
cache 缓存信息
connector PCI设备信息
slot 插槽信息

链接中还有其他后缀的信息,例如-s可以用于获取单属性参数。

中控机模式下,需要怎么把请求发送给服务器?

saltstack大法好!(这是个坑,晚点再填吧)
ssh大法好!(这是个坑,晚点再填吧)

原文地址:https://www.cnblogs.com/scott-lv/p/7521827.html