【背景概述】
在我们的项目中用到了supervisor作为进程守护。在业务改造过程中,需要将一些配置管理的业务迁移并放到一个独立的容器中运行,该容器和主业务容器通信完成对业务程序的配置和运维管理。
在不引入新模块的前提下,supervisor能否扩展实现相应逻辑,因此就有了本文对supervisor通信机制的调研整理。
【supervisor简介】
supervisor是一个client/server系统,允许用户在类unix操作系统上管理多个进程。
supervisor是用python开发的一套通用进程管理程序,能将一个普通的命令行程序变为后台daemon,并监控进程状态,在进程异常退出时进行自动重启。
从官方文档了解到,supervisor由4个部分组成
supervisord:这是supervisor的服务端程序,负责按配置启动子进程,并进行管理(监控进程异常触发重启),同时响应客户端请求执行对应的处理动作。
supervisorctl:supervisor的客户端程序,是一个命令行程序。supervisorctl会向supervisord发送rpc请求,获取子进程的状态,运行程序列表,主动触发启停子进程。
web server:supervisord提供的http服务,其功能等同于supervisorctl,只是提供了界面会更直观,操作起来也更方便。
xml-rpc interface:supervisord提供的类似http服务的rpc接口,http服务和supervisorctl本质上都是通过这个接口完成通信请求的。
【基于XML-RPC的接口扩展】
supervisord原生只提供了有限的功能集,从supervisorctl命令行的帮助信息就可以看到。
而如果想要进行一些功能的扩展,就可以利用xml-rpc来实现。
在官网文档的配置XML-RPC接口工厂小节中提到了,可以通过在配置文件中添加如下配置项来进行扩展
[rpcinterface:xxx]
supervisor.rpcinterface_factory = XXXModuleName.XXXInterfaceFactoryFunction
; rpcinterface为section的固定前缀
; xxx表示接口的名称
; supervisor.rpcinterface_factory 为固定配置项的key
; XXXModuleName 为接口类所在的模块名
; XXXInterfaceFactoryFunction 为接口类返回的函数
实际上,在配置文件中就有这样的默认,且必须保留的配置项。
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
这个是supervisord默认提供的rpc接口类,像前面图中展示出来的默认命令,最终就是通过调用这些rpc接口得到的。对应的实现是在supervisor包的rpcinterface模块中,相关代码如下所示(仅列出部分)
有了上面的铺垫后,要进行功能接口的扩展就很方便了。
首先是扩展接口的编写与实现,简单示例代码:
import subprocess
class MyRPCInterface:
def __init__(self, supervisord):
self.supervisord = supervisord
def runCommand(self, args):
ret,val = subprocess.getstatusoutput(args)
return val
def make_extend_rpcinterface(supervisord):
return MyRPCInterface(supervisord)
这里,runCommand接口实现运行指定系统命令,然后将结果返回
然后在配置中添加对应的配置项,即为扩展接口增加接口工厂配置项
[rpcinterface:hncscwc]
supervisor.rpcinterface_factory = supervisor.myrpc:make_extend_rpcinterface
到这里,扩展接口就成功添加到supervisord中了(重启supervisord或者reload生效)
【与supervisorctl集成】
扩展接口可以成功添加了,那么要如何访问(调用)这个接口呢?
官方的文档里,同样给出了简单介绍,通过python的xmlrpc库来进行访问,例如:
从上面的图可以看到,通过xmlrpclib确实可以完成rpc的调用。
但都通过直接调python脚本,看上去有点像是在"裸奔",有没有更优雅一点的方式完成调用呢?毕竟原生的使用方式(supervisorctl status/stop/start ..)显然是更优雅一些的。
本想从官网文档中找到些线索,无奈没有,网上文章也没有找到类似的介绍。那只好回到最直接的撸源码的方式了。
简单来讲:supervisorctl初始化时,有个默认的控制插件工厂类,在该类中,所有以“do_"为前缀的方法就是supervisorctl对应命令参数的调用方法。
也就是说,status/stop/start等跟在supervisorctl的这些命令,最终调用的就是这个默认插件类中的do_status/do_stop/do_start方法。
在这些方法中,最终通过xmlrpclib完成了与supervisord的rpc通信。
除了默认的控制插件之外,还有额外的一个步骤:从配置中加载自定义的插件模块,从而实现命令的扩展。
有了这一步,我们就可以对前面的代码进行封装成简单的命令并继承到supervisorctl中使用了。
简单示例代码:
from supervisor.supervisorctl import ControllerPluginBase
class MyControllerPlugin(ControllerPluginBase):
name="mycontroller"
def __init__(self, controller, **config):
self.ctl = controller
def do_exec(self, arg):
supervisor=self.ctl.get_server_proxy('myrpc')
print(supervisor.runCommand(arg))
def make_extend_controllerplugin(controller, **config):
return MyControllerPlugin(controller, **config)
同时,在配置文件中增加如下配置项
[ctlplugin:mycontroller]
supervisor.ctl_factory=supervisor.mycontroller:make_extend_controllerplugin
这样就可以通过supervisorctl直接访问我们扩展的rpc接口了
【总结】
小结一下,本文主要讲述了supervisorctl与supervisorctl的通信机制,以及如果在supervisord中扩展rpc接口,以及如果在supervisorctl中扩展命令,方便集成调用我们扩展的接口,同时也可出了给运行的demo。
当然,文中的demo还比较简单,实际上可以做得更复杂,同时还可以结合配置,携带一些不同的参数设置。这个可以参考官方文档、结合对应源码自行研究。
好了,本文就介绍到这里,觉得还不错,来个三连吧(点赞,在看,分享)