CMDB : Configuraion Management Database配置管理数据库,CMDB存储与管理企业IT架构中设备的各类配置信息,它与全部服务支持和服务交付流程都紧密相连,支持这些流程的运转、发挥配置信息的价值,同时依赖于相关流程保证数据的准确性。html
在实际项目中,CMDB经常被认为是构建其余ITIL流程的基础而有限考虑,ITIL项目的成败与是否成功创建CMDB有很是大的关系。70%~80%的IT相关问题与环境的变动有着更直接的关系。实施变动的难点和重点并非工具,而是流程。即经过一个自动化的,可重复的流程管理变动,使得当变动发生的时候,有一个标准化的流程去执行,可以预测到这个变动对整个系统管理产生的影响,并对这些影响进行评估和控制。而变动管理流程自动化的实现关键就是CMDB。python
CMDB工具中至少包括这几种关键的功能:整合、调和、同步、映射和可视化。正则表达式
整合:是指可以充分利用来自其余数据源的信息,对CMDB中包含的记录源属性进行存取,将多个数据源合并至一个视图中,生成连同来自CMDB和其余数据源信息在内的报告。shell
调和:调和能力是指经过对来字每一个数据源的匹配字段进行对比,保证CMDB中的记录在多个数据源中没有重复现象,维持CMDB中每一个配置项目数据源的完整性;自动调整流程使得初始实施、数据库管理员的手动运做和现场维护支持工做将至最低。数据库
同步:是指确保CMDB中的信息可以反映联合数据源的更新状况,在联合数据源更新频率的基础上肯定CMDB更新日程,按照通过批准的变动来更新CMDB,找出未被批准的变动。django
应用映射与可视化:说明应用间的关系并反应应用和其余组件之间的依存关系,了解变动形成的影响并帮助诊断问题。json
CMDB是运维自动化项目,它能够减小人工干预,下降人员成本。api
功能:自动装机、实时监控、自动化部署软件,创建在他们的基础上是资产信息变动记录(资产管控自动进行汇报)服务器
CMDB资产管理项目的代码能够分为:资产采集部分,api部分,后台管理部分。架构
资产采集部分:
--- 采集资产:在各个服务器(在CMDB中是客户端)执行采集数据信息命令,使用正则或字符串匹配方式获取想要数据。
若是此服务器是中控机,须要有执行命令的各个主机名,和执行的命令。
若是服务器为agent,则只须要执行命令。
--- 兼容性(各类资产采集的架构:agent,SSH或者salt均可以兼容)
--- 汇报数据
须要复习知识点:
1 1 高内聚,低耦合 2 2 反射: getattr(obj,'xxx') 3 3 导入模块: 4 import re 5 import importlib 6 importlib.import_module('re') 7 django中是如何导入模块的: 8 m = importlib.import_module('django.middleware.clickjacking') 9 cls = getattr(m,'XFrameOptionsMiddleware') 10 cls() 11 4 面向对象: 12 (1) 13 class Foo: 14 def __init__(self,xx): 15 pass 16 @classmethod 17 def instance(cls): 18 return cls() 19 def process(self): 20 pass 21 if hasattr(Foo,'instance'): 22 obj=Foo.instance() 23 else: 24 obj=Foo() 25 obj.process() 26 (2) 27 class A: 28 def f1(self): 29 self.f2() 30 def f2(self): 31 self('A.f2') 32 class B: 33 def f2(self): 34 print('B.f2') 35 obj = B() 36 obj.f1()
使用场景:主机数量多
使用架构: 数据库------api程序----------各个主机
使用:
agent自动采集是在各个主机上运行程序,采集数据。须要使用subprocess和requests模块
1 v1= subprocess.getoutput('ifconfig') 2 #并发送 3 url="http://127.0.0.1:8000/asset.html" 4 response = request.post(url,data={'k1':value1,'k2':value2}) 5 print(response.text)
当agent采集数据发送到api程序时,api主要的任务是:1 url写路由 2 接收自动采集的数据的格式 3 返回值
使用场景:主机数量少,ssh链接即便慢也没事
使用架构:数据库 ----api程序----中控机----各个主机
中控机须要远程获取各个主机的信息:python中ssh登陆各个主机,执行命令,获得数据
第三方的批量软件: fabric ansible 也是使用ssh原理
须要使用Paramiko模块:
1 import paramiko 2 # 建立SSH对象 3 ssh = paramiko.SSHClient() 4 # 容许链接不在know_hosts文件中的主机 5 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 6 # 链接服务器 7 ssh.connect(hostname='192.168.11.98',port=22,username='wupeiqi',password='123') 8 #执行命令 9 stdin,stdout,stderr=ssh.exec_command('ls') 10 #获取命令结果 11 result= stdout.read() 12 #关闭链接 13 ssh.close()
saltstack: master主服务器 salve客户端
数据库 ----api程序----saltstack服务的master端----各个slave主机
v = subprocess.getoutput(salt "*" cmd.run "ls")
根据正则表达式提交到数据库
saltstack应用
1 安装和配置
1 """ salt服务器安装和配置 2 1. 安装salt-master 3 yum install salt-master 4 2. 修改配置文件:/etc/salt/master 5 interface: 0.0.0.0 # 表示Master的IP 6 3. 启动 7 service salt-master start 8 9 """
1 """ 2 salt客户端安装和配置 3 1. 安装salt-minion 4 yum install salt-minion 5 6 2. 修改配置文件 /etc/salt/minion 7 master: 10.211.55.4 # master的地址 8 或 9 master: 10 - 10.211.55.4 11 - 10.211.55.5 12 random_master: True 13 14 id: c2.salt.com # 客户端在salt-master中显示的惟一ID 15 3. 启动 16 service salt-minion start 17 """
2 受权
1 """ 2 salt-key -L # 查看已受权和未受权的slave 3 salt-key -a salve_id # 接受指定id的salve 4 salt-key -r salve_id # 拒绝指定id的salve 5 salt-key -d salve_id # 删除指定id的salve 6 """
3 执行命令
在master服务器上对salve进行远程操做
(1)使用salt本身命令
1 salt 'c2.salt.com' cmd.run 'ifconfig'
(2)使用salt.client模块
1 import salt.client 2 local = salt.client.LocalClient() 3 result = local.cmd('c2.salt.com', 'cmd.run', ['ifconfig'])
2.2.4 puppet方式
使用场景:公司如今在使用puppet
使用架构:数据库 ----api程序----puppet服务的master端----各个slave主机
bin # 可执行文件 config # 配置文件 -settings.py lib # 公共的模块 - conf -config.py - global_setting.py src # 业务代码目录 # log # 日志 通常不放这里,写在系统的某个目录
有两个配置文件:用户自定义配置文件 , 默认配置文件。 配置文件中的变量名通常都是大写。
使用一个配置文件将两个配置文件结合起来
此例django的默认配置文件: from django.conf import global_settings
此例django中from django.conf import settings 会把默认配置和自定义配置合起来,使用settings.使用全部的配置
lib/conf/config.py 配置文件代码:
1 import os 2 import importlib 3 from . import global_settings 4 class Settings(object): 5 def __init__(self): 6 # ######## 找到默认配置 ######## 先找默认,后找自定义配置 7 for name in dir(global_settings): 8 if name.isupper(): 9 value = getattr(global_settings,name) 10 setattr(self,name,value) 11 12 # ######## 找到自定义配置 ######## 13 # os.environ是全局环境变量,是字典类型 14 # 根据字符串导入模块 15 settings_module = os.environ.get('USER_SETTINGS') 16 if not settings_module: #若是没有自定义配置文件,直接使用默认文件 17 return 18 m = importlib.import_module(settings_module) 19 for name in dir(m): # dir() 能够获得此变量的全部属性 20 if name.isupper(): # 配置文件中的变量名通常大写 21 value = getattr(m,name) # 拿到自定义配置 22 setattr(self,name,value) # 将自定义配置的键和值设置在当前配置文件中 23 settings = Settings()
使用
1 import os 2 os.environ['USER_SETTINGS'] = "config.settings" #os.environ 系统环境变量 ,只在当前运行程序生效 3 import sys # 将代码模块路径放到sys中 4 BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 sys.path.append(BASEDIR) 6 from lib.conf.config import settings #只须要导入settings,默认和自定义配置文件都会导入 7 print(settings.USER) 8 print(settings.EMAIL)
通常软件的配置文件都是这样作,有两个配置文件,没有自定义就是用默认配置文件。
公司采集资产有差异,所以插件最好是可插拨式
在src目录下建立plugins文件夹下basic,board,cpu,disk,menery,nic的py文件,默认采集basic,board,cpu,disk,menery,nic信息
# src/plugins/nic.py中的Nic类 class Nic(object): def process(self): return 'Nic' # src/plugins/board.py中的Board类 class Board(object): def process(self): return 'Board' #.....
#condif/settings.py自定义配置文件写入 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", }
当导入src/plugins里面模块时时,首先会执行src/plugins/__init__.py,src/plugins/__init__.py导入配置文件,获取插件的目录和类名,并导入。根据类名,执行其方法采集资产。
不一样架构执行命令: 例如salt和agent方式不一样,在代码上涉及判断
(1)-- 插件上使用继承解决
继承的类里面command函数须要判断配置文件中的采集数据方式,根据不一样的方式执行命令
# src/plugins/base.py 在command方法中写判断采集数据类型 class BasePlugin(object): def command(self,cmd): if "salt": pass elif "SSH": pass # .... # src/plugins/cpu.py 各个插件继承BasePlugin类,使用其command方法 from .base import BasePlugin class Cpu(BasePlugin): def process(self): self.command('xxx') return "123321123"
(2)-- 插件上多传一个命令参数
在src/plugins/__init__.py中定义command函数,并将command函数在执行插件时做为参数传过去
插拔式插件须要有command函数的形参,不用继承,还可使用__init__.py的全部属性
command函数须要判断配置文件中的采集数据方式,根据不一样的方式执行命令
请求会先执行src/plugins/__init__.py中全部代码
首先PluginManager的__init__函数,在配置文件中注册的全部插件拿出,
PluginManager的exec_plugin函数执行各个插件下的process方法,参数为command函数,接收各个process函数返回值,并返回最终值
各个插件的process方法会拿各个插件本身的命令执行command函数,接收command函数的返回值,并返回
command根据采集数据的模式不一样执行命令,并返回值
发给api的信息,最好的类型时字典:
{
cpu: 'xxx',
disk:'...'
}
若是你想在构造方法(就是__init__)建立的时候想让插件模块自定义一些操做,可使用@classmethod和initial函数来作。
# src/plugins/__init__.py中PluginManager类的exec_plugin函数 def exec_plugins(self): # 获取全部的插件,并执行插件并返回值 response = {} for k,v in self.plugin_dict.items(): # 'basic': "src.plugins.basic.Basic" # result= "根据v获取类,并执行其方法采集资产" module_path,class_name=v.rsplit('.',1) m = importlib.import_module(module_path) cls = getattr(m,class_name) # 找到类 if hasattr(cls,'initial'): obj = cls.initial() else: obj = cls() result = obj.process(self.command) response[k]= result return response
# src/plugins/cpu.py中Cpu类的initial函数 class Cpu(object): def __init__(self): pass @classmethod def initial(cls): return cls() def process(self,command_func): return 'Cpu'
src/plugins/cpu.py下的类Cpu中parse方法
def parse(self, content): """ 解析shell命令返回结果 :param content: shell 命令结果 :return:解析后的结果 """ response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''} cpu_physical_set = set() content = content.strip() for item in content.split('\n\n'): for row_line in item.split('\n'): key, value = row_line.split(':') key = key.strip() if key == 'processor': response['cpu_count'] += 1 elif key == 'physical id': cpu_physical_set.add(value) elif key == 'model name': if not response['cpu_model']: response['cpu_model'] = value response['cpu_physical_count'] = len(cpu_physical_set) return response
trackback模块
traceback.format_exc() 错误信息堆栈
def exec_plugin(self): """ 获取全部的插件,并执行获取插件返回值 :return: """ response = {} for k,v in self.plugin_dict.items(): # 'basic': "src.plugins.basic.Basic", ret = {'status':True,'data':None} try: module_path, class_name = v.rsplit('.', 1) m = importlib.import_module(module_path) cls = getattr(m,class_name) if hasattr(cls,'initial'): obj = cls.initial() else: obj = cls() result = obj.process(self.command,self.debug) # result = "根据v获取类,并执行其方法采集资产" 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()) # traceback.format_exc() 错误信息堆栈 response[k] = ret return response ''' { 'disk':{'status':True,'data':'xxx'}, 'nic':{'status':False,'data':'xxxxxx'} } '''
支持3种模式:
agent直接发送就行
中控机向后台发送,ssh或者salt应该先从后台获取未采集数据的主机列表,再循环主机列表从而获取各个客户端的主机信息,最后将信息数据返回给后台服务器。
#向api发送数据 和采集资产整合起来 import requests from lib.conf.config import settings from src.plugins import PluginManager import json class Base(object): def post_asse(self,server_info): requests.post(settings.API,json=server_info) # server_info 是字典里面有字典,不能直接传过去,须要json转化 # body:json.dumps(server_info) # headers = {'content-type':'application/json'} # json.loads(request.body) class Agent(Base): def execute(self): server_info = PluginManager().exec_plugin() self.post_asse(server_info) class SSHSALT(Base): def get_host(self): # 获取未采集的主机列表 response=requests.get(settings.API) result = json.loads(requests.text) # “{status:'True',data:['c1.com','c2.com']}” if result['status']: return return result['data'] def execute(self): host_list = self.get_host() for host in host_list: server_info = PluginManager(host).exec_plugins() self.post_asse(server_info)
插件只是用来采集资产,而且本身判断是哪一种模式
client.py 是组织插件的功能和拿到数据发送给api的业务
问题:
发送到api时还能够加密
还有并发的问题,使用线程池
创造惟一标识时,以前必需要作标准化 。 好比认为主板SN 物理机的话能够做为惟一标识,虚拟机的话就不是惟一标识了。
标准化:主机名不重复;对于agent方式,主机名是惟一标识,能够将主机名写在客户端服务器的某个文件中,采集数据时能够进行主机名比较,若是不一致就拿文件中的主机名。
标准化:
- 主机名不重复
- 流程:
- 资产录入,机房,机柜,机柜位置
- 装机时,须要将服务信息录入CMDB,c1.com (手动录入或者cobber装机软件录入)
- 资产采集:c1.com
标准化创造惟一标识步骤:
(1)装系统,初始化软件(CMDB),运行CMDB:
- 经过命令获取主机名
- 写入本地指定文件
(2) 将资产信息发送到API
(3) 获取资产信息:
- 本地文件主机名 != 命令获取的主机名(按照文件中的主机名)
- 本地文件主机名 == 命令获取的主机名
最终流程:
标准化:主机名不重复;流程标准化(装机同时,主机名在cmdb中设置)
服务器资产采集(Agent):
a. 第一次:文件不存在,或内容为空;
采集资产:
- 主机名写入文件
- 发送API
b. 第N次:采集资产,主机名:文件中获取
agent方式惟一标识代码:
class Agent(Base): def execute(self): server_info = PluginManager().exec_plugin() hostname = server_info['basic']['data']['hostname'] certname = open(settings.CERT_PATH,'r',encoding='utf-8').read() if not certname.strip(): with open(settings.CERT_PATH,'w',encoding='utf-8') as f: f.write(hostname) else: server_info['basic']['data']['hostname'] = certname self.post_asse(server_info)
SSH或Salt方式:中控机从后台获取未采集主机名列表:【c1.com 】,再直接去找客户端,自己拿到的主机名就是惟一标识
Ssh和salt的采集资产方式才能够有线程池。 提升并发:线程、进程
Python2:
线程池:无
进程池:有
Python3:
线程池:有
进程池:有
线程池简单例子:
import time from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def task(i): time.sleep(1) print(i) p = ThreadPoolExecutor(10) for row in range(100): p.submit(task,row)
进程池简单例子:
import time from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def task(i): time.sleep(1) print(i) p = ProcessPoolExecutor(10) for row in range(100): p.submit(task,row)
线程池在CMDB中采集数据时的应用:
# src/client.py class SSHSALT(Base): def get_host(self): # 获取未采集的主机列表 response=requests.get(settings.API) result = json.loads(requests.text) # “{status:'True',data:['c1.com','c2.com']}” if result['status']: return return result['data'] def run(self,host): server_info = PluginManager(host).exec_plugins() self.post_asse(server_info) def execute(self): host_list = self.get_host() from concurrent.futures import ThreadPoolExecutor pool = ThreadPoolExecutor(10) for host in host_list: pool.submit(self.run,host)
略