文章目录
一:资产采集功能开发
①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只接受我们采集的服务器的资产信息,
解决这个问题的思路:
最容易想到的是把数据进行加密,然后发给后台,后台解密就行了,这种方式好不好,好,别人拿到我们的数据也不知道是什么,但是这个加密和解密都要时间,数据是很敏感的时候,可以这样做,但是我们的资产并不是那么敏感的信息,
- 最开始的时候,我是打算在客户端发资产信息到API的时候,在请求头里边多发一个字符串过来,然后在API里边从
request.META
把这个字符串取出来做验证,如果字符串不对或者没有我就不允许别人提交数据过来 - 但是这样有一个缺点,就是假如黑客拿到了这个请求头的字符串,那么就可以往我们这发数据了,所以我这个字符串必须一直在变,因此我客户端在请求头里面带上
auth_key
的时候,这个auth_key
是使用一串随机字符串和当前的时间进行md5
加密,就可以实现动态的auth_key
,实现这个动态的auth_key
之后还不行,设想一种情况,虽然我的auth_key
是动态的,但是其中有一个key被破解了,它还是能登录上,因此我的API先获取一下服务端的当前时间,客户端把auth_key
发过来的时候,把它那个创建auth_key
的那个时间戳也发过来,假如我服务端的时间戳减去客户端的时间戳,这个时间间隔太长了,比如超过10s说明就不是我们的资产信息了,我就不把数据发过来。假如在这10s之内黑客就破解了怎么办了, - 解决办法是我把这些发过的这些请求头和URL的数据放到Redis数据库里边,设置一个10s的超时时间,然后通过了时间验证后我去Redis数据库里边比对一下,如果已经有相同的数据了,说明是黑客发过来的(因为如果是我们自己发的,每次都会变的),这样就解决了这个问题,
- 这两个防护都通过之后,在进行Md5值加密,把客户端发过来的客户端的时间戳和那串字符串,进行MD5值加密,如果生成的字符串和客户端发过来的字符串相同,那么就认为是OK的