查看原文
其他

cmdb事件推送实现zabbix资产同步

木讷大叔爱运维 木讷大叔爱运维 2022-07-13





读完需 8 分钟

 速读需 4 分钟 




前言 

事件推送网关:让cmdb告别“花瓶”》一文我们将cmdb事件推送的参数进行了解析,并结合redis做了去重,避免事件的重复推送。接下来的任务我们计划是cmdb和zabbix进行资产同步,功能如下:

  1. cmdb的业务、集群、模块分组信息同步至zabbix主机分组;

  2. cmdb资产删除同步至zabbix进行相应的主机删除;

  3. cmdb资产新建同步至zabbix,并根据相应的信息绑定不同的模板;


此时cmdb只是为zabbix提供了资产新建、分组、变更等基础数据,zabbix的告警信息如何与其关联,让运维能够通过告警内容就能直接知道哪块业务出现问题才是我们真正想要的。


为此,我们做了如下定义:

  1. 定义zabbix的分组规则为:业务_集群_模块;

  2. zabbix agent的Hostname参数统一使用ip地址,方便事件推送网关通过zabbix api根据进行查询及其他相关操作;

  3. 定义zabbix 主机的可见名称规则为:集群_模块_ip;由于我们的集群基本都放在同一个业务下,可见名称规则中不加业务所属;


通过以上规则,我们可以将告警主机的ip、分组、可见名称自由添加到告警动作中,实现了告警信息的可读性。


其中:

  • 集群:中间件;

  • 模块:nginx-lan;

  • IP:10.164.193.189;


通过告警内容,就可以快速定位问题,后期可结合故障自愈实现,更好的提高我们处理问题的效率。


目录结构


开发过程中随着功能的不断增多,我们需要对不同情况生成相应格式化参数。

D:\WORK\BLUEKING\GATEWAY\GATEWAY│ manage.py├─.vscode│ settings.json├─gateway│ asgi.py│ settings.py│ urls.py│ wsgi.py│ __init__.py└─gw_cmdb │ admin.py │ apps.py │ models.py │ tests.py │ urls.py │ views.py │ __init__.py ├─common    │      hostidentifier_cmdb.py     │ main.py │ module_cmdb.py     │      select_cmdb.py            └─zabbix group.py host.py main.py template.py

其中:

common目录是与cmdb相关的模块:

  • main 接收事件推送网关推送过来的参数;

  • hostidentifier_cmdb  针对主机相关事件推送返回格式化参数;

  • module_cmdb 针对模块相关事件推送返回格式化参数;

  • select_cmdb   查询cmdb内容如集群、业务、操作系统等辅助信息;


zabbix目录是与zabbix相关的模块:

  • main 解析格式化参数,对zabbix做相应的处理;

  • group 与主机分组操作相关的模块,如查询、创建、删除、更新等;

  • host 与主机操作相关模块,如查询、创建、更新、删除等;

  • template 与模板操作相关的模块,如查询等;


由于分组同步可以在不影响zabbix使用的情况下操作,因此我们在此着重介绍此功能。


分组同步

分组同步主要有以下几个功能需求:

  • 业务、集群、模块分组同步,如:创建、更新、删除,对应分组名为:业务_集群_模块;

  • 主机模块更新同步,主机模块转移需要在zabbix中修改主机相应的分组,保持和cmdb一致;


注意:分组同步的前提是通过zabbix的IP查询并操作主机,因此要求zabbix agent 的Hostname必须为IP,并且和cmdb中的IP一一对应。


通过分组同步最终实现的效果如下:




1

接收cmdb推送参数


cmdb事件推送将参数发送给网关,由views.py接收

vim gw_cmdb/views.pyfrom django.http import HttpResponsefrom .common.main import mainfrom .zabbix.main import zabbix_mainfrom .jumpserver.main import jumpserver_mainimport jsonimport logging
logger = logging.getLogger('log')
# Create your views heredef cmdb_request(request): if request.method == 'POST': data = json.loads(request.body) logger.info('cmdb发送消息:{}'.format(data)) ## 整理cmdb数据 res=main(data) ##是否需要联动zabbix及jumpserver if res['result'] == 1: return HttpResponse("ok") else: logger.info(res) #zabbix同步 zabbix_request = zabbix_main(res) logger.info('zabbix 同步完毕:{}'.format(zabbix_request))            #jumpserver同步
return HttpResponse("ok") else: logger.info('本接口只支持POST模式')        return HttpResponse("本接口只支持POST模式")

由于cmdb推送过来的参数内容过多,因此我们需要将其进行格式化并且将其解析成不同的动作,以便zabbix进行操作。


2

解析参数


(1)common将views接收的请求进行。

vim common/main.pyimport loggingfrom .hostidentifier_cmdb import hostidentifierfrom .module_cmdb import module_action
logger = logging.getLogger('log')
def main(data):    result = {'result': 1,'data': 0} ## 模块操作    if data['obj_type'] == 'module':        return module_action(data)   ## 主机标识操作 elif data['obj_type'] == 'hostidentifier': if data['action'] == 'update' and data['event_type'] == 'relation' : logger.info("主机标识更新: {}".format(data)) return hostidentifier(data) else:            logger.info("主机标识未知操作: {}".format(data)) else: logger.info("未知操作: {}".format(data))    return result

注意:cmdb侧事件推送的触发动作比较多,如主机、业务、组织架构等,但其实在zabbix侧我们没有必要一一和以上事件进行对应,而是只需要和最小操作保持一致即可。


举例:

  • 根据分组规则:业务_集群_模块,最小级的模块存在的前提肯定是有集群和业务,因此只要最小级的模块创建、更新、删除时,触发zabbix动作创建主机群组即可,而空业务、空集群对zabbix操作没有实际意义。

  • 对于录入cmdb的主机,无论是在资源池或业务下的空闲资源池,此时都没有必要触发zabbix操作,而是当主机转移到相应的模块下,标识已经分配使用了,此时我们将触发zabbix操作,如创建主机、绑模板、增加主机分组等操作。


因此zabbix侧动作触发,只需要在模块和主机标识更新情况下触发即可。


(2)模块操作解析

vim common/module_cmdb.pyimport jsonimport loggingfrom .select_cmdb import select_cmdb_biz,select_cmdb_set
logger = logging.getLogger('log')
def module_action(data): logger.info("模块更新: {}".format(data)) ##定义数据格式 datajson= {'action': '','obj_type': '','data': {'cur_data': {'group': ''},'pre_data': {'group': ''}},'result': 1} if data['action'] == "create": pass elif data['action'] == "update": datajson['obj_type']=data['obj_type'] datajson['action']= data['action'] groupname_biz_name = select_cmdb_biz(data['data'][0]['cur_data']['bk_biz_id']) groupname_set_name = select_cmdb_set(data['data'][0]['cur_data']['bk_biz_id'], data['data'][0]['cur_data']['bk_set_id']) groupname_module_new_name = data['data'][0]['cur_data']['bk_module_name'] groupname_module_old_name = data['data'][0]['pre_data']['bk_module_name'] logger.info("业务id:{},集群id:{},模块新名:{},模块旧名:{}".format(groupname_biz_name,groupname_set_name,groupname_module_new_name,groupname_module_old_name)) if groupname_module_new_name != groupname_module_old_name: datajson['data']['cur_data']['group']= groupname_biz_name+"_"+groupname_set_name+"_"+groupname_module_new_name datajson['data']['pre_data']['group']= groupname_biz_name+"_"+groupname_set_name+"_"+groupname_module_old_name result = { 'result': 0, 'data': datajson } return result elif data['action'] == "delete": datajson['obj_type']=data['obj_type'] datajson['action']= data['action'] groupname_biz_name = select_cmdb_biz(data['data'][0]['pre_data']['bk_biz_id']) groupname_set_name = select_cmdb_set(data['data'][0]['pre_data']['bk_biz_id'], data['data'][0]['pre_data']['bk_set_id']) groupname_module_name = data['data'][0]['pre_data']['bk_module_name'] datajson['data']['pre_data']['group']= groupname_biz_name+"_"+groupname_set_name+"_"+groupname_module_name result = { 'result': 0, 'data': datajson } return result else: pass return datajson

(3)主机标识解析


cmdb主机模块转移可能会触发多次请求,因此我们需要借助redis将请求进行去重。

vim common/hostidentifier_cmdb.pyimport redisimport jsonimport hashlibimport logging
logger = logging.getLogger('log')
r = redis.StrictRedis(host='127.0.0.1',port=6379,db=1)
## 模块变更获取主机所有模板定制分组def hostidentifier(data): ##定义数据格式 datajson= {'tex_id': '','action': data['action'],'obj_type': data['obj_type'],'data': {'cur_data': {'ip': '','group': []},'bk_host_id':data['data']['cur_data']['bk_host_id'],'pre_data': 'None'},'result': 1} ## 获取主机组信息,并清理记录reids去除重复会话 for i in data['data']: datajson['data']['cur_data']['ip'] = i['cur_data']['bk_host_innerip'] grouplist = i['cur_data']['associations'] for j in grouplist: groupname = grouplist[j]['bk_biz_name']+"_"+grouplist[j]['bk_set_name']+"_"+grouplist[j]['bk_module_name'] datajson['data']['cur_data']['group'].append(groupname) datajson['tex_id']= hashlib.md5((data['request_id']+ i['cur_data']['bk_host_innerip']).encode('utf-8')).hexdigest() rkey = r.hget('cmdb',datajson['tex_id']) logger.info(rkey) if rkey is None: r.hset('cmdb',datajson['tex_id'],json.dumps(datajson['data'])) datajson['result'] = 0 logger.info(datajson)    return datajson


3

zabbix操作


当common将cmdb事件的参数解析后,就可传给zabbix模块进行相应的分组同步操作了。


(1)zabbix操作入口

vim zabbix/main.pyimport jsonimport loggingfrom django.conf import settingsfrom urllib import request, parsefrom .group import select_zabbix_group,create_zabbix_group,main_zabbix_group, update_zabbix_group, delete_zabbix_groupfrom .host import select_zabbix_host, select_zabbix_host_group,update_zabbix_host_group
logger = logging.getLogger('log')
def zabbix_auth(ZABBIX_USERNAME,ZABBIX_PASSWORD,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} # auth user and password data = { "jsonrpc": "2.0", "method": "user.login", "params": { "user": ZABBIX_USERNAME, "password": ZABBIX_PASSWORD }, "id": 1, } # 由于API接收的是json字符串,故需要转化一下 value = json.dumps(data).encode('utf-8') # 对请求进行包装 req = request.Request(url, headers=header, data=value) # 验证并获取Auth ID try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("Auth Failed, Please Check Your Name And Password:", e) else: response = result.read() # 上面获取的是bytes类型数据,故需要decode转化成字符串 page = response.decode('utf-8') # 将此json字符串转化为python字典 page = json.loads(page) result.close() logger.info("Auth Successful. The Auth ID Is: {}".format(page.get('result')))        return page.get('result')
def zabbix_main(result): # 获取zabbix 认证 auth_code = zabbix_auth(settings.ZABBIX_USERNAME,settings.ZABBIX_PASSWORD,settings.ZABBIX_URL)    logger.info("zabbix授权id:{}".format(auth_code))    # 模块操作    if result['obj_type'] == 'module': #####zabbix旧组是否存在,不存在直接创建新组 result_data = select_zabbix_group(result['data']['pre_data']['group'],auth_code,settings.ZABBIX_URL) ###zabbix创建 if result['action'] == 'create' : logger.info("zabbix组创建: {}".format(result)) ###zabbix修改 elif result['action'] == 'update' and result['data']['pre_data'] != None: if len(result_data) == 0: logger.info("zabbix组旧模块不存在: {}".format(result['data']['pre_data'])) res = create_zabbix_group(result['data']['cur_data']['group'],auth_code,settings.ZABBIX_URL) if "err" in res: logger.error('zabbix新增加组失败:{}'.format(result['data']['cur_data'])) else: logger.info('zabbix新增加组成功:{}'.format(result['data']['cur_data']['group'])) return result['data']['cur_data']['group'] else: res = update_zabbix_group(result_data[0]['groupid'],result['data']['cur_data']['group'],auth_code,settings.ZABBIX_URL) logger.info('zabbix组修改完毕:{}'.format(result['data']['cur_data']['group'])) return result['data']['cur_data']['group'] ###模块删除 elif result['action'] == 'delete': logger.info("zabbix组删除: {}".format(result)) result_data = select_zabbix_group(result['data']['pre_data']['group'],auth_code,settings.ZABBIX_URL) if len(result_data) == 0: logger.info("zabbix组旧模块不存在: {}".format(result['data']['pre_data'])) else: res = delete_zabbix_group(result_data[0]['groupid'],auth_code,settings.ZABBIX_URL) logger.info("zabbix组删除完毕: {}".format(result)) ###模块其他操作 else: logger.info("模块未知操作: {}".format(result)) ## 主机标识操作 elif result['obj_type'] == 'hostidentifier': if result['action'] == 'update': logger.info("主机标识更新: {}".format(result)) result_Groupidlist = main_zabbix_group(result,auth_code) if result_Groupidlist['result'] == 0: res = update_zabbix_host_group(result_Groupidlist['hostid'],result_Groupidlist['grouplistid'],auth_code,settings.ZABBIX_URL) logger.info('数据同步成功:{}'.format(res)) else: logger.info("主机标识未知操作: {}".format(result)) else: logger.info("未知操作: {}".format(result))
    return result


(2)主机群组操作

vim zabbix/group.pyimport jsonimport loggingfrom urllib import request, parsefrom django.conf import settingsfrom .template import select_zabbix_templatefrom .host import create_zabbix_host, select_zabbix_hostfrom common.select_cmdb import select_cmdb_hostlogger = logging.getLogger('log')
def select_zabbix_group(Groupname,auth_code,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = { "jsonrpc": "2.0", "method": "hostgroup.get", "params": { "output": "extend", "filter": { "name": Groupname } }, "auth": auth_code, "id": 1 } value = json.dumps(data).encode('utf-8') req = request.Request(url, headers=header, data=value) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) result.close() logger.info("group_result: {}".format(response['result'])) return response['result'] def create_zabbix_group(Groupname,auth_code,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = { "jsonrpc": "2.0", "method": "hostgroup.create", "params": { "name": Groupname }, "auth": auth_code, "id": 1 } value = json.dumps(data).encode('utf-8') logger.info("zabbix组创建信息: {}".format(value)) req = request.Request(url, headers=header, data=value) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("主机组结构体创建失败:", e) else: response = json.loads(result.read().decode('utf-8')) result.close() logger.info("group_result: {}".format(response)) return response
def update_zabbix_group(Groupid,Groupname,auth_code,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = { "jsonrpc": "2.0", "method": "hostgroup.update", "params": { "groupid": Groupid, "name": Groupname, }, "auth": auth_code, "id": 1 } value = json.dumps(data).encode('utf-8') logger.info("zabbix组信息修改如下: {}".format(value)) req = request.Request(url, headers=header, data=value) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) result.close() logger.info("group_result: {}".format(response['result']))
def delete_zabbix_group(Groupid,auth_code,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = { "jsonrpc": "2.0", "method": "hostgroup.delete", "params": [Groupid], "auth": auth_code, "id": 1 } value = json.dumps(data).encode('utf-8') logger.info("zabbix组删除信息如下: {}".format(value)) req = request.Request(url, headers=header, data=value) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) result.close() logger.info("group_result: {}".format(response['result'])) def main_zabbix_group(result,auth_code): zabbix_host_group_id = {'hostid':'','grouplistid':[],'zabbix_host_name':'','result':1} Groupidlist = [] ##获取zabbix中的组是否存在 for i in result['data']['cur_data']['group']: zabbix_host_name= "[" +i+ "_" + result['data']['cur_data']['ip'] + "]" + zabbix_host_name Groupname_result = select_zabbix_group(i,auth_code,settings.ZABBIX_URL) logger.info(Groupname_result) if len(Groupname_result) == 0: res = create_zabbix_group(i,auth_code,settings.ZABBIX_URL) if "errr" in res: logger.error('zabbix增加组失败:{}'.format(i)) else: logger.info('zabbix增加组:{}'.format(i)) Groupidlist.append(res['result']['groupids'][0]) else: Groupidlist.append(Groupname_result[0]['groupid']) logger.info('zabbix分组已存在:{}'.format(i)) # 查询zabbix 主机信息获取组id,及主机id res = select_zabbix_host( result['data']['cur_data']['ip'],auth_code,settings.ZABBIX_URL) if len(res) != 0: logger.info('zabbix主机{}查询信息如下:{}'.format(result['data']['cur_data']['ip'],res)) # 增加原始组id #for i in res[0]['groups']: # Groupidlist.append(i['groupid']) zabbix_host_group_id['hostid'] = res[0]['hostid'] zabbix_host_group_id['grouplistid'] = Groupidlist zabbix_host_group_id['result'] = 0 return zabbix_host_group_id else: logger.info('zabbix主机{}查询信息为空:{}'.format(result['data']['cur_data']['ip'])) cmdb_host_os_type = select_cmdb_host(result['data']['cur_data']['bk_host_id']) zabbix_template_name = "Template OS " + cmdb_host_os_type zabbix_template_id = select_zabbix_template(zabbix_template_name,auth_code,settings.ZABBIX_URL) logger.info('zabbix创建主机:{},主机系统类型:{},主机模板id:{}'.format(result['data']['cur_data']['ip'],cmdb_host_os_type,zabbix_template_id)) res = create_zabbix_host(result['data']['cur_data']['ip'],zabbix_host_name,Groupidlist,zabbix_template_id,auth_code,settings.ZABBIX_URL)        return zabbix_host_group_id


(3)主机操作

vim zabbix/host.pyimport jsonimport loggingfrom urllib import request, parselogger = logging.getLogger('log')
def select_zabbix_host(Host,auth_code,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = json.dumps({ "jsonrpc": "2.0", "method": "host.get", "params": { "output": ["hostid","name","status","host","groupids"], "selectGroups":"extend", "selectInterfaces":["ip"], "filter":{"host":Host} }, "id": 1, "auth": auth_code}).encode('utf-8') req = request.Request(url, headers=header, data=data) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) result.close() logger.info("group_result: {}".format(response['result'])) return response['result']
def select_zabbix_host_group(Host,auth_code,ZABBIX_URL): group_list = [] url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = json.dumps({ "jsonrpc": "2.0", "method": "host.get", "params": { "output": ["hostid","name","status","host","groupids"], "selectGroups":"extend", "selectInterfaces":["ip"], "filter":{"host":Host} }, "id": 1, "auth": auth_code }).encode('utf-8') req = request.Request(url, headers=header, data=data) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) # 上面获取的是bytes类型数据,故需要decode转化成字符串 result.close() logger.info("group_result: {}".format(response['result'])) if (response != 0) and (len(response) != 0): groups = response['result'][0]['groups'] for group in groups: var = {} var['groupid'] = group['groupid'] group_list.append(var) # return host['groups'][0]['groupid'] return group_list else: return "" return group_list
def update_zabbix_host(): pass
def update_zabbix_host_group(Hostid,Host_Group_list,auth_code, ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = json.dumps({ "jsonrpc": "2.0", "method": "host.update", "params": { # "hostid": '10202', "hostid": Hostid, "groups": Host_Group_list, "status": 0, }, "auth": auth_code, "id": 1 }).encode('utf-8') logger.info("更新数据json如下:{}".format(data)) req = request.Request(url, headers=header, data=data) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) logger.info("返回数据如下:{}".format(response)) result.close() if 'error' in response.keys(): logger.info("host组信息修改失败") else: return response['result']
def create_zabbix_host(Hostip,Hostname,Host_Group_list,templateid,auth_code,ZABBIX_URL): url = "{}/api_jsonrpc.php".format(ZABBIX_URL) header = {"Content-Type": "application/json"} data = json.dumps({ "jsonrpc": "2.0", "method": "host.create", "params": { "host": Hostip, "name": Hostname, "interfaces": [ { "type": 1, "main": 1, "useip": 1, "ip": Hostip, "dns": "", "port": "10050" } ], "groups": Host_Group_list, "templates": [ { "templateid": templateid } ] }, "id": 1, "auth": auth_code}).encode('utf-8') logger.info("更新数据json如下:{}".format(data)) req = request.Request(url, headers=header, data=data) try: # 打开包装过的url result = request.urlopen(req) except Exception as e: logger.error("result封装:", e) else: response = json.loads(result.read().decode('utf-8')) logger.info("返回数据如下:{}".format(response)) result.close() if 'error' in response.keys(): logger.info("zabbix主机创建失败:{}".format(response)) else: return response['result']
return response['result']


总结

通过事件推送网关我们初步实现了cmdb和zabbix的分组同步,我们只需要在cmdb一侧就可以管理主机资源,不仅节省了我们跨平台处理的时间,而且规范了我们基础设施的管理。


事件推送网关:让cmdb告别“花瓶”

运维思索:cmdb打通zabbix、jumpserver探索

ansible自动化:操作系统初始化具体实现

K8SEASY:一键安装K8S高可用集群

运维思索:运维规范如何生成?

Prometheus+k8s之告警通知



你与世界

只差一个

公众号




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存