zoukankan      html  css  js  c++  java
  • (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 代码

     略

  • 相关阅读:
    【noi 2.5_1789】算24(dfs)
    【bzoj 4455】小星星(树型DP+容斥原理+dfs建树和计算的2种方式)
    【bzoj 1190】梦幻岛宝珠(DP)
    【bzoj 3333】排队计划(线段树)
    【noi 2.5_7834】分成互质组(dfs)
    【noi 2.5_1792】迷宫(bfs 或 dfs)
    【noi 2.2_7891】一元三次方程求解(二分枚举+输出程序运行时间)
    JFrame包含的容器(JRootPane)
    JFrame背景色设置
    java自动装箱的一个例子
  • 原文地址:https://www.cnblogs.com/wenyanjie/p/7262856.html
Copyright © 2011-2022 走看看