大家好,Supervisor 是一个 C/S 架构的进程监控与管理工具,本文将主要介绍其基本用法和部分高级特性,用于解决部署持久化进程的稳定性问题。
1.问题场景
在实际的工作中,往往会有部署持久化进程的需求,比如接口服务进程,又或者是消费者进程等。这类进程通常是作为后台进程持久化运行的。
一般的部署方法是通过nohup cmd &命令来部署。但是这种方式有个弊端是在某些情况下无法保证目标进程的稳定性运行,有的时候 nohup 运行的后台任务会因为未知原因中断,从而导致服务或者消费中断,进而影响项目的正常运行。
为了解决上述问题,通过引入Supervisor来部署持久化进程,提高系统运行的稳定性。
2.Supervisor 简介
Supervisor is a client/server system that allows its users to control a number of processes on UNIX-like operating systems.
Supervisor 是一个 C/S 架构的进程监控与管理工具,其最主要的特性是可以监控目标进程的运行状态,并在其异常中断时自动重启。同时支持对多个进程进行分组管理。
完整特性详见官方文档 github 与 document。
3.部署流程
3.1 安装 Supervisor
通过pip命令安装 Supervisor 工具:
pip install supervisor
PS : 根据官方文档的说明 Supervisor 不支持在 windows 环境下运行
3.2 自定义服务配置文件
在安装完成后,通过以下命令生成配置文件到指定路径:
echo_supervisord_conf > /etc/supervisord.conf
配置文件的一些主要配置参数如下
[unix_http_server]
file=/tmp/supervisor.sock ; the path to the socket file
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;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 ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10 ; # of main logfile backups; 0 means none, default 10
loglevel=info ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
[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 ; the program (relative uses PATH, can take args)
;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; the relative start priority (default 999)
;[include]
;files = relative/directory/*.ini
对于上述配置参数,可以按照具体的需求进行自定义,大多数参数可以保持默认设置。但是为了方便多个项目的统一管理,需要启用 [include] 参数。该参数用于将指定文件包含到配置中,通过这种方式来 "扩展" 服务配置文件。
创建配置目录,并修改 files 参数 :
mkdir /etc/supervisord.d
[include]
files = /etc/supervisord.d/*.ini
3.3 自定义应用配置文件
假设现在有一个测试项目 (test),里面有个 test.py 脚本需要持久化运行。现在切换到项目目录 (/root/test),并按照以下格式创建应用配置文件。
supervisor-{porject_name}.ini
配置项目的进程启动参数 :
; /root/test/supervisor-test.ini
[program:test]
command=python -u ./test.py ; 运行命令
directory=/root/test/ ; 运行目录
redirect_stderr=true ; 将 stderr 重定向到 stdout
stdout_logfile=/root/test/test.log ; 日志文件输出路径
将上述配置文件链接到服务配置文件中 [include] 参数设置的目录下 (或者复制):
ln ./supervisor-test.ini /etc/supervisord.d/supervisor-test.ini
需要注意的是,对于 supervisor 来说,上述服务配置文件和应用配置文件并没有直接区别。之所以将其划分成两类配置文件的目的在于当添加新项目时,不需要手动修改配置文件。
3.4 启动 supervisord 服务进程
supervisord 是 supervisor 的核心服务进程,通过配置文件中的参数来创建具体的子进程,并对其进行监控与管理。通过以下命令来启动:
supervisord
默认情况下,按照以下路径顺序查找并加载配置文件
-
../etc/supervisord.conf (Relative to the executable)
-
../supervisord.conf (Relative to the executable)
-
$CWD/supervisord.conf
-
$CWD/etc/supervisord.conf
-
/etc/supervisord.conf
-
/etc/supervisor/supervisord.conf (since Supervisor 3.3.0)
也可以通过 -c
参数来指定配置文件路径。
supervisord -c conf_file_path
3.5 启动 supervisorctl 客户端进程
supervisorctl 是 supervisor 的客户端进程,通过与 supervisord 服务进程建立 socket 连接来进行交互。使用以下命令进行交互式连接:
supervisorctl
成功连接后会显示当前运行的任务状态,或者使用 status
命令查看:
test RUNNING pid 2612, uptime 0:17:06
使用 tail -f test
来查看指定应用的日志输出:
1712051907.8820918
1712051908.8822799
1712051909.8824165
1712051910.8826928
...
PS : 使用 help
命令可以查看支持的所有操作。
3.6 验证 supervisor 的监控重启特性
文章开头描述了引入 supervisor 的主要目的,即通过监控目标进程的运行状态,并在其异常中断后自动重启来提高运行的稳定性,接下来就验证一下是否满足这个需求。
在此通过手动 kill 目标进程的方式来模拟异常中断。
(base) root:~/test# ps -ef | grep test
root 3359 2394 0 10:15 ? 00:00:00 python -u ./test.py
(base) root:~/test# kill -9 3359
(base) root:~/test# ps -ef | grep test
root 3472 2394 1 10:16 ? 00:00:00 python -u ./test.py
通过上述测试可以看到,当手动 kill 掉目标进程后,supervisor 又自动重启了目标进程 (pid 发生了变化)。
要主动退出目标进程,可以通过以下命令实现:
supervisorctl stop test
4. 高级特性
4.1 进程组管理
对于大多数项目,通常会包含多个进程,supervisor 支持将多个进程组成一个 进程组 来进行统一管理。
通过添加 [group:thegroupname] 参数并设置 programs 字段来设置进程组。
[group:test]
programs=test-task_service, test-collector
[program:test-task_service]
command=python -u ./task_service.py
directory=/root/test/
[program:test-collector]
command=python -u ./collector.py
directory=/root/test/
进入 supervisor 并使用 update
命令后查看运行状态:
(base) root:~# supervisorctl
test:test-collector RUNNING pid 1133, uptime 0:02:40
test:test-task_service RUNNING pid 1359, uptime 0:00:01
在使用 restart
, start
, stop
等命令时,可以通过指定进程组名称来进行批量操作。
supervisor> stop test:
test:test-task_service: stopped
test:test-collector: stopped
PS: 进行进程组操作时需要加上 : 号,即 cmd groupname:
。
4.2 [program:x] 配置参数详解
-
command : 用于指定待运行的命令。
[program:test]
command=python -u /root/test/test.py
-
directory : 指定在执行 command 命令前切换的目录,当 command 使用相对路径时,可以与该参数配合使用。
[program:test]
command=python -u ./test.py
directory=/root/test
-
numprocs : 用于指定运行时的进程实例数量,需要与 process_name 参数配合使用。
[program:test]
command=python -u /root/test/test.py
process_name=%(program_name)s_%(process_num)s
numprocs=3
supervisor> status
test:test_0 RUNNING pid 2463, uptime 0:00:02
test:test_1 RUNNING pid 2464, uptime 0:00:02
test:test_2 RUNNING pid 2465, uptime 0:00:02
-
autostart : 用于控制是否在 supervisord 进程启动时同时启动 (默认为 true)
[program:test1]
command=python -u /root/test/test.py
[program:test2]
command=python -u /root/test/test.py
autostart=false
supervisor> reload
Really restart the remote supervisord process y/N? y
Restarted supervisord
supervisor> status
test1 RUNNING pid 3253, uptime 0:00:02
test2 STOPPED Not started
-
stdout_logfile : 指定标准输出流的日志文件路径。
-
stdout_logfile_maxbytes : 单个日志文件的最大字节数,当超过该值时将对日志进行切分。
-
stdout_logfile_backups : 切分日志后保留的副本数,与 stdout_logfile_maxbytes 配合使用实现滚动日志效果。
-
redirect_stderr : 将 stderr 重定向到 stdout。
[program:test]
command=python -u /root/test/test.py
stdout_logfile=/root/test/test.log
stdout_logfile_maxbytes=1KB
stdout_logfile_backups=5
redirect_stderr=true
test.log
test.log.1
test.log.2
test.log.3
test.log.4
test.log.5
4.3 supervisorctl 命令详解
supervisorctl 支持的所有操作可以通过 help
命令来查看:
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
通过 help cmd
可以查看每个命令的意义和用法:
supervisor> help restart
restart <name> Restart a process
restart <gname>:* Restart all processes in a group
restart <name> <name> Restart multiple processes or groups
restart all Restart all processes
Note: restart does not reread config files. For that, see reread and update.
其中与 supervisord 服务进程相关的命令有:
-
open : 连接到远程 supervisord 进程。
-
reload : 重启 supervisord 进程。
-
shutdown : 关闭 supervisord 进程。
而以下命令则用于进行具体的应用进程管理:
-
status : 查看应用进程的运行状态。
-
start : 启动指定的应用进程。
-
restart : 重启指定的应用进程。
-
stop : 停止指定的应用进程。
-
signal : 向指定应用进程发送信号。
-
update : 重新加载配置参数,并根据需要重启应用进程。
4.4 应用进程的信号处理
在某些应用场景,需要在进程结束前进行一些处理操作,比如清理缓存,上传执行状态等。对于这种需求可以通过引入 signal 模块并注册相关处理逻辑,同时结合 supervisorctl 的 signal 命令来实现。
测试代码如下:
import time
import signal
# 运行标志
RUN = True
# 信号处理逻辑
def exit_handler(signum, frame):
print(f'processing signal({signal.Signals(signum).name})')
print("update task status")
print("clear cache data")
global RUN
RUN = False
# 注册信号
signal.signal(signal.SIGTERM, exit_handler)
# 模拟持久化行为
while RUN:
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))))
time.sleep(1)
print("exited")
上述代码在 signal.SIGTERM 信号上注册了一个处理函数,用来在退出之前处理相关逻辑。
通过 supervisorctl 的 signal 向目标进程发送 signal.SIGTERM(15) 信号。
supervisor> status
test RUNNING pid 2855, uptime 0:00:06
supervisor> signal 15 test
test: signalled
supervisor> status
test EXITED Apr 03 03:51 AM
可以看到目标进程正常退出了,再查看日志验证是否执行了 exit 函数的逻辑:
2024-04-03 03:51:34
2024-04-03 03:51:35
2024-04-03 03:51:36
2024-04-03 03:51:37
2024-04-03 03:51:38
processing signal(SIGTERM)
update task status
clear cache data
exited
日志的输出结果与代码的预期一致。
PS : stop test
与 signal 15 test
有相同的效果。
4.5 可视化操作模式
除了使用 supervisorctl 以交互式命令行终端的形式连接 supervisord 外,还支持以可视化 web 页面的方式来操作。修改 服务配置文件 (/etc/supervisord.conf) 并启用以下配置:
[inet_http_server]
port=0.0.0.0:9001
username=user
password=123
重启后访问 http://127.0.0.1:9001/ 输入认证密码后,可以看到以下页面:
PS : 根据配置文档中的警告,以这种模式启动时,应考虑安全问题,不应该把服务接口暴露到公网上。
综上所述,以上是对 Supervisor 的简单介绍与应用,除了上述介绍的基本用法和高级特性外,还支持以 RPC 的方式进行调用,但由于现阶段还未遇到相关的应用场景,因此考虑后续深度使用后再研究相关代码。