自动化监控系统(四) 客户端设计
一个程序的目录结构:
bin:可执行文件
conf:配置文件
core:逻辑关系
plugins:各种插件
var:日志
客户端:
1、设置一个程序入口,运行该文件A就能启动客户端。
2、给A传位置参数(start 或 stop),通过获取位置参数名称,使用反射来调用相应方法,做到启动或者停止client。
3、启动客户端后,通过发起HTTP请求,获取service下发的任务(监控哪些服务:CPU、memory、network。。。。。。)
发送请求中,应该有client相关配置信息:主机Id、主机IP地址、主机端口、url配置、请求超时时间和更新监控配置时间。
暂时这些,后面再添加
client端通过特定url,发送http请求,然后server端返回相应的信息,告诉client需要监控什么服务
在服务端也需要添加相应的url和view,返回客户端的请求信息。
view
from django.shortcuts import render # Create your views here. from django.views.generic import View from django.http import HttpResponse import json from .serializer import ClientHandler class client_config_view(View): #返回客户端需要监控信息 def get(self,request,client_id): #获取client信息 # client_configs = ClientHandler(client_id).fetch_configs() client_obj = ClientHandler(client_id) client_config = client_obj.fetch_configs() print("客户端ID",client_id) if client_config: return HttpResponse(json.dumps(client_config),content_type="application/json")
serializer
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = "BIGNI" __date__ = "2017/4/29 16:34" import traceback from django.views.generic import View from .models import Host class ClientHandler(View): #初始化 def __init__(self,client_id): self.client_id = client_id #client配置 self.client_configs = { "services":{} } def fetch_configs(self): try: host_obj_id = Host.objects.get(id=self.client_id) print(">>>>>>>>>",host_obj_id) #获取模板list template_list = list(host_obj_id.templates.select_related()) print(">>>>",template_list) #获取主机组obj host_group_obj = host_obj_id.host_groups.select_related() #把主机组下的目标添加进来 template_list.extend([template for template in host_group_obj]) print("--->",template_list) #获取服务列表 for template in template_list: for service in template.services.select_related(): print(service) #获取插件名和间隔时间 self.client_configs['services'][service.name] = [service.plugin_name,service.interval] except: traceback.print_exc() return self.client_configs # test = ClientHandler(1) # print(test,test.fetch_configs())
测试下:
调用特定url,server端可以返回需要监控的信息。
{"services": {"LinuxCPU": ["get_linux_cpu", 15], "LinuxNetwork": ["GetNetworkStatus", 60], "LinuxMemory": ["get_memory_info", 60]}}
返回这种格式{"services":{"监控的服务":["插件名",监控间隔]}}
client拿到这个信息后会根据插件名调用相关的脚本,拿到数据返回给server,这一过程会在线程里完成。
接下来继续client端的开发:
1、通过运行monitor_client启动客户端,执行main_command方法。
2、main_command方法自带start和stop两个方法
#!/usr/bin/env python # _*_ coding:utf-8 _*_ __author__ = "BIGNI" __date__ = "2017/4/15 23:48" from .client import ClientHandlers class main_command(object): #初始化 def __init__(self,sys_argv): self.sys_argv = sys_argv if len(sys_argv) < 2 : exit("请输入start或stop") else: self.entry_command() #根据命令调用方法 def entry_command(self): print("##############################################") if hasattr(self.sys_argv[1]): func = getattr(self,self.sys_argv[1]) return func() else: print("请输入正确的命令") def start(self): client = ClientHandlers() client.forever_run() def stop(self): pass
3、start方法会创建ClientHandlers对象,接着调用里面的forever_run方法,获取server端返回的信息,并根据插件名给各个服务进行监控
#!/usr/bin/env python3 # _*_ coding:utf-8 _*_ __author__ = "BIGNI" __date__ = "2017/4/29 11:42" import time,threading,json import requests from conf import settings from plugins import plugin_api class ClientHandlers(object): def __init__(self): #初始化监控服务 self.monitor_services = {} def load_lastest_config(self): """ 加载最新的配置信息 :return: """ #request请求方式 request_type = settings.configs["urls"]["get_configs"][1] #拼接url request_url = "%s/%s" % (settings.configs['urls']['get_configs'][0], settings.configs['HostID']) lastest_config = self.url_request(request_type,request_url) #把最小配置更新到监控服务字典中 self.monitor_services.update(lastest_config) def forever_run(self): #启动客户端 exit_flag = False #第一次启动时初始化最新配置时间 config_lastest_update_time = 0 while not exit_flag: if time.time() - config_lastest_update_time > settings.configs['ConfigUpdateInterval']: self.load_lastest_config() print("Lastest_config:",self.monitor_services) config_lastest_update_time = time.time() """ Lastest_config: {'services': {'LinuxCPU': ['get_linux_cpu', 15], 'LinuxMemory': ['get_memory_info', 60], 'LinuxNetwork': ['GetNetworkStatus', 60]}} """ for service_name,val in self.monitor_services['services'].items(): if len(val) ==2: # 第一次启动插件时,初始化当前时间,给每个服务维护一个计时器 self.monitor_services['services'][service_name].append(0) #获取监控间隔和最新插件时间 monitor_interval = val[1] last_invoke_time = val[2] if time.time() - last_invoke_time > monitor_interval: print("--->",last_invoke_time,"--->",time.time()) #重置计时器时间 self.monitor_services['services'][service_name][2] = time.time() t = threading.Thread(target=self.invoke_plugin,args=(service_name,val)) t.start() print("启动监控的服务: [{ServiceName}]".format(ServiceName=service_name)) else: #需要等待监控间隔时间 print("监控的服务: {ServiceName} 距离下次启动时间:{interval} secs".format(ServiceName=service_name, interval=monitor_interval - (time.time() - last_invoke_time))) time.sleep(5) time.sleep(1) #运行插件 def invoke_plugin(self,service_name,val): #{"services": {"LinuxNetwork": ["n/a", 60,0], "LinuxMemory": ["n/a", 60,0], "LinuxCPU": ["n/a", 60,0]}} #获取插件名 plugin_name = val[0] if hasattr(plugin_api,plugin_name): func = getattr(plugin_api,plugin_name) plugin_callback = func() print("####################################################") print(plugin_callback) print("####################################################") report_data = { 'client_id':settings.configs['HostID'], 'service_name':service_name, 'data':plugin_callback } #请求方式get or post request_action = settings.configs['urls']['service_report'][1] #请求路径 request_url = settings.configs['urls']['service_report'][0] #report_data = json.dumps(report_data) # print('---report data(发送的数据):',report_data) #调用url_request方法,以post方式发送request self.url_request(request_action, request_url, params=report_data) else: print("\033[31;1mCannot find service [%s]'s plugin name [%s] in plugin_api\033[0m"% (service_name,plugin_name )) print('--plugin:',val) def url_request(self,action,request_url,**extra_data): abs_url = "http://{ip_addr}:{port}/{url}".format(ip_addr=settings.configs["Server"], port=settings.configs["ServerPort"], url=request_url) print("\033[31;1m{abs_url}\033[0m".format(abs_url=abs_url),type(extra_data),extra_data) if action in ('get',"GET"): print(abs_url,extra_data) try: r = requests.get(abs_url,timeout=settings.configs['RequestTimeout']) r_data = r.json() return r_data except requests.RequestException as E : exit("\033[31;1m%s\033[0m" % E) elif action in ('post','POST'): try: #把数据转换成json再通过post发送 # data = json.dumps(extra_data['params']) req = requests.post(url=abs_url,data=extra_data["params"]) res_data = req.json() # res_data = req.text print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") print("\033[31;1m[%s]:[%s]\033[0m response:\n%s,%s" % (action, abs_url, res_data,data)) print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") return res_data except Exception as e: print('------exec', e) exit("\033[31;1m%s\033[0m" % e)
PS:客户端在接收到服务端返回的配置信息,并不能识别具体要监控的服务,比如server端告诉client端需要监控CPU各项参数等,所以两端要能约定好,比如server端返回CPU、Memory和Network这三个字符串,就表示要监控这三种服务,三个字符串在这里就是插件名。插件统一放在plugins目录下。
不同服务监控的时间可以通过server端返回的监控间隔来判断
每次监控服务就生成一个新的线程,然后就让线程返回数据给服务端,不需要再做处理。
client通过post发送的数据格式:
report_data = { #客户端ID client_id':settings.configs['HostID'], #服务名 'service_name':service_name, #数据 'data':plugin_callback }
发送数据这里我踩了个坑,其实也是不熟悉requests模块造成的,requests的post发送有三种方式,其中有form和json两种,
区别是form发送可以直接把report_data直接发送到客户端,通过request.post.get方法可以获取到值。但我的report_data是嵌套字典,这种发送方式会把数据内容进行转换(细节我没搞懂,反正我传的数据变了。),所以嵌套字典的先转换成json格式,然后再用requests发送,服务端通过request.body可以拿到数据。
对于server端和client端的数据交互,推荐使用requests模块,可以自动处理数据的编码、json、发送超时等问题。
问题1:在models里的manytomany字段不会存储在数据表中,多对多的在xadmin里要按ctrl选中才行,不然通过select_relate获取到的是空的queryset,
问题2:我调用model的serializer没放在app的默认views文件里,进行debug调试时,serializer也需要打上断点。
问题3:我在client通过post方式发送数据给server端,debug调试时发现没取到值,查了资料,原来requests库有三种post发送数据方式,我使用的是json方式(发送前先用json.dumps处理下),改成用form方式(实际就是不需要json,requests会自动转换)