CMDB开发
本节内容:
1:CMDB是一个什么项目
2:运维自动化的分类
3:CMDB资产采集的4种方式
4:程序可插拔机制
5:对发送数据的中控机或者agent进行一个api的验证
6:数据库的实现
一:CMDB是一个什么项目
CMDB:[运维自动化的一个项目] -运维自动化基础 -主要就是资产管理
运维自动化项目目的:减少人工干预,降低人员成本。
二:运维自动化的分类
运维自动化工具大概四类; 安装批量系统: linux kgstart、i z卡 装环境: 初始化环境,也有一个工具 批量打包项目部署:ssh/crt/种子(如迅雷种子) 监控服务的实时状态: 以上的四类都要依赖CMDB: 你安装系统,是不是要指定往哪一台机器安装系统? 装环境 一样需要执行往哪一台? 批量?哪几台 监控?监控谁?
常见的运维项目就是如下了:
我们要实现的CMDB:主要就是针对资产管理的方面。
三:CMDB资产采集的4种方式
要做资产管理 那么就必须先获取资产的信息。
自动获取。
方式1:Agent方式
agent方式:要求在每个的服务器上安装agent程序,
在agent中链接上API,然后再有API去向数据库添加数据。
API:也是一个django的程序
agent程序:可以通过subpeocess 模块执行命令,再采用requests模块get或post发送数据。
API:获取数据,执行添加数据(比如网卡等等信息)
方式2:SSH的方式
SHH方式:
服务端没有了agent程序,而是在服务器和API的中间加了一个中控服务端[用于采集服务端信息]
中控服务端通过paramiko模块主动去链接服务器,然后通过subprocess模块执行命令,返回命令结果;再发送到API:
去链接服务器可能我们会使用别的模块:如ansible 或 fabic 模块,但是他们都是对paramiko模块的封装。
注意:最新的ansible不再使用paramiko模块封装了,而是自己实现了新的方式。
方式3:saltstack方式
saltstack方式: 基于第三方软件(去python写的)去实现,在中控服务端安装一个saltstack-master;服务器安装saltstack-slave。 执行过程: saltstack-master指定主机发送命令("c1.com","ifconfig"),将命令发送到队列(RPC)中去; 服务器取命令,执行完又将结果发送到一个新的队列中去; saltstack-master获取数据,向API发送数据。
方式4:puppet方式
puppet方式: 这是相对比较古老的一种方式,上面的几种都是由中控服务端去主动获取数据; 而puppet方式是由puppet-master和puppet-salve组成;puppet-salve每隔30分钟发送数据给puppet-master; puppet-master内维护了一个报表:获取这个报表发送数据给API puppet:是用ruby写的。所以我们在通过报表采集资产的时候,需要使用ruby语言。
4:程序可插拔机制
关于程序的可插拔机制。
开发一套程序时,要使其有充分的扩展性。接下来可以写一些伪代码...
假设项目名为AutoClient, 目录结构如下:
AutoClient/
|-- bin/
| |-- auto_client.py
|-- config/
| |-- settings.py
|-- lib/
| |--
|-- log/
| |-- error.log
| |-- run.log
|-- src/
| |-- plugins/
| |-- __init__.py
| |-- base.py
| |-- cpu.py
| |-- disk.py
| |-- ...
| |-- package.py
| |-- scripts.py
|-- README
例如,采集资产信息有上述描述的三种形式(agent,ssh,salt,),而将要做的一件事就是要让程序兼容这三种形式
在配置文件中写一点东西、配置文件就先写这些(settings.py)
# 采集资产的方式,选项有:agent(默认), salt, ssh MODE = 'agent' # 采集硬件数据的插件 PLUGINS_DICT = { 'cpu': 'src.plugins.cpu.CpuPlugin', 'disk': 'src.plugins.disk.DiskPlugin', 'main_board': 'src.plugins.main_board.MainBoardPlugin', 'memory': 'src.plugins.memory.MemoryPlugin', 'nic': 'src.plugins.nic.NicPlugin', }
继而写一些公用的(base.py)
# 导入settings文件 from AutoCMDB.conf import settings class BasePlugin(object): def __init__(self): """ 验证配置文件 """ mode_list = ['agent', 'ssh', 'salt'] if settings.MODE in mode_list: self.mode = settings.MODE else: raise Exception('配置文件发生错误') def ssh(self, cmd): pass def agent(self, cmd): pass def salt(self, cmd): pass def shell_cmd(self, cmd): """ 执行判断模式 :param cmd: :return: """ if self.mode == 'agent': result = self.agent(cmd) elif self.mode == 'ssh': result = self.ssh(cmd) else: result = self.salt(cmd) return result def execute(self): """ 执行判断平台 :return: 目标 拿到资产采集信息 """ result = self.shell_cmd('查看平台命令') if result == 'win': return self.window() elif result == 'linux': return self.linux() else: raise Exception('只支持windows') def window(self): raise Exception('...') def linux(self): raise Exception('...') class DiskPlugin(BasePlugin): def window(self): # 正则表达式匹配想要的信息 return '硬盘' def linux(self): # 正则表达式匹配想要的信息 #result = shell_cmd("命令") ##直接调用shell_cmd函数去执行命令 return '硬盘' obj = DiskPlugin() result = obj.execute()
上述代码看似很多,但是这样写有什么好处或者说有什么用意 这样写程序的可插拔性就很强了,例如采集硬盘信息只需在DiskPlugin的window或者linux写代码,此时不用再去判断什么模式,只需要将命令传输即可 其次,做内存、CPU、网卡信息按照硬盘那样去写。而后将各个单独存入相应的文件,这更使程序简洁明了。 接下来呢,想要同时获取CPU、内存、网卡的资产信息 那么配置文件的插件PLUGINS_DICT可以派上用场了。依托于它,可根据反射去做(package.py)
# 导入settings from AutoCMDB.conf import settings # 动态导入模块 import importlib response = {} for k, v in settings.PLUGINS_DICT.items(): moudle_path, cls_name = v.rsplit('.', 1) if hasattr(importlib.import_module(moudle_path), cls_name): func = getattr(importlib.import_module(moudle_path), cls_name) response[k] = func().execute() # 这样response便封装了所有资产信息
5:对发送数据的中控机或者agent进行一个api的验证
为什么需要进行验证api?因为我不可能说随便一个人都给可以发资产的信息,让我更新把?
方式1:通过auth-key进行验证
服务端和发送端各保存一组用于验证的字符串,当post请求来的时候进行比较两个字符串,一样则认证通过。
############发送请求的agent或中控机服务######## import requests auth_key = "dadsdasdad21" data_cic = {"nic":{"status":True,"error":"222"}} result = requests.post( url=" http://127.0.0.1:8090/api/asset/", json=data_cic, headers={"auth-key":auth_key} ##请求头的key不能是下划线! ) print(result.text) ##授权失败 ###########进行认证的API################# from django.shortcuts import render,HttpResponse from django.views.decorators.csrf import csrf_exempt,csrf_protect import json auth_key = "dadsdasdad" @csrf_exempt ##装饰器,就代表这个函数不再使用csrf进行验证 def asset(request): if request.method == "POST": custom_auth_key = request.META.get("HTTP_AUTH_KEY") ##请求头的信息在META中,以HTTP_开头,加上发送过来的小写 if custom_auth_key != auth_key: return HttpResponse("授权失败") ##认证通过获取请求体的内容 data = json.loads(str(request.body,encoding="utf8")) print(data) return HttpResponse("...")
方式2:进行对auth_key的MD5加密
############发送请求的agent或中控机服务######## import requests import hashlib auth_key = "dadsdasdad12" ###auth_key data_cic = {"nic":{"status":True,"error":"222"}} md5 = hashlib.md5() md5.update(auth_key.encode("utf-8")) ##要字节码,在python2没有这个限制 md5_result = md5.hexdigest() ##加密的auth_key result = requests.post( url=" http://127.0.0.1:8090/api/asset/", json=data_cic, headers={"auth-key":md5_result} ) print(result.text) ##授权失败 ###########进行认证的API################# from django.shortcuts import render,HttpResponse from django.views.decorators.csrf import csrf_exempt,csrf_protect import json import hashlib auth_key = "dadsdasdad" md5 = hashlib.md5() md5.update(auth_key.encode("utf-8")) md5_result = md5.hexdigest() @csrf_exempt ##装饰器,就代表这个函数不再使用csrf进行验证 def asset(request): if request.method == "POST": custom_auth_key = request.META.get("HTTP_AUTH_KEY") if custom_auth_key != md5_result: return HttpResponse("授权失败") ##认证通过获取请求体的内容 data = json.loads(str(request.body,encoding="utf8")) print(data) return HttpResponse("...")
方式3:进行对auth_key的MD5加密和加上动态时间
发送requests客户端:
############发送请求的agent或中控机服务######## import requests import hashlib import time auth_key = "dadsdasdad1" current_time = str(time.time()) auth_result= "%s|%s"%(auth_key,current_time) md5 = hashlib.md5() md5.update(auth_result.encode("utf-8")) ##要字节码,在python2没有这个限制 md5_result = md5.hexdigest() ##这是因为服务器和时候和发送的时候肯定是不一样的,所以我们需要携带上客户端的时间 ##认证字符串结果= MD5(认证字符+当前时间)|当前时间 md5_result = "%s|%s"%(md5_result,current_time) print(md5_result) ##3b2ef845fcbd32a8609db0cb33eecb6c|1526024865.6403847 data_cic = {"nic":{"status":True,"error":"222"}} result = requests.post( url=" http://127.0.0.1:8090/api/asset/", json=data_cic, ##放在请求体中 headers={"auth-key":md5_result} ##放在请求头中 ) print(result.text) ##授权失败
API服务端:
###########进行认证的API################# from django.shortcuts import render,HttpResponse from django.views.decorators.csrf import csrf_exempt,csrf_protect import json import hashlib import time auth_key = "dadsdasdad" md5 = hashlib.md5() ##这是第三层的验证 auth_list = [] @csrf_exempt ##装饰器,就代表这个函数不再使用csrf进行验证 def asset(request): if request.method == "POST": ##获取客户端开来的验证字符串 和客户端时间 ##81cb319de0d593d81dd7b06a4ae9d091 1526025063.857722 custom_auth_key,custom_time = request.META.get("HTTP_AUTH_KEY").split("|") print("custom_auth_key:",custom_auth_key,custom_time) ##进行一个时间的判断,假如客户端发来时间超过了5秒钟,就认为是hack窃取到,url并发送过来 ##进行第一个api的验证 if time.time() - float(custom_time) > 5: print(1111) return HttpResponse("授权失败") ##服务端的auth_key加上custom的时间判断两个的验证字符串是不是一样 ##进行第二个api的验证 auth_keys="%s|%s"%(auth_key,custom_time) md5.update(auth_keys.encode("utf-8")) md5_result = md5.hexdigest() print("md5_result:",md5_result) if custom_auth_key != md5_result: return HttpResponse("授权失败") ##进行第三次的api验证,如果已经存在了客户端验证信息,就说明这次是无效的! if request.META.get("HTTP_AUTH_KEY") in auth_list: print(3333) return HttpResponse("授权失败") ##认证通过获取请求体的内容 data = json.loads(str(request.body,encoding="utf8")) print(data) ##如果上面三个通过了就,进行添加进已认证的客户端列表中 auth_list.append(request.META.get("HTTP_AUTH_KEY")) return HttpResponse("...")
6:数据库的实现