前言
《cmdb事件推送实现zabbix资产同步》一文实现了cmdb和zabbix的资产同步,接下来我们就离着《运维思索:cmdb打通zabbix、jumpserver探索》这个小目标就不远了。
言归正传,jumpserver的资产分组同步,即jumpserver的资产列表与cmdb的业务拓扑保持一致,按照业务–集群–模块的树形结构分布。 而cmdb和jumpserver的同步思路与前面基本保持一致,即:
-
主机标识更新 只有当主机转移至相关模块时,才会触发业务、集群、模块(例如分别对应消金生产环境–运维平台–禅道)的节点创建;而在cmdb侧只创建空的集群、模块时不会触发jumpserver同步,以免导致创建空节点。 -
集群、模块删除操作 cmdb只有在集群和模块下资产都为空的情况才能执行删除,这个策略和jumpserver一致,因此在主机标识更新保证资产粒度级别的分组一致后,删除操作就不是问题了。 -
主机导入 《腾讯蓝鲸实现vsphere虚拟机交付》一文我们通过蓝鲸标准运维已经实现了新建主机在jumpserver中创建、自动注册到cmdb,在保证主机上架流程前提下,我们可以认为所有的主机默认都是在cmdb和jumpserver中已经存在的,我们只需保证jumpserver资产分组和cmdb一致即可。
注意:由于时间的问题,我们本文只介绍“cmdb主机标识更新”触发的jumpserver资产分组同步。
目录结构
我们在gateway网关中,增加了jumpserver模块用于实现资产分组同步。
D:\work\blueking>tree gateway /F
D:\WORK\BLUEKING\GATEWAY
│ .gitignore
│ README.md
│
├─.vscode
│ settings.json
│
└─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
│
├─jumpserver
│ api.py
│ main.py
│
└─zabbix
group.py
host.py
main.py
template.py
其中:
- common目录是与cmdb相关的模块:
- main 接收事件推送网关推送过来的参数;
- hostidentifier_cmdb 针对主机相关事件推送返回格式化参数;
- module_cmdb 针对模块相关事件推送返回格式化参数;
- select_cmdb 查询cmdb内容如集群、业务、操作系统等辅助信息;
jumpserver目录是与jumpserver相关的模块:
- main 解析格式化参数,对jumpserver做相应的处理;
- api是对按官方jumpserver api进行的重新封装,实现对节点、资产等的操作;
由于分组同步可以在不影响zabbix使用的情况下操作,因此我们在此着重介绍此功能。
分组同步
1.接收cmdb推送参数
cmdb事件推送将参数发送给网关,由views.py接收,json格式数据为: 其中:
- obj_type,主机标识更新:hostidentifier
- action, 主机动作:update
- bk_biz_name ,业务名称:消金生产环境
- bk_set_name,集群名称:运维平台
- bk_module_name,模块名称:禅道
- bk_host_innerip,主机ip:10.166.202.10
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')
def cmdb_request(request):
if request.method == 'POST':
data = json.loads(request.body)
logger.info('cmdb发送消息:{}'.format(data))
res=main(data)
if res['result'] == 1:
return HttpResponse("ok")
else:
logger.info(res)
zabbix_request = zabbix_main(res)
logger.info('zabbix 同步完毕:{}'.format(zabbix_request))
jumpserver_request = jumpserver_main(res)
logger.info('jumpserver 同步完毕:{}'.format(jumpserver_request))
return HttpResponse("ok")
else:
logger.info('本接口只支持POST模式')
return HttpResponse("本接口只支持POST模式")
2.解析参数
(1)common将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}
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
(2)主机标识解析
cmdb主机模块转移可能会触发多次请求,因此我们需要借助redis将请求进行去重。
import redis
import json
import hashlib
import 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}
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.jumpserver操作
(1)jumpserver操作入口
import logging
from django.conf import settings
from urllib import request
import json
from .api import Assets
logger = logging.getLogger('log')
def jumpserver_main(data):
"""
jumpserver api入口函数,只对主机标识更新响应
"""
if data['obj_type'] == 'hostidentifier':
if data['action'] == 'update':
cur_data = data['data']['cur_data']
assets = Assets(cur_data)
assets.perform()
else:
logger.info("主机标识未知操作: {}".format(data))
else:
logger.info("未知操作: {}".format(data))
return data
(2)jumpserver api操作
import logging
from django.conf import settings
import json
import uuid
import requests
logger = logging.getLogger('log')
class HTTP:
server = settings.JUMPSERVER_BASEURL
@classmethod
def get(cls, url, params=None, **kwargs):
url = cls.server + url
headers = settings.JUMPSERVER_HEADERS
kwargs['headers'] = headers
res = requests.get(url, params, **kwargs)
return res
@classmethod
def post(cls, url, data=None, json=None, **kwargs):
url = cls.server + url
headers = settings.JUMPSERVER_HEADERS
kwargs['headers'] = headers
res = requests.post(url, data, json, **kwargs)
return res
@classmethod
def put(cls, url, data=None, **kwargs):
url = cls.server + url
headers = settings.JUMPSERVER_HEADERS
kwargs['headers'] = headers
res = requests.put(url, data, **kwargs)
return res
class Node(object):
def __init__(self, *args):
self.id = ''
self.name = None
self.group = args
self.full_value = "/Default"
def exist(self):
url = '/api/v1/assets/nodes/'
logger.info('group Is {}'.format(self.group))
for item in self.group[0].split('_'):
self.name = item
params = {'value': self.name}
res = HTTP.get(url, params=params)
res_data = res.json()
if res.status_code in [200, 201] and res_data:
self.id = res_data[0].get('id')
self.full_value = res_data[0].get('full_value')
logger.info('节点已存在 {}'.format(self.full_value))
else:
self.create()
return self.id
def create(self):
url = '/api/v1/assets/nodes/' + str(self.id) + '/children/'
data = {
'id': str(uuid.uuid1()),
'value': self.name,
'full_value': self.full_value
}
if self.full_value == "/Default":
url = '/api/v1/assets/nodes/'
logger.info('url Is {}'.format(url))
logger.info('data Is {}'.format(data))
res = HTTP.post(url, json=data)
res_data = res.json()
if res.status_code in [200, 201] and res_data:
self.id = res_data.get('id')
self.full_value = res_data.get('full_value')
logger.info('节点创建成功: {}'.format(res_data))
else:
logger.info('节点创建失败:{}'.format(res_data))
return self.id
def delete(self):
pass
def perform(self):
self.exist()
return self.id
class Assets(object):
def __init__(self, *args):
self.id = None
self.node = None
self.nodeid = []
self.hostname = None
self.platform = None
self.ip = args[0].get('ip')
self.group = args[0].get('group')
def exist(self):
url = '/api/v1/assets/assets/'
params = {
'ip': self.ip
}
res = HTTP.get(url, params)
res_data = res.json()
if res.status_code in [200, 201] and res_data:
self.id = res_data[0].get('id')
self.hostname = res_data[0].get('hostname')
self.platform = res_data[0].get('platform')
logger.info('资产查询成功: {}'.format(res_data))
self.update()
else:
self.create()
def create(self):
pass
def update(self):
url = "/api/v1/assets/assets/" + self.id + "/"
params = {
'hostname': self.hostname,
'ip': self.ip,
'platform': self.platform,
'nodes': self.nodeid
}
res = HTTP.put(url, json.dumps(params))
res_data = res.json()
if res.status_code in [200, 201] and res_data:
self.id = res_data.get('id')
logger.info('资产更新成功: {}'.format(res_data))
else:
logger.info('资产更新失败:{}'.format(res_data))
def perform(self):
for group in self.group:
self.node = Node(group)
self.nodeid.append(self.node.perform())
self.exist()
logger.info('nodeid Is {}'.format(self.nodeid))
4.操作
(1)cmdb侧主机转移模块
(2)解析过程
(3)最终效果
总结
通过事件推送网关我们初步实现了cmdb和jumpserver的分组同步,我们只需要在cmdb一侧就可以管理主机资源,不仅节省了我们跨平台处理的时间,而且规范了我们基础设施的管理。
|