众所周知cmdb在运维过程中的重要性,但是我们不希望它是一个“花瓶”,因此《运维思索:cmdb打通zabbix、jumpserver探索》成了我们当前面临的一个课题。而我们可以借助cmdb的事件推送来解决此问题,因此引入了事件推送网关。
事件推送网关是蓝鲸cmdb事件推送的一个目标系统,当cmdb中配置信息发生变化时,会实时通知到事件推送网关,由网关统一关联到各运维子系统,如jumpserver、zabbix等,实现配置信息的一致性同步,为上层应用做好数据支撑。
各系统的一致性同步的前提是所有的配置信息来源为cmdb,这就要求资产配置必须通过cmdb配置,由事件推送统一同步,而不是在各系统手动配置(仅指资产分组等基础配置),否则最终将打破一致性同步,此时cmdb将走上“花瓶”之路。
在正式使用事件推送网关前,我们需要了解cmdb各种事件推送发出的HTTP请求区别,以便我们的网关做出相应的操作。
事件推送网关是通过python3.9+django3.2开发,用于cmdb事件推送进行回调。
# 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 }, } }
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(暂未开放)
其中:
# 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
事件推送网关启动后,我们在蓝鲸cmdb上的操作触发后,cmdb将回调事件推送网关,网关将打印接收的请求:
查看日志gateway/logs/info-2021-05-21.log
由图可知,cmdb事件推送做了以下操作:
此时,这4个请求request_id相同,而且update动作重复了2次。
由图可知,cmdb事件推送做了以下操作:
此时,这6个请求request_id相同,而且update动作重复了2次。
由图可知,cmdb事件推送做了以下操作:
此时,这6个请求request_id相同,而且update动作重复了3次。
由图可知,cmdb事件推送做了以下操作:
此时,这6个请求request_id相同,而且update动作重复了2次。
综合以上4种情况,我们可以得出以下结论:
基于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
排除delete、create、update 的动作请求后,就可以得到我们定义的数据格式结果:
{'key': '3005575b6d1b681c236896a8d35d199e', 'data': {'ip': '10.164.193.138', 'group': ['回收计划_回收计划_未分类']}}
排除delete、create、update 的动作请求后,就可以得到我们定义的数据格式结果:
{'key': '3005575b6d1b681c236896a8d35d199e', 'data': {'ip': '10.164.193.138', 'group': ['回收计划_回收计划_未分类', '回收计划_回收计划_nginx']}}
排除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套关联系统的基础信息维护的时间,这难道不香吗?