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)
略