(1)CMDB項目1

 1 CMDB概念

  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

  功能:自動裝機、實時監控、自動化部署軟件,創建在他們的基礎上是資產信息變動記錄(資產管控自動進行彙報)服務器

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('\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
對獲取數據進行整理

 

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 代碼

 略

相關文章
相關標籤/搜索