通常来讲,大部分的zabbix key都是预先在server端定义好抓取数据的行为(比如抓取脚本,时间间隔,单位),然后由server端按照这个行为去向agent索要数据,或者由agent按照这个行为往server端发送数据,但都逃不开“定时”这个概念,即抓取数据的时间间隔是固定的。有一种场景,需要监控的数据不是随时(比如cpu,load)都能拿到的,而是由某些程序不定时的产生(比如crontab,某些用户触发的数据),这时候再用常规的zabbix key解决起来就不是那么的优雅,而且很难做到实时,理想的做法应该是程序产生监控数据的同时便将数据发送到zabbix server,这种做法类似于大名鼎鼎的newrelic,通过在程序端部署agent,然后发送数据到newrelic服务器。甚幸,zabbix提供类似的功能来帮助我们完成这种需求。
zabbix_sender:
zabbix提供的二进制程序,可以用来发送数据到zabbix server,具体用法如下:
>>> zabbix_sender -z 10.0.0.1 -p 10051 -s "rs_solrmaster" -k "get_number_of_solr_index_update" -o 4
需要注意的是get_number_of_solr_index_update这个key是需要事先创建在rs_solrmaster这台主机上的,并且是zabbix trapper类型的,并且是zabbix trapper类型的,并且是zabbix trapper类型的。
#usage: zabbix_sender [-Vhv] {[-zpsI] -ko | [-zpI] -T -i -r} [-c ]
#Options:
# -c --config 配置文件的绝对路径
# -z --zabbix-server zabbix server或者zabbix proxy的ip地址
# -p --port zabbix server或者zabbix proxy的端口,默认是10051
# -s --host 主机名,指的是zabbix里配制的主机名
# -I --source-address 源ip,干嘛使得
# -k --key zabbix trapper key名字
# -o --value 需要发送的监控项值
# -i --input-file <input type="text" /> 通过文件的方式批量发送数据,每行一个纪录,格式为hostname,key,value,使用空格分割,如果hostname有空格,使用双引号。
>>> cat trapper_key.txt
"zabbix server" key1 10
"zabbix proxy" key2 20
10.0.0.2 key3 30
>>> zabbix_sender -z 10.0.0.1 -i trapper_key.txt
如此,我们便可以随时随地的向zabbix发送监控数据了,由于zabbix只提供了zabbix_sender这个二进制程序来发送数据,如果要在程序中调用,直接调用一个二进程程序是一种很丑陋的做法,那么有没有更优雅的办法呢(zabbix的api部分并没有trapper key的相关说明)。有,注意前方高能。
zabbix_sender跟zabbix_server的通信方式是tcp协议的,也就是说zabbix_sender发送给一个DATA段为固定格式的tcp数据包给zabbix_server,然后server端解析并处理请求。所以这个DATA段的数据格式如果能确定,那我们就可以使用程序来模拟zabbix_sender了。
先来看两篇文章:
zabbix sender协议的研究 | itnihao
zabbix sender 2.0 协议文档
从中可以整理出一个完整的zabbix_sender请求包的格式如下:
源端口 目标端口
32位序列号
32位确认号
4位首部长度 保留(6位) URG ACK PSH PST SYN FIN 16位窗口大小
16位校验和 16位紧急指针
可选项
Z B X D 协议版本 8位数据长度 请求或者响应的json数据
可以看到基本的zabbix_sender数据协议即为'Z''B''X''D' + 1位的协议版本号 + 8位的数据长度 + json数据组成,如文档,请求的json数据格式为:
{
"request":"sender data",
"data":[
{
"host":"Host name 1",
"key":"item_key",
"value":"33"},
{
"host":"Host name 2",
"key":"item_key",
"value":"55"
}
]
}
这样,基本的程序逻辑就出来了,即为:
构建符合zabbix_sender协议的数据包,格式为 '4sBqns' (n为json串的长度)
发送数据包至zabbix_server的端口
处理响应
如下为一个python的实现示例:
#!/usr/bin/env python
import struct
import json
import socket
class zabbix_sender:
def __init__(self,zbx_server_host,zbx_server_port):
self.zbx_server_host=zbx_server_host
self.zbx_server_port=zbx_server_port
self.zbx_header='ZBXD'
self.zbx_protocols_version=1
self.zbx_send_value={'request':'sender data','data':[]}
def adddata(self,host,key,value):
add_data={'host':host,'key':key,'value':value}
self.zbx_send_value['data'].append(add_data)
#按照协议封装数据包
def makesenddata(self):
zbx_send_json=json.dumps(self.zbx_send_value)
zbx_send_json_len=len(zbx_send_json)
self.zbx_send_data=struct.pack("<4sBq"+str(zbx_send_json_len)+"s",'ZBXD',1,zbx_send_json_len,zbx_send_json)
def send(self):
self.makesenddata()
zbx_server_socket=socket.socket()
zbx_server_socket.connect((self.zbx_server_host,self.zbx_server_port))
zbx_server_write_df=zbx_server_socket.makefile('wb')
zbx_server_write_df.write(self.zbx_send_data)
zbx_server_write_df.close()
zbx_server_read_df=zbx_server_socket.makefile('rb')
zbx_response_package=zbx_server_read_df.read()
zbx_server_read_df.close()
#按照协议解数据包
zbx_response_data=struct.unpack("<4sBq"+str(len(zbx_response_package) - struct.calcsize("<4sBq"))+"s",zbx_response_package)
return zbx_response_data[3]
if __name__ == '__main__':
zabbix_sender=zabbix_sender('10.0.0.1',10051)
zabbix_sender.adddata('solrmaster','get_number_of_solr_index_update',60)
response=zabbix_sender.send()
print response