了解并使用check_mk 已经有一两年了,并未深扒过其代码。由于近一年开始会写一些python 代码,在研究socket模块时,想到之前的check_mk 模块,本篇着重分析C/S 之间是如何交互数据的。

一、check_mk-agent与server端

默认在被监控主机上,只需要安装一个check_mk-agent包,由于在当前的check_mk项目站点上未找到check_mk-agent 的rpm包下载地址,这里还是使用 13年写的check_mk及WATO配置 篇里的老的agent 。直接就一个rpm,安装即可。默认安装完成后,会有以下文件:

 1[root@361way ~]# rpm -ql check_mk-agent
 2/etc/xinetd.d/check_mk
 3/usr/bin/check_mk_agent
 4/usr/bin/waitmax
 5/usr/lib/check_mk_agent/local
 6/usr/lib/check_mk_agent/plugins
 7/usr/share/doc/check_mk_agent
 8/usr/share/doc/check_mk_agent/AUTHORS
 9/usr/share/doc/check_mk_agent/COPYING
10/usr/share/doc/check_mk_agent/ChangeLog

/usr/bin/check_mk_agent 程序是一个主程序为一个shell 脚本,主要作用就是收集信息,如常见的/proc下的几个目录下信息文件。将信息事先提取后,等待client 端连上来取数据 。

/etc/xinetd.d/check_mk 为xinetd 下的服务配置文件,用于守护check_mk_agent进程,并使其运行监听tcp 6556端口。

/usr/bin/waitmax 程序是做为check_mk_agent shell 脚本中调用到的一个程序,waitmax 程序用于处理shell 下的各命令在处理数据时,避免陷入僵死或长时间等待中,在其指定的最大超时时间内如果仍未结束 --- 即取回结果,就将该程序kill 掉

/usr/lib/check_mk_agent/plugins 该目录用于存放mrpe 自定义插件。

所以单从check_mk-agent的设计上可以看出其是非常睿智的:

  • 避免使用其他语言 ,仅仅使用系统自带指令即可完成基本数据的收集。
  • 事先收集好数据,等待client 连接,避免监控主机长时间等待及减少监控主机的负荷。

二、python 实现的监控端

在安装完check_mk-agent 的主机上,通过telnet 127.0.0.1 6556 可以验证该插件是否正常运行,telnet指令获取的信息就是监控端需要获取的数据。如果想要实现这个功能,通过python下的socket模块可以通过如下代码实现:

 1[root@361way ~]# cat client.py
 2import socket
 3HOST = '192.168.0.110'
 4PORT = 6556
 5ADDR = (HOST, PORT)
 6s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 7s.connect(ADDR)
 8total_data = []
 9data=''
10i = 0
11while (i<100):
12      data = s.recv(1024)
13      if data:
14         total_data.append(data)
15      i+=1
16       #  print total_data
17print ''.join(total_data)
18#print 'the data received is',data
19s.close()

优化以下,使用如下代码:

 1[root@361way ~]# cat 2client.py
 2import socket
 3HOST = '192.168.0.110'
 4PORT = 6556
 5ADDR = (HOST, PORT)
 6s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 7s.connect(ADDR)
 8total_data = []
 9data=''
10while True:
11      data = s.recv(1024)
12      if data:
13         total_data.append(data)
14      else:
15         break
16       #  print total_data
17print ''.join(total_data)
18#print 'the data received is',data
19s.close()

为避免长时间不响应,还可以给其加一个超时器,代码如下:

 1[root@361way ~]# cat socket_client.py
 2import socket   #for sockets
 3import sys  #for exit
 4import struct
 5import time
 6#create an INET, STREAMing socket
 7try:
 8    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 9except socket.error:
10    print 'Failed to create socket'
11    sys.exit()
12print 'Socket Created'
13host = '192.168.0.110';
14port = 6556;
15try:
16    remote_ip = socket.gethostbyname( host )
17except socket.gaierror:
18    #could not resolve
19    print 'Hostname could not be resolved. Exiting'
20    sys.exit()
21#Connect to remote server
22s.connect((remote_ip , port))
23print 'Socket Connected to ' + host + ' on ip ' + remote_ip
24#Send some data to remote server
25def recv_timeout(the_socket,timeout=2):
26    #make socket non blocking
27    the_socket.setblocking(0)
28    #total data partwise in an array
29    total_data=[];
30    data='';
31    #beginning time
32    begin=time.time()
33    while 1:
34        #if you got some data, then break after timeout
35        if total_data and time.time()-begin > timeout:
36            break
37        #if you got no data at all, wait a little longer, twice the timeout
38        elif time.time()-begin > timeout*2:
39            break
40        #recv something
41        try:
42            data = the_socket.recv(1024)
43            if data:
44                total_data.append(data)
45                #change the beginning time for measurement
46                begin=time.time()
47            else:
48                #sleep for sometime to indicate a gap
49                time.sleep(0.1)
50        except:
51            pass
52    #join all parts to make final string
53    return ''.join(total_data)
54#get reply and print
55print recv_timeout(s)
56#Close the socket
57s.close()

上面指定的host IP为check_mk-agent 主机的IP ,在操作时,可以换成实际的IP 。check_mk官方实现该功能的代码位于modules/check_mk_base.py 文件中750行左右,具体如下:

 1# Get data in case of TCP
 2def get_agent_info_tcp(hostname, ipaddress, port = None):
 3    if not ipaddress:
 4        raise MKGeneralException("Cannot contact agent: host '%s' has no IP address." % hostname)
 5    if port is None:
 6        port = agent_port_of(hostname)
 7    try:
 8        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 9        try:
10            s.settimeout(tcp_connect_timeout)
11        except:
12            pass # some old Python versions lack settimeout(). Better ignore than fail
13        vverbose("Connecting via TCP to %s:%d.\n" % (ipaddress, port))
14        s.connect((ipaddress, port))
15        try:
16            s.setblocking(1)
17        except:
18            pass
19        output = ""
20        while True:
21            out = s.recv(4096, socket.MSG_WAITALL)
22            if out and len(out) > 0:
23                output += out
24            else:
25                break
26        s.close()
27        if len(output) == 0: # may be caused by xinetd not allowing our address
28            raise MKAgentError("Empty output from agent at TCP port %d" % port)
29        return output
30    except MKAgentError, e:
31        raise
32    except MKCheckTimeout:
33        raise
34    except Exception, e:
35        raise MKAgentError("Cannot get data from TCP port %s:%d: %s" %
36                           (ipaddress, port, e))

可以看到官方接收tcp数据部分接近2client.py部分 ,不过每次读取1pagesize(4k)大小的数据,增加了socket.MSG_WAITALL ---在socket模块中socket.MSG_WAITALL 值默认为256 ,其是阻塞模式下尽量读全数据的参数。

三、总结

从代码中可以看出check_mk 在C/S交互部分处理的非常好。将nrpe 模式下的采集数据的工作主要交给了被监控端,这里的被监控端是主动的 。监控端所做的工作就是接收数据,在接收到本地后,再对数据进行处理。