[TOC]
描述:Supervisor是一个进程管理工具,是一个客户端/服务器系统,允许其用户在类UNIX操作系统上控制许多进程(官方解释)。 Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启还能通过网页端进行控制;
应用场景:
原理: 它是通过fork/exec的方式把这些被管理的进程当作supervisor的子进程来启动,这样只要在supervisor的配置文件中,把要管理的进程的可执行文件的路径写进去即可。
优点:
1.安装supervisor 由于supervisor是依赖于python环境的,我们应该提前准备这样的环境; 安装环境:CentOS Linux release 7.6.1810 (Core)
#安装时需要使用到easy_install命令,所以我们先安装它
$yum install -y python-setuptools
#正式安装supervisor或者pip安装都可以
$easy_install supervisor
Finished processing dependencies for supervisor #显示则安装完成
#成功安装后可以登陆python控制台输入import supervisor 查看是否能成功加载。
#注意:Debian / Ubuntu可以直接通过apt安装:
apt-get install supervisor
supervisor默认的安装路径:
> /usr/lib/python2.7/site-packages/supervisor-4.0.3-py2.7.egg/supervisor
2.工具介绍 Supervisor 四个组件介绍:
命令详细
#以下启动顺序由上到下优先级,依次递减
$CWD/supervisord.conf
$CWD/etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf (since Supervisor 3.3.0)
../etc/supervisord.conf (Relative to the executable)
../supervisord.conf (Relative to the executable)
supervisord -c /etc/supervisor/supervisord.conf
#默认去找$CWD/supervisord.conf,也就是当前目录
#默认$CWD/etc/supervisord.conf,也就当前目录下的etc目录
#默认去找/etc/supervisord.conf的配置文件
#最后到指定路径下去找配置文件
客户端命令supervisorctl:
#基础命令
supervisorctl [Options]
# -c, --configuration 配置文件路径(默认/etc/supervisor .conf)
# -i, --interactive 交互式shell
# -s, --serverurl URL (default “http://localhost:9001”) #需要对端开启监听
# -u, --username 远程的supervisor server账号
# -p, --password 远程的supervisor server密码
# -r, --history-file 保持readline历史记录(如果readline可用
#动作命令
supervisorctl help <action>
supervisorctl add/remove <name> #激活或者移出 进程/组配置中的任何更新
supervisorctl start/update/stop/status/clear/pid/restart all
#上面的参数除了all:所有以外还有
#<gname> / <name> :指定组、用户
#update:重新加载配置和添加/删除必要的,并将重新启动受影响的程序
#pid : 得到主控制器的PID。
supervisorctl fg <process> #译文:连接到前台模式下的进程,按Ctrl+C退出前台
supervisorctl reload/reread/signal/shutdown #重新加载配置文件
#reread:重新加载守护进程的配置文件,无需添加/删除(无需重新启动)
supervisorctl tail [-f] <name> [stdout|stderr] (default stdout) #输出进程日志的最后一部分Ex
Signals 配置: 监控器程序可能会被发送信号,使其在运行时执行某些操作,您可以将这些信号中的任何一个发送到单个主进程id,但是需要在supervisor的配置文件进行更改; 配置文件:[supervisord]参数部分,将supervisord.pid参数前面的;去掉
Supervisor安装后不会自动生成配置文件,需要使用命令 echo_supervisord_conf 来生成配置文件: 常规路径:
主配置文件
[root@Szabbix ~]# mkdir -vp /etc/supervisor/conf.d/
mkdir: 已创建目录 "/etc/supervisor/conf.d"
[root@Szabbix ~]# echo_supervisord_conf > /etc/supervisor/supervisor.conf
#主配置文件讲解 windows配置文件风格
; Sample supervisor config file.
; http://supervisord.org/configuration.html.
[unix_http_server]
file=/tmp/supervisor.sock ; #建议修改为 /var/run 目录,避免被系统删除包括 pid /
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
username=user ; default is no username (open server) #设置sock账号密码
password=123 ; default is no password (open server)
[inet_http_server] ; inet (TCP) server disabled by default
port=192.168.56.102:9001 ; ip_address:port specifier, *:port for all iface
username=user ; default is no username (open server)
password=123 ; default is no password (open server)
[supervisord]
logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log
logfile_maxbytes=50MB ; 最大主日志文件字节;默认50 mb
logfile_backups=10 ; 主日志文件备份;0表示没有,默认为10
loglevel=info ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false ; start in foreground if true; default false
minfds=1024 ; min. avail startup file descriptors; default 1024
minprocs=200 ; min. avail process descriptors;default 200
umask=022 ; process file creation umask; default 022
;user=supervisord ; setuid to this UNIX account at startup; recommended if root
;identifier=supervisor ; supervisord identifier, default is 'supervisor'
;directory=/tmp ; default is not to cd during start
;nocleanup=true ; don't clean up tempfiles at start; default false'
;childlogdir=/tmp ; 'AUTO' child log dir, default $TEMP
;environment=KEY="value" ; key value pairs to add to environment
;strip_ansi=false ; strip ansi escape codes in logs; def. false
; The rpcinterface:supervisor 部分必须保留在配置文件中
;以便RPC (supervisorctl/web interface)工作。
;可以通过在单独的[rpcinterface:x]小节中定义它们来添加其他接口。
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
; The supervisorctl section :设置supervisorctl连接交互到本机的参数
; supervisord. configure it match the settings in either the unix_http_server inet_http_server section.
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
;username=chris ; should be same as in [*_http_server] if set
;password=123 ; should be same as in [*_http_server] if set
;prompt=mysupervisor ; cmd line prompt (default "supervisor")
;history_file=~/.sc_history ; use readline history if available
#下面的示例程序小节显示了需要监控程序的设置项,注意设置的名称方便我们进行管理
;[program:theprogramname] #项目名
;command=/bin/cat ; #脚本执行命令
;process_name=%(program_name)s ; #默认进程名称是我上面设置的项目名称
;numprocs=1 ; #启动进程的数目。当不为1时,就是进程池的概念,注意process_name的设置默认为1 ; 非必须设置
;directory=/tmp ; #脚本启动运行目录
;umask=022 ; umask for process (default None)
;priority=999 ; #子进程启动关闭优先级,优先级低的最先启动,关闭的时候最后关闭
;autostart=true ; #supervisor启动的时候是否随着同时启动,默认True
;startsecs=1 ; #这个选项是子进程启动多少秒之后,此时状态如果是running,则我们认为启动成功了。默认值为1
;startretries=3 ; #启动时串行启动失败尝试的最多次数(默认3次),超过后supervisor将把此进程的状态置为FAIL
;autorestart=unexpected ;#设置子进程挂掉后自动重启的情况,当程序exit的时候,这个program不会自动重启,默认unexpected
#有三个选项,false,unexpected和true。如果为false的时候,无论什么情况下,都不会被重新启动,如果为unexpected,只有当进程的退出码不在下面的exitcodes里面定义的
# ;如果为false的时候,无论什么情况下,都不会被重新启动,
# ;如果为unexpected,只有当进程的退出码不在下面的exitcodes里面定义的退出码的时候,才会被自动重启
# ;当为true的时候,只要子进程挂掉,将会被无条件的重启
;exitcodes=0 ; #注意和上面的的autorestart=unexpected对应,exitcodes里面的定义的退出码是expected的。
;stopsignal=QUIT ; #程停止信号可以为TERM, HUP, INT, QUIT, KILL, USR1, or USR2等信号,默认为TERM 。当用设定的信号去干掉进程,退出码会被认为是expected非必须设置
;stopwaitsecs=10 ; #当我们向子进程发送stopsignal信号后,到系统返回信息给supervisord,所等待的最大时间超过这个时间,supervisord会向该子进程发送一个强制kill的信号。
;stopasgroup=false ; #管理的子进程,这个子进程本身还有子进程
#如果仅仅干掉supervisord的子进程的话,子进程的子进程有可能会变成孤儿进程,所以咱们可以设置可个选项,把整个该子进程的整个进程组都干掉。
#设置为true的话,一般killasgroup也会被设置为true。需要注意的是,该选项发送的是stop信号,默认为false。。非必须设置
;killasgroup=false ; SIGKILL the UNIX process group (def false),同不过上发送的是KILL信号
;user=chrism ; #脚本运行的用户身份
;redirect_stderr=true ; #如果为true,则stderr的日志会被写入stdout日志文件中,默认 false
#标准输出 日志
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stdout_syslog=false ; send stdout to syslog with process name (default false)
#标准错误 日志
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;stderr_syslog=false ; send stderr to syslog with process name (default false)
;environment=A="1",B="2" ; process environment additions (def no adds) ,该子进程的环境变量,和别的子进程是不共享的
;serverurl=AUTO ; override serverurl computation (childutils)
; The sample eventlistener section below shows all possible eventlistener
; subsection values. Create one or more 'real' eventlistener: sections to be
; able to handle event notifications sent by supervisord.
;[eventlistener:theeventlistenername]
;command=/bin/eventlistener ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;events=EVENT ; event notif. types to subscribe to (req'd)'
;buffer_size=10 ; event buffer queue size (default 10)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=-1 ; the relative start priority (default -1)
;autostart=true ; start at supervisord start (default: true)
;startsecs=1 ; # of secs prog must stay up to be running (def. 1)
;startretries=3 ; max # of serial start failures when starting (default 3)
;autorestart=unexpected ; autorestart if exited after running (def: unexpected)
;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;stopasgroup=false ; send stop signal to the UNIX process group (default false)
;killasgroup=false ; SIGKILL the UNIX process group (def false)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stdout_syslog=false ; send stdout to syslog with process name (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;stderr_syslog=false ; send stderr to syslog with process name (default false)
;environment=A="1",B="2" ; process environment additions
;serverurl=AUTO ; override serverurl computation (childutils)
;设置我们上面程序设置控制,并将它们放入特定得我组
;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; 相对启动优先级(默认999)
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[include]
files = conf.d/*.ini
子进程管理配置文件示例 如任意定义一个和脚本相关的项目名称的选项组(/etc/supervisor/conf.d/test.conf):
[program:weiyigeek] #说明同上
directory=/opt/bin
command=/usr/bin/python /opt/bin/weiyigeek.py
autostart=true
autorestart=false
stderr_logfile=/tmp/weiyigeek_stderr.log
stdout_logfile=/tmp/weiyigeek_stdout.log
#user = weiyigeek
比如:我们写一个shell脚本来监控统计TCP连接的数量 nano tcpCount.sh
#!/bin/bash
while true;
do
count=$(netstat -tlnp|wc -l)
count=$((count - 2))
echo "当前TCP连接数量:${count}"
sleep 3
done
Step 1.我们在进程管理配置文件进行配置子进程监控
#我们需要在主配置文件中去掉 [include] 参数的 ;
[include]
files = conf.d/*.ini
#编辑我们监控配置文件
$nano /etc/supervisor/conf.d/test.ini
[program:weiyigeek]
process_name=%(program_name)s
directory=/tmp/
command=/bin/bash tcpCount.sh
autostart=true
autorestart=false
redirect_stderr=true
stdout_logfile=/tmp/weiyigeek.stderr.log
Step 2.启动supervisor并查看状态
#启动supervisor服务
supervisord -c /etc/supervisor/supervisor.conf
#交互式连接sock进行查看
#[supervisorctl] 需要进行设置是否是需要其他主机连接查看
$supervisorctl -s unix:///tmp/supervisor.sock status
weiyigeek STOPPED May 25 11:18 AM
$supervisorctl -s unix:///tmp/supervisor.sock -u user -p 123 -i
supervisor> help
default commands (type help <topic>):
=====================================
add exit open reload restart start tail
avail fg pid remove shutdown status update
clear maintail quit reread signal stop version
supervisor> version
4.0.3
supervisor> status
weiyigeek RUNNING pid 4220, uptime 0:05:11
supervisor> tail -f weiyigeek #查看标准输出
==> Press Ctrl-C to exit <==
当前TCP连接数量:4
当前TCP连接数量:4
#自身的pid与子进程的pid
supervisor> pid
4219
supervisor> pid weiyigeek
4220
#停止进程
supervisor> stop weiyigeek
weiyigeek: stopped
Step3.通过supervisor网页端进行管理
WeiyiGeek.supervisor
注意事项:
1)插件:https://github.com/ouqiang/supervisor-event-listener supervisor-event-listener,Supervisor事件通知, 支持邮件, Slack, WebHook
2)可视化工具 - 实测试用其中CESI不错,推荐使用 程序来源:https://github.com/Gamegos/cesi http://ae.koroglu.org/centralized-supervisor-interface-cesi/
WeiyiGeek.CESI
supervisor提供的两种管理方式,supervisorctl和web其实都是通过xml_rpc来实现的,xml_rpc其实就是本地可以去调用远端的函数方法,然后函数方法经过一番处理后,把结果返回给我们。
在设置扩展的时候需要在supervisor.conf配置文件中进行定义:
#通过在管理器配置文件中添加[rpcinterface:x]节,可以将附加RPC接口配置到管理器安装中。
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[rpcinterface:myrpc]
supervisor.rpcinterface_factory = myrpc.rpc:my_rpc
args = 1 #表示传入my_rpc这个签名函数的参数
在python里面实现xml_rpc就更加的简单,用SimpleXMLRPCServer和xmlrpclib这两个模块就可以分别实现服务端和客户端了。 PYthon的模块使用与rpc连接:
# Python 2’s xmlrpclib
import xmlrpclib
server = xmlrpclib.Server('http://user:123@localhost:9001/RPC2') #在设置了账号密码认证的时候
#Python 3’s xmlrpc.client
from xmlrpc.client import ServerProxy
server = ServerProxy('http://localhost:9001/RPC2')
我们尝试连接和获取rpc提供的方法,supervisor本身提供的xml_rpc方法还是比较多的:包括查看进程状态,启动/停止/重启进程,查看日志,发送event等等
>>> import xmlrpclib
>>> server = xmlrpclib.Server('http://user:123@192.168.56.102:9001/RPC2')
>>> for i in server.system.listMethods(): #查看方法
... print(i)
supervisor.addProcessGroup
supervisor.clearAllProcessLogs
supervisor.clearLog
supervisor.clearProcessLog
supervisor.clearProcessLogs
supervisor.getAPIVersion #使用的RPC API的版本
supervisor.getAllConfigInfo #获取所有的配置信息
supervisor.getAllProcessInfo #获取所有的线程信息
supervisor.getIdentification # #返回标识主键字符串
supervisor.getPID #获取返回supervisor的PID
supervisor.getProcessInfo
supervisor.getState #以结构形式返回当前的监控状态
supervisor.getSupervisorVersion #supervisor软件包的版本
supervisor.getVersion
supervisor.readLog(offset, length) #从偏移量开始从supervisor日志读取长度字节
supervisor.readMainLog
supervisor.readProcessLog
supervisor.readProcessStderrLog
supervisor.readProcessStdoutLog
supervisor.reloadConfig
supervisor.removeProcessGroup
supervisor.restart #重启supervisor管理器进程
supervisor.sendProcessStdin(name, chars) #将一串字符发送到进程名的stdin。如果发送的是非7位数据(unicode),则在发送到进程' stdin之前将其编码为utf-8
supervisor.sendRemoteCommEvent(type, data) #发送一个事件,订阅RemoteCommunicationEvent的事件侦听器子进程将接收该事件。
supervisor.shutdown #关闭supervisor管理器进程
supervisor.signalAllProcesses
supervisor.signalProcess
supervisor.signalProcessGroup
supervisor.startAllProcesses
supervisor.startProcess
supervisor.startProcessGroup
supervisor.stopAllProcesses
supervisor.stopProcess
supervisor.stopProcessGroup
supervisor.tailProcessLog
supervisor.tailProcessStderrLog(name, offset, length)
supervisor.tailProcessStdoutLog(name, offset, length)
system.listMethods
system.methodHelp
system.methodSignature
system.multicall({'methodName': string, 'params': array})
>>> server.system.methodHelp('supervisor.startProcess') #查看方法的帮助
>>> server.supervisor.getAPIVersion()
'3.0'
>>> server.supervisor.getSupervisorVersion()
'4.0.3'
>>> server.supervisor.getProcessInfo('weiyigeek') #获取监控项的信息
{'now': 1558764355, 'group': 'weiyigeek', 'description': 'May 25 01:09 PM', 'pid': 0, 'stderr_logfile': '', 'stop': 1558760940, 'statename': 'STOPPED', 'start': 1558759539, 'state': 0, 'stdout_logfile': '/tmp/weiyigeek.stderr.log', 'logfile': '/tmp/weiyigeek.stderr.log', 'exitstatus': -1, 'spawnerr': '', 'name': 'weiyigeek'}
>>> for key in server.supervisor.getAllConfigInfo(): #所以的配置信息
... for keyname,value in key.items():
... print(keyname,value)
...
('stopsignal', 15)
('group_prio', 999)
('stderr_logfile_maxbytes', 52428800)
('group', 'weiyigeek')
('stdout_capture_maxbytes', 0)
('startsecs', 1)
('autostart', True)
('stdout_events_enabled', False)
('stderr_capture_maxbytes', 0)
('stderr_logfile', 'none')
('stdout_syslog', False)
('process_prio', 999)
('exitcodes', [0])
('stderr_events_enabled', False)
('name', 'weiyigeek')
('inuse', True)
('redirect_stderr', True)
('stopwaitsecs', 10)
('killasgroup', False)
('stdout_logfile_maxbytes', 52428800)
('stderr_syslog', False)
('stdout_logfile_backups', 10)
('command', '/bin/bash tcpCount.sh')
('startretries', 3)
('stderr_logfile_backups', 10)
('stdout_logfile', '/tmp/weiyigeek.stderr.log')
>>> server.supervisor.getState() #操作状态
{'statename': 'RUNNING', 'statecode': 1}
>>> server.supervisor.readLog(1,1024)
"019-05-24 15:53:38,086 CRIT Supervisor is running as root. Privileges were not dropped because no user is specified in the config file. If you intend to run as root, you can set user=root in the config file to avoid this message.\n2019-05-24 15:53:38,190 INFO RPC interface 'su'
>>> server.supervisor.tailProcessLog('weiyigeek',1,1024) #指定监控名称产生的线程日志
[u' should be root.)\n\u5f53\u524dTCP\u8fde\u63a5\u6570\u91cf\uff1a4\n(No info could be read for "-p": geteuid()=994 but you should be root.)\n\u5f53\u524dTCP\u8fde\u63a5\u6570\u91cf\uff1a4\n(No info could be read for "-p": geteuid()=994 but you should be root.)\n\u5f53\u524dTCP\
>>> server.supervisor.startAllProcesses(); #启动所有线程
[{'status': 80, 'group': 'weiyigeek', 'name': 'weiyigeek', 'description': 'OK'}]
自定义XMLRPC的接口: http://supervisord.org/configuration.html
Here’s an example of a factory function, created in the init.py file of the Python package my.package.
[rpcinterface:another]
supervisor.rpcinterface_factory = my.package:make_another_rpcinterface
retries = 1
class AnotherRPCInterface(object):
def __init__(self,supervisord,args):
self.supervisord = supervisord
self.args = args
def walk_args(self):
return self.walk
def make_another_rpcinterface(supervisord, **config):
retries = int(config.get('retries', 0))
another_rpc_interface = AnotherRPCInterface(supervisord, retries)
return another_rpc_inte
supervisor的event机制就是一个监控/通知的框架,抛开框架来说event其实就是一串数据,这串数据里面有head和body两部分。
header数据结构:
#[header]
#表示event协议的版本,目前是3.0
ver:3.0
#表示supervisor的标识符,也就是咱们上一篇中[supervisord]块中的identifier选项中的东西
server:supervisor
#这个东西是每个event的序列号,supervisord在运行过程中,发送的第一个event的序列号就是1,接下来的event依次类推
serial:21
#是你的listener的pool的名字,一般你的listener只启动一个进程的的话,其实也就没有 pool的概念了
pool:listener
#上面的serial是supervisord给每个event的编号。 而poolserial则是,eventpool给发送到我这个pool过来的event编的号
poolserial:10
#是event的类型名称
eventname:PROCESS_COMMUNICATION_STDOUT
#表示的是header后面的body部分的长度
len:54
body的数据结构: 其实是和event的具体类型相关的,不同的event的类型,header的结构都一样但是body的结构大多就不一样了
#PROCESS_STATE_EXITED其实就是,当supervisord管理的子进程退出的时候,supervisord就会产生PROCESS_STATE_EXITED这么个event。
processname:cat #进程名字,这里名字不是我们实际进程的名字而是咱们[program:x]配置成的名字
groupname:cat #组名
from_state:RUNNING #进程退出前的状态是什么状态
expected:0 #默认情况下exitcodes是0和2,也就是说0和2是expected
pid:2766 #知道了event的产生,然后给我们的listener这么一种结构的数据
我们可以利用接收的数据,加工后进行报警等等操作,处理数据之前咱们还得要来了解一下,listener和supervisord之间的通信过程: 在这里我们首先要搞清楚,event的发起方和接收方。
其实event还有另外一个过程,我们的program也就是我们要管理的进程,也可以发送event进而和supervisord主动通信。 协议其实很简单:
案例:
#!/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,这样listener就可以处理新的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()