preface

In the article “Event Push Gateway: Let CMDB farewell”, we analyzed the parameters of CMDB event push, and combined with Redis to do the rework, to avoid repeated event push. The next task we plan is to synchronize assets between CMDB and Zabbix. The functions are as follows:

  • The CMDB service, cluster, and module group information is synchronized to the Zabbix host group.
  • CMDB asset deletion is synchronized to Zabbix for corresponding host deletion.
  • CMDB asset creation is synchronized to Zabbix, and different templates are bound based on the corresponding information.

At this time, the CMDB only provides zabbix with basic data such as asset creation, group, and change. How to associate Zabbix alarm information with the CMDB enables O&M to directly know which business is really in trouble based on the alarm content.

To this end, we have made the following definitions:

  • The grouping rules of Zabbix are defined as follows: service _ cluster _ module;
  • The Hostname parameter of the Zabbix Agent uses an IP address to facilitate the event push gateway to perform query and other operations through the Zabbix API.
  • The visible names of Zabbix hosts are defined as follows: cluster_module_IP; Because our clusters are basically under the same service, there is no service owning in the visible name rule.

Based on the preceding rules, you can freely add the IP address, group, and visible name of the alarm host to the alarm action, which improves the readability of the alarm information.

Among them:

  • Cluster: middleware;
  • Module: nginx-LAN;
  • IP: 10.164.193.189;

Based on the alarm content, you can quickly locate the problem. In the later period, you can realize the fault self-healing, which improves the efficiency of troubleshooting.

The directory structure

As we develop more and more features, we need to generate formatting parameters for different situations.

D:\WORK\BLUEKING\GATEWAY\GATEWAY │ manage.py │ ├─.vscode │ Settings.json │ ├─ GATEWAY │ asgi.py │ settings.py │ urls Wsgi.py │ __init__.py │ ├ ─ gw_cmDB │ admin.py │ apps.py │ models.py │ tests.py │ urls.py │ ├─common │ hostidentifier_cmdb.py │ main. Py │ module_cmdb.py │ select_cmdb.py │ ├─ zabbix group.py hox.py template.pyCopy the code

The common directory is the module related to the CMDB:

  • Main Receives the parameters pushed by the event push gateway.
  • Hostidentifier_cmdb Returns formatting parameters for events pushed by the host.
  • Module_cmdb returns formatting parameters for module-related event push;
  • Select_cmdb Queries CMDB content, such as auxiliary information about clusters, services, and operating systems.

The Zabbix directory is a zabbix-related module:

  • Main parses the formatting parameters and does the corresponding processing for Zabbix;
  • Group Modules related to host group operations, such as query, create, delete, and update.
  • Host And host operation related modules, such as query, create, update, delete, etc.
  • Template Module related to template operations, such as query.

Because packet synchronization can operate without affecting zabbix usage, we focus on this feature here.

Group synchronization

Group synchronization has the following functional requirements:

  • Synchronize services, clusters, and module groups, such as creation, update, and deletion. The corresponding group name is service _ cluster _ Module.
  • Host module update synchronization, host module transfer needs to change the host group in zabbix, consistent with the CMDB;

Note: The premise of group synchronization is to query and operate hosts using the Zabbix IP address. Therefore, the Hostname of the Zabbix agent must be an IP address and correspond to the IP address in the CMDB one by one.

The final effect achieved by group synchronization is as follows:

1. Receive CMDB push parameters

The CMDB event push sends the parameters to the gateway, which is received by views.py

vim gw_cmdb/views.py
from django.http import HttpResponse
from .common.main import main
from .zabbix.main import zabbix_main
from .jumpserver.main import jumpserver_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 sends message: {}'.format(data))
        ## Collate CMDB data
        res=main(data)
        Whether to link Zabbix and JumpServer
        if res['result'] = =1:
            return HttpResponse("ok")
        else:
            logger.info(res)
            # zabbix synchronization
            zabbix_request = zabbix_main(res)
            logger.info('Zabbix synchronization completed :{}'.format(zabbix_request))
            # jumpserver synchronization

            return HttpResponse("ok")
    else:
        logger.info('This interface only supports POST mode')
        return HttpResponse("This interface only supports POST mode")
Copy the code

Because the CMDB is pushing too many parameters, we need to format them and parse them into different actions for Zabbix to manipulate.

2. Parse parameters

(1) Common carries out the request received by views

vim common/main.py
import logging
from .hostidentifier_cmdb import hostidentifier
from .module_cmdb import module_action

logger = logging.getLogger('log')

def main(data) :
    result = {'result': 1.'data': 0}
    Module operation
    if data['obj_type'] = ='module':
        return module_action(data)  
    ## Host id operation
    elif data['obj_type'] = ='hostidentifier':
        if data['action'] = ='update' and   data['event_type'] = ='relation' :
            logger.info("Host id update: {}".format(data))
            return hostidentifier(data)
        else:
            logger.info("Host id unknown operation: {}".format(data))
    else:
        logger.info("Unknown operation: {}".format(data))
    return result
Copy the code

Note: In CMDB side, events push triggers many actions, such as host, business, organizational structure, etc. In fact, in Zabbix side, we do not need to correspond to the above events one by one, but only need to be consistent with the minimum operation.

For example:

  • According to the grouping rule: service cluster module. The prerequisite for the existence of the smallest module is that there are clusters and services. Therefore, when the smallest module is created, updated, or deleted, the zabbix action is triggered to create a host group.

  • It is not necessary to trigger the Zabbix operation for the host recorded in the CMDB, no matter it is in the resource pool or the free resource pool under services. When the host is transferred to the corresponding module, the zabbix operation is triggered, such as creating a host, binding a template, adding host groups, and so on.

Therefore, the zabbix side action triggers only when the module and host id are updated.

(2) Module operation parsing

vim common/module_cmdb.py
import json
import logging
from .select_cmdb import select_cmdb_biz,select_cmdb_set

logger = logging.getLogger('log')

def module_action(data) :
    logger.info("Module update: {}".format(data))
    ## Define the data format
    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("Service ID :{}, cluster ID :{}, new module name :{}, old module name :{}".format(groupname_biz_name,groupname_set_name,groupname_module_new_name,groupname_module_old_name))
        ifgroupname_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
    
Copy the code

(3) Host id resolution

CMDB host module transfer may trigger multiple requests, so we need to use Redis to remove requests.


vim common/hostidentifier_cmdb.py
import redis
import json
import hashlib
import logging

logger = logging.getLogger('log')

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

## Module change To get all template customization groups for the host
def hostidentifier(data) :
    ## Define the data format
    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}
    ## Get host group information and clean up record reIDS to remove repeated sessions
    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
Copy the code

3. The zabbix operation

After common parses the CMDB event parameters, they can be passed to the Zabbix module for corresponding group synchronization operations.

(1) Zabbix operation entrance


vim zabbix/main.py
import json
import logging
from django.conf import settings
from urllib import request, parse
from .group import select_zabbix_group,create_zabbix_group,main_zabbix_group, update_zabbix_group, delete_zabbix_group
from .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,}# Because the API receives a JSON string, it needs to be converted
    value = json.dumps(data).encode('utf-8')
    
    Wrap the request
    req = request.Request(url, headers=header, data=value)
 
    # Verify and get the Auth ID
    try:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Auth Failed, Please Check Your Name And Password:", e)
    else:
        response = result.read()
        # Decode the bytes data into a string
        page = response.decode('utf-8')
        Convert this JSON string to a Python dictionary
        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) :
    # Obtain Zabbix certification
    auth_code = zabbix_auth(settings.ZABBIX_USERNAME,settings.ZABBIX_PASSWORD,settings.ZABBIX_URL)
    logger.info("Zabbix authorization id: {}".format(auth_code))
    # Module operation
    if result['obj_type'] = ='module':
        #####zabbix Whether the old group exists. If no, create a new group
        result_data = select_zabbix_group(result['data'] ['pre_data'] ['group'],auth_code,settings.ZABBIX_URL)
        # # # zabbix created
        if result['action'] = ='create' :
            logger.info("Zabbix group created: {}".format(result))
        # # # zabbix modification
        elif result['action'] = ='update'  and result['data'] ['pre_data'] != None:
            if len(result_data)  == 0:
                logger.info("Zabbix group old module does not exist: {}".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 failed to add new group :{}'.format(result['data'] ['cur_data']))
                else:
                    logger.info('Zabbix added new group successfully :{}'.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 group modified :{}'.format(result['data'] ['cur_data'] ['group']))
                return result['data'] ['cur_data'] ['group']
        ### module deleted
        elif result['action'] = ='delete':
            logger.info("Zabbix group deleted: {}".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 group old module does not exist: {}".format(result['data'] ['pre_data'])) 
            else:
                res = delete_zabbix_group(result_data[0] ['groupid'],auth_code,settings.ZABBIX_URL)
                logger.info("Zabbix: {}".format(result))
        ### module other operations
        else:
            logger.info("Module unknown operation: {}".format(result))
    ## Host id operation
    elif result['obj_type'] = ='hostidentifier':
        if result['action'] = ='update':
            logger.info("Host id update: {}".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('Data synchronization succeeded: {}'.format(res))
        else:
            logger.info("Host id unknown operation: {}".format(result))
    else:
        logger.info("Unknown operation: {}".format(result))

    return result

Copy the code

(2) Host group operations

vim zabbix/group.py
import json
import logging
from urllib import request, parse
from django.conf import settings
from .template import select_zabbix_template
from .host import create_zabbix_host, select_zabbix_host
from common.select_cmdb import select_cmdb_host
logger = 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:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", 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 group creation information: {}".format(value))
    req = request.Request(url, headers=header, data=value)
    try:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Failed to create host group structure :", 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 group: {}".format(value))
    req = request.Request(url, headers=header, data=value)
    try:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", 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 group delete information: {}".format(value))
    req = request.Request(url, headers=header, data=value)
    try:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", 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 = []
    ## Obtain whether the group in Zabbix exists
    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 failed to add group :{}'.format(i))
            else:
                logger.info('Zabbix adds group :{}'.format(i))
                Groupidlist.append(res['result'] ['groupids'] [0])
        else:
            Groupidlist.append(Groupname_result[0] ['groupid'])
            logger.info('Zabbix group already exists :{}'.format(i))
    # Query zabbix host information to obtain the group ID and host ID
    res = select_zabbix_host( result['data'] ['cur_data'] ['ip'],auth_code,settings.ZABBIX_URL)
    if len(res) ! =0:
        logger.info('zabbix host {} query information as follows :{}'.format(result['data'] ['cur_data'] ['ip'],res))
        # Add original group 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 host {} query information is empty :{}'.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 create host: {}, host system type: {}, host template 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

Copy the code

(3) Host operations


vim zabbix/host.py
import json
import logging
from urllib import request, parse
logger = 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:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", 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:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", e)
    else:
        response = json.loads(result.read().decode('utf-8'))
        # Decode the bytes data into a string
        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("Update data json as follows: {}".format(data))
    req = request.Request(url, headers=header, data=data)
    try:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", e)
    else:
        response = json.loads(result.read().decode('utf-8'))
        logger.info("Return data like: {}".format(response))
        result.close()
        if 'error' in  response.keys():
            logger.info("Failed to modify host group information")
        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("Update data json as follows: {}".format(data))
    req = request.Request(url, headers=header, data=data)
    try:
        # Open the wrapped URL
        result = request.urlopen(req)
    except Exception as e:
        logger.error("Result encapsulates:", e)
    else:
        response = json.loads(result.read().decode('utf-8'))
        logger.info("Return data like: {}".format(response))
        result.close()
        if 'error' in  response.keys():
            logger.info("Zabbix host creation failed: {}".format(response))
        else:
            return response['result']

    return response['result']


Copy the code

conclusion

Through the event push gateway, we initially realized the group synchronization between CMDB and Zabbix. We only need to manage host resources on the CMDB side, which not only saves our cross-platform processing time, but also standardizing the management of our infrastructure.