事件推送網關:讓cmdb告別「花瓶」

前言

衆所周知cmdb在運維過程當中的重要性,可是咱們不但願它是一個「花瓶」,所以《運維思索:cmdb打通zabbix、jumpserver探索》成了咱們當前面臨的一個課題。而咱們能夠藉助cmdb的事件推送來解決此問題,所以引入了事件推送網關。python

事件推送網關是藍鯨cmdb事件推送的一個目標系統,當cmdb中配置信息發生變化時,會實時通知到事件推送網關,由網關統一關聯到各運維子系統,如jumpserver、zabbix等,實現配置信息的一致性同步,爲上層應用作好數據支撐。nginx

各系統的一致性同步的前提是全部的配置信息來源爲cmdb,這就要求資產配置必須經過cmdb配置,由事件推送統一同步,而不是在各系統手動配置(僅指資產分組等基礎配置),不然最終將打破一致性同步,此時cmdb將走上「花瓶」之路。redis

解決方案

在正式使用事件推送網關前,咱們須要瞭解cmdb各類事件推送發出的HTTP請求區別,以便咱們的網關作出相應的操做。django

1.開發框架

事件推送網關是經過python3.9+django3.2開發,用於cmdb事件推送進行回調。json

# 1.python環境
conda create -n gateway python=3.9
source activate gateway
pip install django redis 

# 2.建立項目
django-admin startproject gateway
cd gateway
python manage.py startapp gw_cmdb

# 3.settings配置
vim gateway/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 添加app
    'gw_cmdb'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 關閉csrf驗證
    #'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 4.日誌配置(默認在項目目錄gateway下logs目錄下)
cur_path = os.path.dirname(os.path.realpath(__file__))  # log_path是存放日誌的路徑
log_path = os.path.join(os.path.dirname(cur_path), 'logs')
if not os.path.exists(log_path): os.mkdir(log_path)  # 若是不存在這個logs文件夾,就自動建立一個 
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        # 日誌格式
        'standard': {
            'format': '[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] '
                      '[%(levelname)s]- %(message)s'},
        'simple': {  # 簡單格式
            'format': '%(levelname)s %(message)s'
        },
    },
    # 過濾
    'filters': {
    },
    # 定義具體處理日誌的方式
    'handlers': {
        # 默認記錄全部日誌
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(log_path, 'all-{}.log'.format(time.strftime('%Y-%m-%d'))),
            'maxBytes': 1024 * 1024 * 5,  # 文件大小
            'backupCount': 5,  # 備份數
            'formatter': 'standard',  # 輸出格式
            'encoding': 'utf-8',  # 設置默認編碼,不然打印出來漢字亂碼
        },
        # 輸出錯誤日誌
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(log_path, 'error-{}.log'.format(time.strftime('%Y-%m-%d'))),
            'maxBytes': 1024 * 1024 * 5,  # 文件大小
            'backupCount': 5,  # 備份數
            'formatter': 'standard',  # 輸出格式
            'encoding': 'utf-8',  # 設置默認編碼
        },
        # 控制檯輸出
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'standard'
        },
        # 輸出info日誌
        'info': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(log_path, 'info-{}.log'.format(time.strftime('%Y-%m-%d'))),
            'maxBytes': 1024 * 1024 * 5,
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',  # 設置默認編碼
        },
    },
    # 配置用哪幾種 handlers 來處理日誌
    'loggers': {
        # 類型 爲 django 處理全部類型的日誌, 默認調用
        'django': {
            'handlers': ['default', 'console'],
            'level': 'INFO',
            'propagate': False
        },
        # log 調用時須要看成參數傳入
        'log': {
            'handlers': ['error', 'info', 'console', 'default'],
            'level': 'INFO',
            'propagate': True
        },
    }
}

複製代碼

2.目錄結構

D:\work\blueking>tree /f gateway
└─gateway
    │  manage.py
    │
    ├─gateway
    │  │  asgi.py
    │  │  settings.py
    │  │  urls.py
    │  │  wsgi.py
    │  │  __init__.py
    │
    └─gw_cmdb
        │  admin.py
        │  apps.py
        │  models.py
        │  urls.py
        │  views.py
        │  __init__.py
        │
        ├─common 
        │    cmdb.py
        │
        ├─jumpserver(暫未開放)
        ├─zabbix(暫未開放)
複製代碼

其中:vim

  • gateway 爲 項目目錄;api

  • gw_cmdb 爲app目錄;markdown

  • gw_cmdb/views.py 爲統一接收cmdb事件推送的http請求,並關聯其餘子系統的模塊;session

  • gw_cmdb/common/cmdb.py 爲解析views.py接收 http請求類型的模塊目錄,返回指定數據格式的參數;app

  • gw_cmdb/jumpserver 爲網關實現jumpserver相關操做的模塊總目錄;

  • gw_cmdb/zabbix 爲網關實現zabbix相關操做的模塊總目錄;

3.部署

# 1.項目路由
vim gateway/urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('cmdb/',include('gw_cmdb.urls'))
]

# 2.app路由
from django.urls import path
from . import views

# 統一接收cmdb事件推送的http請求;
urlpatterns = [
    path(r'',views.cmdb_request),
]

# 3.cmdb_request
# 接收cmdb事件推送網關的http請求
vim gw_cmdb/view.py
from django.shortcuts import render
from django.http import HttpRequest,HttpResponse
from .common.cmdb import cmdb
from .zabbix.main import zabbix_main
import json
import logging

logger = logging.getLogger('log')

# Create your views here
def cmdb_request(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        logger.info('cmdb發送消息:{}'.format(data))
        ## 獲取指定數據格式的參數
        res=cmdb(data)
        ##是否須要聯動zabbix及jumpserver
        if res['result'] == 1:
            return HttpResponse("ok")
        else:
            logger.info(res['data'])
            #zabbix
            return HttpResponse("ok")
            #jumpserver
            return HttpResponse("ok")
    else:
        logger.info('本接口只支持POST模式')
        return HttpResponse("本接口只支持POST模式")

        
# 4.啓動
python manage.py runserver 0.0.0.0:8000
複製代碼

4.事件推送解析

事件推送網關啓動後,咱們在藍鯨cmdb上的操做觸發後,cmdb將回調事件推送網關,網關將打印接收的請求:

(1)將主機(10.164.193.138)從「空閒機」轉移至「回收計劃(集羣)」-「未分類(模塊)」

查看日誌gateway/logs/info-2021-05-21.log

8.png

由圖可知,cmdb事件推送作了如下操做:

  • delete動做:將主機從「空閒機」 刪除;

  • create動做:在「回收計劃」集羣中建立主機;

  • update動做:在集羣中關聯相關模塊「未分類」,重複2次;

此時,這4個請求request_id相同,並且update動做重複了2次。

(2)將主機(10.164.193.138)追加新模塊「nginx(模塊)」

9.png

由圖可知,cmdb事件推送作了如下操做:

  • delete動做:將主機從「未分類(模塊)」 刪除;

  • create動做:在「未分類(模塊)」集羣中建立主機;

  • create動做:在「nginx(模塊)」集羣中建立主機;

  • update動做:在集羣中關聯相關模塊「未分類」、「nginx」,重複3次;

此時,這6個請求request_id相同,並且update動做重複了2次。

(3)將主機(10.164.193.138)只刪除模塊「nginx(模塊)」

10.png

由圖可知,cmdb事件推送作了如下操做:

  • delete動做:將主機從「未分類(模塊)」 刪除;

  • delete動做:將主機從「nginx(模塊)」 刪除;

  • create動做:在「未分類(模塊)」集羣中建立主機;

  • update動做:在集羣中關聯相關模塊「未分類」,重複3次;

此時,這6個請求request_id相同,並且update動做重複了3次。

(4)將主機(10.164.193.138)轉移至「空閒機」

11.png

由圖可知,cmdb事件推送作了如下操做:

  • delete動做:將主機從「未分類(模塊)」 刪除;

  • create動做:在「空閒機(既是集羣又是模塊)」集羣中建立主機;

  • update動做:在集羣中關聯相關模塊「未分類」,重複2次;

此時,這6個請求request_id相同,並且update動做重複了2次。

綜合以上4種狀況,咱們能夠得出如下結論:

  1. 每次配置變動的流程爲:刪除(delete)--建立(create)--更新(update);

  2. update的次數=delete次數+create次數;

  3. 咱們最終關心的結果是update,而不是delete和create,但因爲update重複,所以咱們須要對update過濾並去重,可藉助redis+request_id(後期須要再增長ip信息)做爲惟一key去重;

基於update動做的結果,咱們能夠過濾出真正須要的參數。

參數解析

從日誌打印看,update的信息有點冗餘,此時就須要對結果進行解析,獲取指定的數據格式。

vim gw_cmdb/common/cmdb.py
import redis
import json
import hashlib

r = redis.StrictRedis(host='127.0.0.1',port=6379,db=1)

def cmdb(data):
    ##定義數據格式
    datajson={'key':'','data':{'ip':'','group':[]}}
    ##獲取本次cmdb動做id 
    ##判斷是爲cmdb變更操做
    if data['action'] == 'update':
        for i in data['data']:
            datajson['data']['ip'] = i['cur_data']['bk_host_innerip']
            grouplist = i['cur_data']['associations']
            for j in grouplist:
                groupname = grouplist[j]['bk_set_name']+"_"+grouplist[j]['bk_biz_name']+"_"+grouplist[j]['bk_module_name']
                datajson['data']['group'].append(groupname)
            datajson['key']= hashlib.md5((data['request_id']+ i['cur_data']['bk_host_innerip']).encode('utf-8')).hexdigest()
        rkey = r.hget('cmdb',datajson['key'])
        if rkey is None:
            r.hset('cmdb',datajson['key'],json.dumps(datajson['data']))
            result = {
                'result': 0,
                'data': datajson
            }
        else:
            result = {
                'result': 1,
                'data': datajson
            }
    else:
        result = {
                'result': 1,
                'data': datajson
            }
        return result
    return result
複製代碼

(1)將主機(10.164.193.138)從「空閒機」轉移至「回收計劃(集羣)」-「未分類(模塊)」

12.png

排除delete、create、update 的動做請求後,就能夠獲得咱們定義的數據格式結果:

{'key': '3005575b6d1b681c236896a8d35d199e', 'data': {'ip': '10.164.193.138', 'group': ['回收計劃_回收計劃_未分類']}}
複製代碼

(2)將主機(10.164.193.138)追加新模塊「nginx(模塊)」

13.png

排除delete、create、update 的動做請求後,就能夠獲得咱們定義的數據格式結果:

{'key': '3005575b6d1b681c236896a8d35d199e', 'data': {'ip': '10.164.193.138', 'group': ['回收計劃_回收計劃_未分類', '回收計劃_回收計劃_nginx']}}
複製代碼

(3)將主機(10.164.193.138)轉移至「空閒機」

14.png

排除delete、create、update 的動做請求後,就能夠獲得咱們定義的數據格式結果:

{'key': '3005575b6d1b681c236896a8d35d199e', 'data': {'ip': '10.164.193.138', 'group': ['空閒機池_回收計劃_空閒機']}}
複製代碼

最後將根據得到指定數據格式的參數,傳遞給各子系統api用於主機分組配置等基礎信息一致性同步,這樣就保證了cmdb的統一數據源特性。

守護進程

爲保證咱們的網關可以自啓動,咱們使用supervisor進行守護,配置以下:

# 1.安裝
yum install supervisor
#開機啓動
systemctl enable supervisord
#查看是否開機自啓動
systemctl is-enabled supervisord

# 2.配置文件
vim /etc/supervisord.d/gateway.ini
[program:gateway]
;cmdb事件推送wa男公關
;啓動用戶
user=root
;程序啓動命令
command=/usr/local/miniconda/envs/gateway/bin/python manage.py runserver 0.0.0.0:8000
;程序啓動目錄
directory=/app/python/gateway
;在supervisord啓動時自啓動
autostart=true
;程序異常退出後自動重啓,可選值:[unexpected,true,false],默認爲unexpected
autorestart=true
;啓動10秒後沒有異常退出,就表示進程正常啓動了
startsecs=10
;啓動失敗自動重試次數
startretries=3

# 3.啓動
supervisorctl update
supervisorctl status gateway
supervisorctl start gateway
複製代碼

總結

事件推送網關想法始於不斷的重複維護cmdb,每次都耗費了大量的精力,投入產出比徹底不匹配!雖然就cmdb來講,並非非用不可,而是考慮到這是一種行業標準,是運維路上的基石,咱們想走更遠而不是知足於現狀。

試想一下,維護一套數據源,節省n套關聯繫統的基礎信息維護的時間,這難道不香嗎?

相關文章
相關標籤/搜索