CMDB开发-资产采集功能开发

一:资产采集功能开发

①agent方式

通过在客户端写一些脚本,然后加入到定时任务,每隔一段时间执行一下命令采集硬件信息,发给API,API根据收到的数据更新数据库相关信息。

思路核心代码(未完善)

import subprocess
import requests
# request是第三方模块,pip3 install requests

# ################## 采集数据 ##################
result = subprocess.getoutput('ipconfig')
# result要做正则处理获取想要数据

# 整理资产信息
data_dict ={
    'nic': {},
    'disk':{},
    'mem':{}
}

# ################## 发送数据给API ##################
requests.post('http://www.127.0.0.1:8000/assets.html',data=data_dict)
②SSH类的方式

先去API那,API从数据库中取出没有进行资产采集的主机列表,然后去对应的主机列表里边基于paramiko模块执行命令,收集到数据,把数据发给API,然后进行数据库的更新。

思路核心代码(未完善)

# 基于paramiko模块, pip3 install paramiko
import requests
import paramiko

# ################## 获取今日未采集主机名 ##################
result = requests.get('http://www.127.0.0.1:8000/assets.html')
result = ['c1.com','c2.com']


# ################## 通过paramiko连接远程服务器,执行命令 ##################
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='192.168.14.36', port=22, username='wupeiqi', password='123')

# 执行命令
stdin, stdout, stderr = ssh.exec_command('df')

# 获取命令结果
result = stdout.read()

# 关闭连接
ssh.close()
print(result)

data_dict = {result}

# ################## 发送数据 ##################
requests.post('http://www.127.0.0.1:8000/assets.html',data=data_dict)
③基于saltstack

先去API那里,API从数据库里边拿到没有采集资产的主机给中控机,然后基于saltstack去对应的主机那里执行命令,saltstack把要执行的命令先放到队列里,然后主机执行队列里面对应的命令,把命令执行结果(数据)放到另外一个队列里边,saltstack再去这个队列里边拿数据,然后发给API,API进行数据的更新。

面试问为什么不考虑使用ansible?

答:因为ansible就是基于ssh的,跟第二种是差不多的,我把他归到了ssh类里边

要先下载saltstack,去阿里镜像站epel源和网络源配置好就可以下载
大致流程

# 1. 安装saltstack
# rpm --import https://repo.saltstack.com/yum/redhat/6/x86_64/latest/SALTSTACK-GPG-KEY.pub

"""
        Master: yum install salt-master
       Master准备:
            a. 配置文件,监听本机IP
                vim /etc/salt/master
                interface: 本机IP地址
            b. 启动master
                /etc/init.d/salt-master start


        Slave: yum install salt-minion
        Slave准备:
            a. 配置文件,连接那个master
                vim /etc/salt/minion
                master: 远程master地址
            b. 启动slave
                /etc/init.d/salt-minion start

2. 创建关系
    查看
    Master:salt-key -L
        Accepted Keys:
        Denied Keys:
        Unaccepted Keys:
            c1.com
            c2.com
            c3.com
        Rejected Keys:
    接受
    Master:salt-key -a c1.com
        Accepted Keys:
            c1.com
            c2.com
        Denied Keys:
        Unaccepted Keys:
            c3.com
        Rejected Keys:


3. 执行命令
    master:
        salt 'c1.com' cmd.run 'ifconfig'

    import salt.client
    local = salt.client.LocalClient()
    result = local.cmd('c2.salt.com', 'cmd.run', ['ifconfig'])

"""
# ################## 获取今日未采集主机名 ##################
#result = requests.get('http://www.127.0.0.1:8000/assets.html')
# result = ['c1.com','c2.com']


# ################## 远程服务器执行命令 ##################
# import subprocess
# result = subprocess.getoutput("salt 'c1.com' cmd.run 'ifconfig'")
#
# import salt.client
# local = salt.client.LocalClient()
# result = local.cmd('c2.salt.com', 'cmd.run', ['ifconfig'])


# ################## 发送数据 ##################
# requests.post('http://www.127.0.0.1:8000/assets.html',data=data_dict)

CMDB插件开发

目录结构,主目录下主要有这几个文件夹,src是拿来写逻辑代码的,

  • src下面的plugins文件夹是存放插件的,主要是为了可插拔,我们写好一个Base类,然后未来想添加采集网卡信息,内存信息或者硬盘信息等,就再创建相应的文件,里面写上对应的类,继承自Base类就好了,
  • package文件实现将采集到的数据进行打包,我们每加一个插件,就去配置文件里面记录上,然后package里边通过反射进行打包数据,否则每次加插件都要改package文件,容易出问题。
    在这里插入图片描述

CMDB采集资产

CMDB采集资产数据标识规定

我觉得每个公司在开发CMDB的时候,或者说一个运维平台的时候,我们需要先指定好规则,让大家去遵守,设计CMDB首先要确定资产数据的唯一标识,IP这个会变的,不行,一开始想用SN号,也就是主板上的序列号,这个对物理机来讲是唯一的,但是公司里面有虚拟机的,所以不行,我用了主机名来做标识,约定好主机名不能改,大部分情况下也是不能随便改了,但是如果某个需求必须改,又出现问题了,所以我先把第一次的主机名先放到文件里边,到时候去这个文件里边拿,那么后面主机名改了也不影响了。系统重装的时候怎么办?系统重装如果是批量装机,我们点按钮,我们是可以看到那个主机名的。

知识拾遗之线程线程池

进程池与线程池,线程池是Python3才有的

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor
import time
def task(arg):
    print(arg)
    time.sleep(1)

# pool = ProcessPoolExecutor(5)
pool = ThreadPoolExecutor(5)

for i in range(50):
    pool.submit(task,i)

使用线程池开多个线程来向API发数据。线程池规定了每次最多开多少个线程,防止线程开多了,上下文切换,也就是CPU在线程之间切换的频率太快了,相当于是看一本书,没看几个字就要传给下一个人。

class AutoSSH(AutoBase):
    def process(self):
        """
        根据主机名获取资产信息,将其发送到API
        :return:
        """
        task = self.get_asset() # 采集到数据
        if not task['status']:
            # 打印错误日志,true表示正确运行信息,false表示错误信息
            Logger().log(task['message'], False)

        pool = ThreadPoolExecutor(10)
        for item in task['data']:
            hostname = item['hostname']
            pool.submit(self.run, hostname) # 开多线程调用run方法把数据发送到API
        pool.shutdown(wait=True)
采集资产之日志记录

回调函数,获取资产,发到API,API可能出错呀,这个出错我们不能草草了之,什么时候出错,或者运行正常了,都用日志记录下来,并且日志存储的路径可以在配置文件里边改。取到公司的时候,应用程序的目录要做迁移的时候,千万不能把日志写死在某个文件上,否则随着程序的运行,越来越大越来越大就不行了

def callback(self, status, response):
   """
   提交资产后的回调函数
   :param status: 是否请求成功
   :param response: 请求成功,则是响应内容对象;请求错误,则是异常对象
   :return:
   """
   if not status:
       Logger().log(str(response), False)
       return
   ret = json.loads(response.text)
   if ret['code'] == 1000:
       Logger().log(ret['message'], True)
   else:
       Logger().log(ret['message'], False)
序列号发送数据到API的数据

发送数据到API,不能传对象,像字典,列表,这些可以进行json序列化,但是如果想传我们自己写的类给API怎么办,这时候需要进行序列化的自定制

import json
from datetime import date
from datetime import datetime

class JsonCustomEncoder(json.JSONEncoder):
    def default(self, field):
        if isinstance(field, datetime):
            return field.strftime('%Y-%m-%d %H')
        elif isinstance(field, date):
            return field.strftime('%Y-%m-%d')
        elif isinstance(field, Response):
            return field.__dict__ # 类的字典
        else:
            return json.JSONEncoder.default(self, field)

class Response(object):
    def __init__(self):
        self.status =True
        self.data = "asdf"
data = {
    'k1': 123,
    'k2': datetime.now(),
    'k3': Response()
}
ds = json.dumps(data, cls=JsonCustomEncoder)
print(ds)

使用request模块汇报资产数据

request模块使用介绍

利用request模块可以吧数据去采集到通过URL可以以POST或者get请求发数据给api

  • get请求是以params参数传过去的
  • 通过json参数发送数据在服务端里面可以使用request.body里面去获取数据,使用request.post/get是获取不到的
import requests
host_data = {
    'status': True,
    'data':{
        'hostname': 'c1.com',
        'disk': {'status':True,'data': 'xxx'},
        'mem': {'status':True,'data': 'xxx'},
        'nic': {'status':True,'data': 'xxx'},
    }
}

# response封装了请求发过去之后返回来的所有的值
response = requests.post(
    url='http://127.0.0.1:8000/api/asset/',
    json=host_data,
    headers={'authkey': authkey_time}   # 从请求头里边发过去
)
print(response.text)   # 它的文本信息


# requests.get(url='http://127.0.0.1:8000/api/asset/?k1=123')
# requests.get(url='http://127.0.0.1:8000/api/asset/',params={'k1':'v1','k2':'v2'})
# requests.post(
#     url='http://127.0.0.1:8000/api/asset/',
#     params={'k1':'v1','k2':'v2'}, # GET形式传值
#     data={'username':'1123','pwd': '666'}, # POST形式传值
#     headers={'a':'123'} # 请求头数据
# )
API认证

发送数据给API的时候,要注意的问题,如果随便一个人都可以往API发送数据,这样肯定不好,所以必须要做到,API只接受我们采集的服务器的资产信息,

解决这个问题的思路:
最容易想到的是把数据进行加密,然后发给后台,后台解密就行了,这种方式好不好,好,别人拿到我们的数据也不知道是什么,但是这个加密和解密都要时间,数据是很敏感的时候,可以这样做,但是我们的资产并不是那么敏感的信息,

  1. 最开始的时候,我是打算在客户端发资产信息到API的时候,在请求头里边多发一个字符串过来,然后在API里边从request.META把这个字符串取出来做验证,如果字符串不对或者没有我就不允许别人提交数据过来
  2. 但是这样有一个缺点,就是假如黑客拿到了这个请求头的字符串,那么就可以往我们这发数据了,所以我这个字符串必须一直在变,因此我客户端在请求头里面带上auth_key的时候,这个auth_key是使用一串随机字符串和当前的时间进行md5加密,就可以实现动态的auth_key,实现这个动态的auth_key之后还不行,设想一种情况,虽然我的auth_key是动态的,但是其中有一个key被破解了,它还是能登录上,因此我的API先获取一下服务端的当前时间,客户端把auth_key发过来的时候,把它那个创建auth_key的那个时间戳也发过来,假如我服务端的时间戳减去客户端的时间戳,这个时间间隔太长了,比如超过10s说明就不是我们的资产信息了,我就不把数据发过来。假如在这10s之内黑客就破解了怎么办了,
  3. 解决办法是我把这些发过的这些请求头和URL的数据放到Redis数据库里边,设置一个10s的超时时间,然后通过了时间验证后我去Redis数据库里边比对一下,如果已经有相同的数据了,说明是黑客发过来的(因为如果是我们自己发的,每次都会变的),这样就解决了这个问题,
  4. 这两个防护都通过之后,在进行Md5值加密,把客户端发过来的客户端的时间戳和那串字符串,进行MD5值加密,如果生成的字符串和客户端发过来的字符串相同,那么就认为是OK的
原创文章 85 获赞 120 访问量 4万+

猜你喜欢

转载自blog.csdn.net/happygjcd/article/details/104774519