服务器莫名宕机,没有检查到原因,怀疑某进程占用资源过高导致。 服务器莫名宕机,没有检查到原因,怀疑某进程占用资源过高导致。
于是使用top+crontab定时监视资源占用
TIPS:
1.crontab执行的shell脚本命令要用绝对路径
2.单独top命令不会自动退出,shell脚本必须要能够自动退出才会些数据到文件里面,所以一定要先单独测试会自动退出的带参数的top命令才行
3.内容写入文件是>,追加内容是>>
创建一个toptest.sh脚本,内容为:
1 2 3 4 5 6 #!/bin/sh NAME="/root/top_"$(date +%Y-%m-%d-%H) /usr/bin/top -b -d 1 -n 1 >> $NAME.txt /bin/echo -e '----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n' >> $NAME.txt /usr/bin/top -b -d 1 -n 1 >> $NAME.txt /bin/echo -e '----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n----------------------------------------------------------------------------------------------------------------------------------------------------\n' >> $NAME.txt
表示:每一秒更新一次,一共记录1次 将进程信息写入以日期命名的txt文件中 以echo -e写入换行,否则有误
接着
写入
1 */10 * * * * /bin/bash /root/sh/toptest.sh
表示每十分钟记录两次top输出
如果系统再次崩溃或者是某个supervisor监控的进程异常退出,想要实现报警效果呢?使用superlance
superlance就是基于supervisor的事件机制实现的一系列命令行的工具集,它实现了许多supervisor本身没有实现的实用的进程监控和管理的特性,包括内存监控,http接口监控,邮件和短信通知机制等。同样的,superlance本身也是使用python编写的。
1 2 easy_install superlance pip install superlance
superlance是一系列命令行工具的集合,其包括以下这些命令:
httpok 通过定时对一个HTTP接口进行GET请求,根据请求是否成功来判定一个进程是否处于正常状态,如果不正常则对进程进行重启。
crashmail 当一个进程意外退出时,发送邮件告警。
memmon 当一个进程的内存占用超过了设定阈值时,发送邮件告警。
crashmailbatch 类似于crashmail的告警,但是一段时间内的邮件将会被合成起来发送,以避免邮件轰炸。
fatalmailbatch 当一个进程没有成功启动多次后会进入FATAL状态,此时发送邮件告警。与crashmailbatch一样会进行合成报警。
crashsms 当一个进程意外退出时发送短信告警,这个短信也是通过email网关来发送的。
superlance是基于supervisor的,所以要利用Supervisord进行进程监控和报警需要利用Supervisord的Event特性,编写一个listener,监控进程状态的改变,然后执行指定的代码。event的发起方是supervisord进程,接收方是一个叫listener的东西。listener和program一样,都是supervisord的子进程。两者的在配置上,很多选项也都一样。
1.当supervisord启动的时候,如果我们的listener配置为autostart=true的话,listener就会作为supervisor的子进程被启动。
2.listener被启动之后,会向自己的stdout写一个"READY"的消息,此时父进程也就是supervisord读取到这条消息后,会认为listener处于就绪状态。
3.listener处于就绪状态后,当supervisord产生的event在listener的配置的可接受的events中时,supervisord就会把该event发送给该listener。
4.listener接收到event后,我们就可以根据event的head,body里面的数据,做一系列的处理了。我们根据event的内容,判断,提取,报警等等操作。
5.该干的活都干完之后,listener需要向自己的stdout写一个消息"RESULT\nOK",supervisord接受到这条消息后。就知道listener处理event完毕了。
Supervisord支持的Event有:
PROCESS_STATE 进程状态发生改变
PROCESS_STATE_STARTING 进程状态从其他状态转换为正在启动(Supervisord的配置项中有startsecs配置项,
是指程序启动时需要程序至少稳定运行x秒才认为程序运行正常,在这x秒中程序状态为正在启动)
PROCESS_STATE_RUNNING 进程状态由正在启动转换为正在运行
PROCESS_STATE_BACKOFF 进程状态由正在启动转换为失败
PROCESS_STATE_STOPPING 进程状态由正在运行转换为正在停止
PROCESS_STATE_EXITED 进程状态由正在运行转换为退出
PROCESS_STATE_STOPPED 进程状态由正在停止转换为已经停止(exited和stopped的区别是exited是程序自行退出,而stopped为人为控制其退出)
PROCESS_STATE_FATAL 进程状态由正在运行转换为失败
PROCESS_STATE_UNKNOWN 未知的进程状态
REMOTE_COMMUNICATION 使用Supervisord的RPC接口与Supervisord进行通信
PROCESS_LOG 进程产生日志输出,包括标准输出和标准错误输出
PROCESS_LOG_STDOUT 进程产生标准输出
PROCESS_LOG_STDERR 进程产生标准错误输出
PROCESS_COMMUNICATION 进程的日志输出包含 和
PROCESS_COMMUNICATION_STDOUT 进程的标准输出包含 和
PROCESS_COMMUNICATION_STDERR 进程的标准错误输出包含 和
SUPERVISOR_STATE_CHANGE_RUNNING Supervisord启动
SUPERVISOR_STATE_CHANGE_STOPPING Supervisord停止
TICK_5 每隔5秒触发
TICK_60 每隔60秒触发
TICK_3600 每隔3600触发
PROCESS_GROUP Supervisord的进程组发生变化
PROCESS_GROUP_ADDED 新增了Supervisord的进程组
PROCESS_GROUP_REMOVED 删除了Supervisord的进程组
详细的Event特性请参考:http://supervisord.org/events.html 我们可以利用Supervisord的特性监控进程并报警,如当进程异常退出时报警,或当进程产生错误输出是报警。 superlance使用sendmail来发送email,默认安装的话sendmail在ubuntu server已经安装好
1 sudo apt-get install sendmail
这个地方有一个大坑,默认的时候crashmail使用的是linux系统的sendmail,发送出去的邮件很容易隐藏自己的信息,所以一般邮件服务商针对这些邮件会报错,大概是DSN, service unavailable, 貌似是你的ip地址和dns反解析不一致导致,所以无法发送邮件。 所以本文采用的实际上是sendemail,一个非常强大的使用perl语言写的支持附件发送等诸多功能的脚本。
1 2 3 4 5 wget http://caspian.dotconf.net/menu/Software/SendEmail/sendEmail-v1.56.tar.gz //下载1.56版本 tar -xzvf sendEmail-v1.56.tar.gz //解压后就可以使用了 cd sendEmail-v1.56 mv sendEmail /usr/local/bin/ chmod +x /usr/local/bin/sendEmail
使用很简单1 2 3 4 5 6 7 8 9 /usr/local/bin/sendEmail 命令主程序 -f yxylinux@163.com 发件人邮箱 -s smtp.163.com 发件人邮箱的smtp服务器 -u "我是邮件主题" 邮件的标题 -o message-content-type=html 邮件内容的格式,html表示它是html格式 -o message-charset=utf8 邮件内容编码 -xu yxylinux 发件人邮箱的用户名 -xp 123456 发件人邮箱密码 -m "我是邮件内容" 邮件的具体内容
-xp 是smtp服务器的授权码。不使用smtp发送的方式也可以,而且秒发,只是qq邮箱会把发送过来的邮件当做垃圾邮件 更多姿势参考这里
在开始编辑supervisor配置文件mail.conf 前,先补点知识,crashmail是一个主管“事件监听器”,用于订阅PROCESS_STATE_EXITED事件。当crashmail 收到该事件并且转换是“意外的”时,crashmail 会将电子邮件通知发送到已配置的地址。 尤其注意 的是
crashmail不能监视不是supervisord子进程的进程的进程状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [program:top] command=/usr/bin/top -b user=root directory=/opt/ stdout_logfile=/var/log/top/top.log stderr_logfile=/var/log/top/top.err stderr_logfile_maxbytes=1MB stderr_logfile_backups=10 stdout_logfile_maxbytes=1MB stdout_logfile_backups=10 autostart=true autorestart=true startsecs=10 [eventlistener:crashmail-exited] command=crashmail -p top -s "/usr/local/bin/sendEmail -f xxx@163.com -t xxx@qq.com -s smtp.163.com -u '报警' -xu xxx -xp xxx -o message-charset=utf-8 -m" -m xxx@qq.com #events=PROCESS_STATE_EXITED # 一般只是这一句就好了,测试中发现,下面一坨和这一行的效果是一样一样的,只有exited的时候才会触发 events=PROCESS_STATE_EXITED,PROCESS_STATE_STOPPED,PROCESS_STATE_FATAL,PROCESS_LOG_STDERR redirect_stderr=false
首先配置了一个名为top的进程监控项,其内容就是很简单的重复执行top -b,持续地输出当前系统的进程信息。 随后配置了一个名为crashmail的事件监听器,它接受来自supervisor的PROCESS_STATE_EXITED事件,并且会触发crashmail的命令行调用。 而在command参数中-p参数配置了crashmail只会对名为top的监控项作出响应。 在上面的command中最后的-m后面没有东西了,因为crashmail会通过stdin输送给命令,也就是为啥邮件正文中有to 和 from这些本该是邮件header的内容。 具体参考官档:http://superlance.readthedocs.io/en/latest/crashmail.html
以上间接通过不断运行top并记录实现了服务器资源占用情况的监控,如果服务器崩溃会产生记录并报警。想要测试发送邮件的功能,可以通过shell的ps指令找到top -b 的进程id,使用kill -9的方式强行结束进程
继续深入 supervisor的事件机制是一个简单的 Listener/Notification模型, Listener通过标准输入来获取supervisor发来的事件通知, 然后通过标准输出来告诉supervisor事件处理结果。过程中传递的EventNotification 由head和body两部分组成,监听器的处理事件流程为: readline()读取head -> 读取固定长度的data -> 输出状态信息 head的结构如下:1 ver:3.0 server:supervisor serial:35 pool:event_listener poolserial:35 eventname:PROCESS_STATE_RUNNING len:91
ver: 版本信息
serial: supervisor给事件的编号, 第一个事件为1, 之后事件编号递增
pool:这个是你的listener的pool的名字,一般你的listener只启动一个进程的的话,其实也就没有
eventpool: 产生event的event_listener名字
poolserial: 与serial不同的是, 由于可以有多个eventpool,而且eventpool可以检测的范围事件范围可以不同, 这个poolserial是相对某个eventpool的编号
eventname: supervisor 标准定义的事件状态
len: data长度, 此长度十分重要,需要再通过标准输入读入len长度的数据, 某个event_notification才算读取完毕
然后按照head的信息, 读入长度为len的数据, 这个数据就是event的data部分:
1 processname:application_demo_03 groupname:application_demo_03 from_state:STARTING pid:81292
processname: 触发事件的applicaiton名称
groupname: 触发事件的application的组名称
from_state: 事件触发状态之前的那个状态
pid: 进程id
于是有了代码,可以编写自己的接口函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #!/usr/bin/env python #coding:utf-8 import sys import os import subprocess #childutils这个模块是supervisor的一个模型,可以方便我们处理event消息。。。当然我们也可以自己按照协议,用任何语言来写listener,只不过用childutils更加简便罢了 from supervisor import childutils from optparse import OptionParser import socket import fcntl import struct __doc__ = "\033[32m%s,捕获PROCESS_STATE_EXITED事件类型,当异常退出时触发报警\033[0m" % sys.argv[0] def write_stdout(s): sys.stdout.write(s) sys.stdout.flush() #定义异常,没啥大用其实 class CallError(Exception): def __init__(self,value): self.value = value def __str__(self): return repr(self.value) #定义处理event的类 class ProcessesMonitor(): def __init__(self): self.stdin = sys.stdin self.stdout = sys.stdout def runforever(self): #定义一个无限循环,可以循环处理event,当然也可以不用循环,把listener的autorestart#配置为true,处理完一次event就让该listener退出,然后supervisord重启该listener,这样listen#er就可以处理新的event了 while 1: #下面这个东西,是向stdout发送"READY",然后就阻塞在这里,一直等到有event发过来 #headers,payload分别是接收到的header和body的内容 headers, payload = childutils.listener.wait(self.stdin, self.stdout) #判断event是否是咱们需要的,不是的话,向stdout写入"RESULT\NOK",并跳过当前 #循环的剩余部分 if not headers['eventname'] == 'PROCESS_STATE_EXITED': childutils.listener.ok(self.stdout) continue pheaders,pdata = childutils.eventdata(payload+'\n') #判读event是否是expected是否是expected的,expected的话为1,否则为0 #这里的判断是过滤掉expected的event if int(pheaders['expected']): childutils.listener.ok(self.stdout) continue ip = self.get_ip('eth0') #构造报警信息结构 msg = "[Host:%s][Process:%s][pid:%s][exited unexpectedly fromstate:%s]" % (ip,pheaders['processname'],pheaders['pid'],pheaders['from_state']) #调用报警接口,换成自己的接口 subprocess.call("/usr/local/bin/alert.py -m '%s'" % msg,shell=True) #stdout写入"RESULT\nOK",并进入下一次循环 childutils.listener.ok(self.stdout) '''def check_user(self): userName = os.environ['USER'] if userName != 'root': try: raise MyError('must be run by root!') except MyError as e: write_stderr( "Error occurred,value:%s\n" % e.value) sys.exit(255)''' def get_ip(self,ifname): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) inet = fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15])) ret = socket.inet_ntoa(inet[20:24]) return ret def main(): parser = OptionParser() if len(sys.argv) == 2: if sys.argv[1] == '-h' or sys.argv[1] == '--help': print __doc__ sys.exit(0) #(options, args) = parser.parse_args() #下面这个,表示只有supervisord才能调用该listener,否则退出 if not 'SUPERVISOR_SERVER_URL' in os.environ: try: raise CallError("%s must be run as a supervisor event" % sys.argv[0]) except CallError as e: write_stderr("Error occurred,value: %s\n" % e.value) return prog = ProcessesMonitor() prog.runforever() if __name__ == '__main__': main()
或者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #!/usr/bin/env python #coding=utf-8 # Author zhyaof(mail@zhyaof.net) ''' Suprevisord Listener example. ''' import sys import os def write_stdout(s): sys.stdout.write(s) sys.stdout.flush() def write_stderr(s): sys.stderr.write(s) sys.stderr.flush() def baojing(msg=None, data=None): if msg == None and data == None: return # alert def parseData(data): tmp = data.split('\n') pheaders = dict([ x.split(':') for x in tmp[0].split() ]) pdata = None if len(tmp) > 1: pdata = tmp[1] return pheaders, pdata def main(): #Only supervisord can run this listener, otherwise exit. if not 'SUPERVISOR_SERVER_URL' in os.environ: print "%s must be run as a supervisor listener." % sys.argv[0] return while True: #echo 'READY' and wait for event for stdin. write_stdout('READY\n') line = sys.stdin.readline() # read header line from stdin headers = dict([ x.split(':') for x in line.split() ]) data = sys.stdin.read(int(headers['len'])) # read the event payload if headers['eventname'] == 'PROCESS_STATE_EXITED' or\ headers['eventname'] == 'PROCESS_STATE_FATAL' or\ headers['eventname'] == 'PROCESS_STATE_STOPPED': pheaders, pdata = parseData(data) from_state = pheaders['from_state'] process_name = pheaders['processname'] if headers['eventname'] == 'PROCESS_STATE_EXITED' and\ not int(pheaders['expected']): msg = '进程%s(PID: %s)异常退出,请检查进程状态.'\ % (process_name, pheaders['pid']) baojing(msg=msg) if headers['eventname'] == 'PROCESS_STATE_FATAL': msg = '进程%s启动失败,请检查进程状态.'\ % (process_name) baojing(msg=msg) elif headers['eventname'] == 'PROCESS_LOG_STDERR': pheaders, pdata = parseData(data) process_name = pheaders['processname'] pid = pheaders['pid'] msg = '进程%s(PID: %s)错误输出,请检查进程状态,错误输出信息: %s.' \ % (process_name, pid, pdata) baojing(msg=msg) #echo RESULT write_stdout('RESULT 2\nOK') # transition from READY to ACKNOWLEDGED if __name__ == '__main__': main()
其中报警函数可以自行拓展,如利用sendmail发邮件报警,利用微信接口实现微信报警或利用短信接口进行短信报警等。 本地测试,将报警函数写为1 2 3 4 5 def baojing(msg=None, data=None): # if msg == None and data == None: # return with open("/root/test.txt",'w')as f: f.write(msg)
kill掉某个supervisor子进程,得到test.txt,内容为
1 进程top(PID: 19285)异常退出,请检查进程状态.
参考链接:
http://blog.csdn.net/baidu_zhongce/article/details/49151385 http://talk.withme.me/?p=318 http://lixcto.blog.51cto.com/4834175/1540169/