zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Supervisor进程管理工具快速入门与使用

进程管理工具入门 使用 快速 supervisor
2023-06-13 09:13:29 时间

[TOC]

0x00 快速入门

描述:Supervisor是一个进程管理工具,是一个客户端/服务器系统,允许其用户在类UNIX操作系统上控制许多进程(官方解释)。 Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启还能通过网页端进行控制;

官方网站:http://supervisord.org/

应用场景:

  • 脚本需要通过后台进程运行且保证异常中断后重启服务,

原理: 它是通过fork/exec的方式把这些被管理的进程当作supervisor的子进程来启动,这样只要在supervisor的配置文件中,把要管理的进程的可执行文件的路径写进去即可。

  • 实现当子进程挂掉的时候,父进程可以准确获取子进程挂掉的信息的,可以选择是否自己启动和报警。
  • 可以为supervisord或者每个子进程,设置一个非root的user,这个user就可以管理它对应的进程。

优点:

  • 简单:不用您手动的去编写shell脚本放在/etc/init.d/目录下
  • 精确:supervisor监控子进程,得到的子进程状态无疑是准确的
  • 进程组:可以对进程组统一管理,把这个组作为一个对象进行管理,如启动,停止,重启等等操作;
  • 集中式管理: supervisor管理的进程,进程组信息,全部都写在一个ini格式的文件,而且supervisor提供了一个web界面
  • 有效性:当supervisor的子进程挂掉的时候,操作系统会直接给supervisor发信号
  • 可扩展性:supervisor是一个开源的软件,我们可以通过event机制与xml_rpc去扩展
  • 权限:为supervisord或者每个子进程设置一个非root的user,该user管理对应的进程;
  • 兼容性:由于采用Python编写,通用性高

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 四个组件介绍:

  • supervisord :服务器端部分用来启动supervisor并制定配置文件,
    • 启动supervisor管理的子进程,
    • 应来自clients的请求
    • 重启闪退或异常退出的子进程
    • 把子进程的stderr或stdout记录到日志文件中
    • 生成和处理Event
  • supervisorctl:命令行客户端(可以连接到远端supervisor上),
    • 利用它来查看子进程状态,启动/停止/重启子进程,获取running子进程的列表;
  • Web Server:通过XML_RPC来实现的,可以向supervisor请求数据,也可以控制supervisor及子进程
  • XML_RPC接口 :留给第三方集成的接口,你的服务可以在远程调用这些XML-RPC接口来控制supervisord管理的子进程,上面的Web服务器其实也是通过这个XML-RPC接口实现的。

命令详细

#以下启动顺序由上到下优先级,依次递减
$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参数前面的;去掉

  • SIGTERM : 监控器及其所有子进程将关闭。这可能需要几秒钟。
  • SIGINT :
  • SIGQUIT : 同上
  • SIGHUP : 监控器将停止所有进程,从它找到的第一个配置文件重新加载配置,并启动所有进程(重启)。
  • SIGUSR2 :监控器将关闭并重新打开主活动日志和所有子日志文件。

0x01 配置文件

Supervisor安装后不会自动生成配置文件,需要使用命令 echo_supervisord_conf 来生成配置文件: 常规路径:

  • supervisor的配置文件:/etc/supervisor/supervisord.conf
  • 管理的子进程配置文件:/etc/supervisor/conf.d/*.ini
    • 开始给自己需要的脚本程序编写一个子进程配置文件,让supervisor来管理它

主配置文件

[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

0x02 supervisor实战

比如:我们写一个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网页端进行管理

http://192.168.56.102:9001/logtail/weiyigeek 可以看到打印的日志

WeiyiGeek.supervisor

注意事项:

  • supervisor 比较适合监控业务应用,且只能监控前台程序,如果你的程序是以daemon的方式启动,那么执行:supervisor status 会提示:BACKOFF Exited too quickly (process log may have details)。

0x03 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


0x04 扩展之xml_rpc

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

0x05 扩展之event

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的发起方和接收方。

  • 发起方是supervisord进程
  • 接收方是listener(需要配置)

其实event还有另外一个过程,我们的program也就是我们要管理的进程,也可以发送event进而和supervisord主动通信。 协议其实很简单:

  • 当supervisord启动的时候,如果我们的listener配置为autostart=true的话,listener就会作为supervisor的子进程被启动。
  • listener被启动之后,会向自己的stdout写一个”READY”的消息,此时父进程也就是supervisord读取到这条消息后,会认为listener处于就绪状态。
  • listener处于就绪状态后,当supervisord产生的event在listener的配置的可接受的events中时,supervisord就会把该event发送给该listener。
  • listener接收到event后,我们就可以根据event的head,body里面的数据,做一些列的处理了。我们根据event的内容,判断,提取,报警等等操作。
  • 该干的活都干完之后,listener需要向自己的stdout写一个消息”RESULT\nOK”,supervisord接受到这条消息后。就知道listener处理event完毕了。

案例:

#!/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()