cmdb項目-1

1.什麼是cmdbpython

  配置管理數據庫 ,存儲基礎設備的各類信息配置等linux

  CMDB能夠存儲並自動發現整個IT網絡上的各類信息,好比一個IT網絡上有多少臺服務器、多少存儲、設備的品牌、資產編號、維護人員、所屬部門、服務器上運營什麼操做系統、操做系統的版本、操做系統上有哪些應用、每一個應用的版本等等,不只如此,CMDB還有一個很是重要的功能——存儲不一樣資源之間的依賴關係,若是網絡上某個節點出現問題(好比某個服務器down了),經過CMDB,能夠判斷所以受到影響的業務shell

  CMDB由三個部分實現 : api系統(django)   +  資產採集系統   +  後臺管理系統數據庫

 

2.知識點分解django

  1)django實現api ,python普通項目實現client ,完成兩者間的通訊json

    api使用Django提供的rest_framwork模塊api

      APIView避免csrf對post的限制服務器

      request.data中存放了客戶端post請求的數據網絡

      Response方法將數據進行json轉換app

    client使用requests和json模塊

      requests.get對api發起get請求獲取到的數據可使用content拿出Byte類型

      requests.post對api發起post提交數據必須提交json編碼後的類型 

###api
##路由
urlpatterns = [
    url(r'^asset/', views.Asset.as_view()),
]
##視圖函數
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from rest_framework.response import Response

class Asset(APIView):
    def get(self, request):
        server_info = ['master1', 'master2']
        return Response(server_info)

    def post(self, request):
        print(request.data)
        return HttpResponse('200ok')
###client端
import
requests import json client_info = {'hostname': 'c1.com'} r1 = requests.get( url='http://127.0.0.1:8000/api/asset/' ) r1_data = json.loads(r1.content.decode('utf-8')) print(r1_data)    r2 = requests.post(                      ###post提交數據必定要加請求頭部標記json格式 url='http://127.0.0.1:8000/api/asset/', data=json.dumps(client_info).encode('utf-8'), headers={ 'content-type': 'application/json' } )

   2)經過字符串加載文件中的類 ,實現開放封閉必備小點

    咱們如何在配置中定義使用哪一個文件中的類呢 ,使用字典 ,以點分割 目錄.文件.類名

    如 : agent模式對應了一個字符串它對應了 /src/engine/agent.py文件中的AgentHandler類

###settings.py
ENGINE = 'agent' ENGINE_DICT = { 'agent': 'src.engine.agent.AgentHandler', 'ssh': 'src.engine.ssh.SSHHandler', }

####設計一個函數完成從文件中獲取類
import importlib
def import_string(class_string):
"""
根據配置中字符串獲取文件中的類
:param 'src.engine.agent.AgentHandler',
module, engine = src.engine.agent 和 AgentHandler
importlib.import_module()就是import src.engine.agent
engine_class = 反射獲取到AgentHandler類
"""
module, engine = class_string.rsplit('.', maxsplit=1)
module_file = importlib.import_module(module)
engine_class = getattr(module_file, engine)
return engine_class
 

 

3.CMDB資產採集系統簡單實現要點

  採集模式engine
    agent模式 ,每臺主機安裝client ,執行命令將採集的數據上報api
    中控機模式(ssh ansible) ,使用一臺機器遠程全部的主機 ,執行命令完成資產採集 ,再將數據上報api
    調用issa層的api接口直接獲取基礎設備信息

  程序設計思想

    開放封閉原則 ,代碼封閉 ,配置開放(定義當前的使用的engine與plugin),支持擴展

  程序設計關注點

    資產採集

      engine採集模式可擴展 (抽象接口)

      plugin命令插件可擴展 (抽象接口)

    惟一標識
    錯誤處理
    日誌

 

4.CMDB資產採集系統簡單實現代碼

  1)API端後續完善 ,先使用標題2-1的api

  2)新建項目 ,完善常規目錄

  3)程序入口/bin/client

    執行run()

import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from src.script import run

if __name__ == '__main__':
    run()

  4)資產採集上報入口/src/script

    定義run()

    根據配置settings實例化engine的對象 ,並執行對象的handler方法

from conf import settings
from lib.import_class import import_string    #標題2-2的根據配置字符串取類

def run():
    """資產採集入口"""
    engine_path = settings.ENGINE_DICT.get(settings.ENGINE)
    engine_class = import_string(engine_path)                     
    obj = engine_class()
    obj.handler()

  5)採集模式engine實現handler()方法

    定義基類約束BaseHandler() 要求每一個engine必須有handler()完成採集與上報, cmd()完成調用命令窗口

    定義基類簡化ssh這一類的插件SSHandSaltHandler(BaseHandler)  ,這一類的handler()方法都是從api獲取主機列表 ,遠程採集設備信息 ,再提交api ,因此會出現同時對不少的主機進行鏈接 ,這裏使用線程池

import requests
from concurrent.futures import ThreadPoolExecutor
from ..plugins import get_server_info
import json


class BaseHandler():
    """
    定義engine的基類 ,每一個engine都要有這兩個方法
    """

    def handler(self):
        raise NotImplementedError('handler() must be Implemented')

    def cmd(self, command, hostname=None):
        raise NotImplementedError('cmd() must be Implemented')


class SShandSaltHandler(BaseHandler):
    """
    簡化ssh這一類engine的代碼
    """

    def handler(self):
        # 1.獲取主機列表
        r1 = requests.get(
            url='http://127.0.0.1:8000/api/asset/',
        )
        host_list = r1.json()

        # 2.建立線程池
        pool = ThreadPoolExecutor(20)

        # 3.提交任務給線程池
        for hostname in host_list:
            pool.submit(self.task, hostname)

    def task(self, hostname):
        """線程池任務"""
        info = get_server_info(self, hostname)
        r1 = requests.post(
            url='http://127.0.0.1:8000/api/asset/',
            data=json.dumps(info).encode('gbk'),
            headers={
                'content-type': 'application/json'
            }
        )

    engine--agent模式實現handler()

      cmd()方法使用subprocess模塊完成本地命令調用

from src.engine.base import BaseHandler
from ..plugins import get_server_info
import requests
import json


class AgentHandler(BaseHandler):
    """定義cmd窗口 ,操控資產採集 + 上報"""

    def cmd(self, command, hostname=None):
        import subprocess
        return subprocess.getoutput(command)

    def handler(self):
        info = get_server_info(self)
        r1 = requests.post(
            url='http://127.0.0.1:8000/api/asset/',
            data=json.dumps(info).encode('gbk'),
            headers={
                'content-type': 'application/json'
            }
        )
View Code

    engine--ssh模式實現handler()

      cmd()方法使用paramiko模塊完成遠程命令調用 (祕鑰須要配置在settings中)

from src.engine.base import SShandSaltHandler
from conf import settings


class SSHandler(SShandSaltHandler):
    """僅定義cmd便可 ,採集與上報在父類中"""

    def cmd(self, command, hostname=None):
        import paramiko
        private_key = paramiko.RSAKey.from_private_key_file(settings.SSH_PRIVATE_KEY)
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(hostname=hostname, port=settings.SSH_PORT, username=settings.SSH_USER, pkey=private_key)
        stdin, stdout, stderr = ssh.exec_command(command)
        result = stdout.read()
        ssh.close()
        return result
View Code

  6)handler方法中的數據採集使用了get_server_info()方法

    script.py與__init__有殊途同歸之處 ! 依據配置應用不一樣插件

from conf import settings
from lib.import_class import import_string

def get_server_info(handler, hostname=None):
    """採集信息入口"""
    info = {}
    for name, path in settings.PLUGINS_DICT.items():
        """
        {'disk':'src.plugins.disk.Disk'}
        """
        plugin_class = import_string(path)
        obj = plugin_class()
        info = obj.process(handler, hostname)
    return info

  7)命令插件plugins實現process()方法

    定義基類約束BasePlugin

      增長debug屬性判斷是否爲調試模式 ,增長項目根路徑

      每一個命令插件都要分爲window與linux的具體系統判斷執行什麼命令

from conf import settings

class BasePlugin:
    def __init__(self):
        self.debug = settings.DEBUG
        self.base_dir = settings.BASE_DIR

    def get_os(self, handler, hostname=None):  ## 調試
        # os = handler.cmd('命令',hostname)
        return 'linux'

    def win(self, handler, hostname=None):
        raise NotImplementedError('win() must be Implemented')

    def linux(self, handler, hostname=None):
        raise NotImplementedError('linux() must be Implemented')

    def process(self, handler, hostname=None):
        os = self.get_os(handler, hostname)
        if os == 'win':
            ret = self.win(handler, hostname)
        else:
            ret = self.linux(handler, hostname)

        return ret

    查看硬盤的命令插件

      其中process--->調用win或者linux--->執行handler對象的cmd()方法 (這個對象最開始就被當作參數傳過來了)

      其中還會有debug判斷 ,若是是調試就直接從文件獲取數據了

      其中parse方法是將採集的數據格式化 ,須要根據api所須要的格式進行格式化

from .base import BasePlugin
import os
import re


class Disk(BasePlugin):
    def win(self, handler, hostname=None):
        ret = handler.cmd('dir', hostname)[:60]

        return 'Disk'

    def linux(self, handler, hostname=None):
        if self.debug:
            with open(os.path.join(self.base_dir, 'files', 'disk.out')) as f:
                ret = f.read()
        else:
            ret = handler.cmd('ifconfig', hostname)[:60]
        return self.parse(ret)

    def parse(self, content):
        """
        解析shell命令返回結果
        :param content: shell 命令結果
        :return:解析後的結果
        """
        response = {}
        result = []
        for row_line in content.split("\n\n\n\n"):
            result.append(row_line)
        for item in result:
            temp_dict = {}
            for row in item.split('\n'):
                if not row.strip():
                    continue
                if len(row.split(':')) != 2:
                    continue
                key, value = row.split(':')
                name = self.mega_patter_match(key)
                if name:
                    if key == 'Raw Size':
                        raw_size = re.search('(\d+\.\d+)', value.strip())
                        if raw_size:
                            temp_dict[name] = raw_size.group()
                        else:
                            raw_size = '0'
                    else:
                        temp_dict[name] = value.strip()
            if temp_dict:
                response[temp_dict['slot']] = temp_dict
        return response

    @staticmethod
    def mega_patter_match(needle):
        grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}
        for key, value in grep_pattern.items():
            if needle.startswith(key):
                return value
        return False
View Code

    查看內存的命令插件

from .base import BasePlugin
import os
from lib import convert


class Memory(BasePlugin):
    def win(self, handler, hostname=None):
        """
        windowns下執行命令
        :param handler:
        :param hostname:
        :return:
        """
        ret = handler.cmd('dir', hostname)[:60]

        return 'Disk'

    def linux(self, handler, hostname=None):
        if self.debug:
            with open(os.path.join(self.base_dir, 'files', 'memory.out')) as f:
                ret = f.read()
        else:
            ret = handler.cmd('lsblk', hostname)[:60]

        return ret

    def parse(self, content):
        """
        解析shell命令返回結果
        :param content: shell 命令結果
        :return:解析後的結果
        """
        ram_dict = {}
        key_map = {
            'Size': 'capacity',
            'Locator': 'slot',
            'Type': 'model',
            'Speed': 'speed',
            'Manufacturer': 'manufacturer',
            'Serial Number': 'sn',

        }
        devices = content.split('Memory Device')
        for item in devices:
            item = item.strip()
            if not item:
                continue
            if item.startswith('#'):
                continue
            segment = {}
            lines = item.split('\n\t')
            for line in lines:
                if len(line.split(':')) > 1:
                    key, value = line.split(':')
                else:
                    key = line.split(':')[0]
                    value = ""
                if key in key_map:
                    if key == 'Size':
                        segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0)
                    else:
                        segment[key_map[key.strip()]] = value.strip()
            ram_dict[segment['slot']] = segment

        return ram_dict
View Code

 

5.簡單一些想法完成開放封閉

  首先屬於同類功能 ,這類的功能是須要不停增長的 ,或者說能夠選用的都經過配置實現 ,使用抽象類約束

    配置 :指定操做者  ,操做者使用的工具

    生成操做者(script)

    handler操做者: 能夠操做任意工具

    生成操做者使用的工具(__init__)  

    plugin工具: 網卡查看工具  硬盤查看工具查看

相關文章
相關標籤/搜索