(1)CMDB项目1

 1 CMDB概念

  CMDB :  Configuraion Management Database配置管理数据库,CMDB存储与管理企业IT架构中设备的各种配置信息,它与所有服务支持和服务交付流程都紧密相连,支持这些流程的运转、发挥配置信息的价值,同时依赖于相关流程保证数据的准确性。

  在实际项目中,CMDB常常被认为是构建其他ITIL流程的基础而有限考虑,ITIL项目的成败与是否成功建立CMDB有非常大的关系。70%~80%的IT相关问题与环境的变更有着更直接的关系。实施变更的难点和重点并不是工具,而是流程。即通过一个自动化的,可重复的流程管理变更,使得当变更发生的时候,有一个标准化的流程去执行,能够预测到这个变更对整个系统管理产生的影响,并对这些影响进行评估和控制。而变更管理流程自动化的实现关键就是CMDB。

  CMDB工具中至少包括这几种关键的功能:整合、调和、同步、映射和可视化。

  整合:是指能够充分利用来自其他数据源的信息,对CMDB中包含的记录源属性进行存取,将多个数据源合并至一个视图中,生成连同来自CMDB和其他数据源信息在内的报告。

  调和:调和能力是指通过对来字每个数据源的匹配字段进行对比,保证CMDB中的记录在多个数据源中没有重复现象,维持CMDB中每个配置项目数据源的完整性;自动调整流程使得初始实施、数据库管理员的手动运作和现场维护支持工作将至最低。

  同步:是指确保CMDB中的信息能够反映联合数据源的更新情况,在联合数据源更新频率的基础上确定CMDB更新日程,按照经过批准的变更来更新CMDB,找出未被批准的变更。

  应用映射与可视化:说明应用间的关系并反应应用和其他组件之间的依存关系,了解变更造成的影响并帮助诊断问题。

  CMDB是运维自动化项目,它可以减少人工干预,降低人员成本。

  功能:自动装机、实时监控、自动化部署软件,建立在他们的基础上是资产信息变更记录(资产管控自动进行汇报)

2  资产采集

2.1 资产采集分析  

   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()
需要复习知识点

2.2 资产采集的四种方案

2.2.1 Agent方式

  使用场景:主机数量多

       使用架构: 数据库------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 返回值

 2.2.2  SSH方式

使用场景:主机数量少,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()

2.2.3 saltstack方式 

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主机

2.3  创建资产采集代码的目录

bin    # 可执行文件
config  # 配置文件
      -settings.py
lib     #  公共的模块
   - conf 
         -config.py 
         - global_setting.py
src     # 业务代码目录
 #  log     # 日志  一般不放这里,写在系统的某个目录

 

2.4 资产采集代码中的配置文件

有两个配置文件:用户自定义配置文件 , 默认配置文件。   配置文件中的变量名一般都是大写。

使用一个配置文件将两个配置文件结合起来

此例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)
结合后配置文件使用

一般软件的配置文件都是这样做,有两个配置文件,没有自定义就是用默认配置文件。 

2.5 可插拨式资产采集插件

公司采集资产有差别,因此插件最好是可插拨式

在src目录下创建plugins文件夹下basic,board,cpu,disk,menery,nic的py文件,默认采集basic,board,cpu,disk,menery,nic信息

2.5.1 定义插件类

# 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'
#.....
定义插件类

2.5.2  插件类写到配置文件中

#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",
}
插件类信息写到配置文件中

2.5.3 可插拨式采集资产插件的使用方法

 当导入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函数需要判断配置文件中的采集数据方式,根据不同的方式执行命令

2.5.4 插件部分代码运行逻辑

请求会先执行src/plugins/__init__.py中所有代码
首先PluginManager的__init__函数,在配置文件中注册的所有插件拿出,
PluginManager的exec_plugin函数执行各个插件下的process方法,参数为command函数,接收各个process函数返回值,并返回最终值
各个插件的process方法会拿各个插件自己的命令执行command函数,接收command函数的返回值,并返回
command根据采集数据的模式不同执行命令,并返回值

发给api的信息,最好的类型时字典:
{
cpu: 'xxx',
disk:'...'
}

2.5.5 资产查询插件中的钩子

如果你想在构造方法(就是__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'
插件中的钩子

2.5.6  插件对获取的数据进行处理

 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('

'):
            for row_line in item.split('
'):
                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
对获取数据进行整理

2.5.7 插件获取到错误堆栈信息时的处理

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'}
    }

    '''
trackback处理错误信息

2.5.8 向api发送数据

支持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)
向api发送数据

插件只是用来采集资产,并且自己判断是哪种模式
client.py 是组织插件的功能和拿到数据发送给api的业务

问题:

发送到api时还可以加密
还有并发的问题,使用线程池

2.5.9  唯一标识

创造唯一标识时,之前必须要做标准化 。 比如认为主板SN  物理机的话可以作为唯一标识,虚拟机的话就不是唯一标识了。

标准化:主机名不重复;对于agent方式,主机名是唯一标识,可以将主机名写在客户端服务器的某个文件中,采集数据时可以进行主机名比较,如果不一致就拿文件中的主机名。

标准化:

  - 主机名不重复

  - 流程:

  - 资产录入,机房,机柜,机柜位置

  - 装机时,需要将服务信息录入CMDBc1.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)
agent方式中唯一标识代码

 SSHSalt方式:中控机从后台获取未采集主机名列表:【c1.com 】,再直接去找客户端,本身拿到的主机名就是唯一标识

2.5.10 基于线程池实现并发采集资产

Sshsalt的采集资产方式才可以有线程池。  提高并发:线程、进程

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)
基于线程池采集数据

2.5.11 代码

 略

原文地址:https://www.cnblogs.com/wenyanjie/p/7262856.html