代码后台运行,不止nohup,还有Python Supervisor!
1. 概述
2. 问题场景
3. Supervisor 简介
Supervisor is a client/server system that allows its users to control a number of processes on UNIX-like operating systems.
4.部署流程
4.1. 安装 Supervisor
通过 pip 命令安装 Supervisor 工具:
pip install supervisor
4.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.loglogfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MBlogfile_backups=10 ; # of main logfile backups; 0 means none, default 10loglevel=info ; log level; default info; others: debug,warn,tracepidfile=/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
👉点击领取:最全Python资料合集
创建配置目录,并修改 files 参数 :
mkdir /etc/supervisord.d
[include]files = /etc/supervisord.d/*.ini
4.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 重定向到 stdoutstdout_logfile=/root/test/test.log ; 日志文件输出路径
将上述配置文件链接到服务配置文件中 [include] 参数设置的目录下 (或者复制): ln ./supervisor-test.ini /etc/supervisord.d/supervisor-test.ini
4.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
4.5. 启动 supervisorctl 客户端进程
supervisorctl 是 supervisor 的客户端进程,通过与 supervisord 服务进程建立 socket 连接来进行交互。使用以下命令进行交互式连接:
supervisorctl
成功连接后会显示当前运行的任务状态,或者使用 status 命令查看:
test RUNNING pid 2612, uptime 0:17:06
使用 tail -f test 来查看指定应用的日志输出:
1712051907.88209181712051908.88227991712051909.88241651712051910.8826928...
4.6. 验证 supervisor 的监控重启特性
在此通过手动 kill 目标进程的方式来模拟异常中断。
(base) root:~/test# ps -ef | grep testroot 3359 2394 0 10:15 ? 00:00:00 python -u ./test.py(base) root:~/test# kill -9 3359(base) root:~/test# ps -ef | grep testroot 3472 2394 1 10:16 ? 00:00:00 python -u ./test.py
要主动退出目标进程,可以通过以下命令实现:
supervisorctl stop test
5. 高级特性
5.1. 进程组管理
通过添加 [group:thegroupname] 参数并设置 programs 字段来设置进程组。
[group:test]programs=test-task_service, test-collector[program:test-task_service]command=python -u ./task_service.pydirectory=/root/test/[program:test-collector]command=python -u ./collector.pydirectory=/root/test/
进入 supervisor 并使用 update 命令后查看运行状态:
(base) root:~# supervisorctltest:test-collector RUNNING pid 1133, uptime 0:02:40test:test-task_service RUNNING pid 1359, uptime 0:00:01
在使用 restart, start, stop 等命令时,可以通过指定进程组名称来进行批量操作。
supervisor> stop test:test:test-task_service: stoppedtest:test-collector: stopped
5.2. [program:x] 配置参数详解
command : 用于指定待运行的命令。
[program:test]command=python -u /root/test/test.py
directory : 指定在执行 command 命令前切换的目录,当 command 使用相对路径时,可以与该参数配合使用。
[program:test]command=python -u ./test.pydirectory=/root/test
numprocs : 用于指定运行时的进程实例数量,需要与 process_name 参数配合使用。
[program:test]command=python -u /root/test/test.pyprocess_name=%(program_name)s_%(process_num)snumprocs=3
supervisor> statustest:test_0 RUNNING pid 2463, uptime 0:00:02test:test_1 RUNNING pid 2464, uptime 0:00:02test: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.pyautostart=false
supervisor> reloadReally restart the remote supervisord process y/N? yRestarted supervisordsupervisor> statustest1 RUNNING pid 3253, uptime 0:00:02test2 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.pystdout_logfile=/root/test/test.logstdout_logfile_maxbytes=1KBstdout_logfile_backups=5redirect_stderr=true
test.logtest.log.1test.log.2test.log.3test.log.4test.log.5
5.3. supervisorctl 命令详解
supervisorctl 支持的所有操作可以通过 help 命令来查看:
supervisor> helpdefault commands (type help <topic>):=====================================add exit open reload restart start tailavail fg pid remove shutdown status updateclear maintail quit reread signal stop version
通过 help cmd 可以查看每个命令的意义和用法:
supervisor> help restartrestart <name> Restart a processrestart <gname>:* Restart all processes in a grouprestart <name> <name> Restart multiple processes or groupsrestart all Restart all processesNote: restart does not reread config files. For that, see reread and update.
open : 连接到远程 supervisord 进程。 reload : 重启 supervisord 进程。 shutdown : 关闭 supervisord 进程。
status : 查看应用进程的运行状态。 start : 启动指定的应用进程。 restart : 重启指定的应用进程。 stop : 停止指定的应用进程。 signal : 向指定应用进程发送信号。 update : 重新加载配置参数,并根据需要重启应用进程。
5.4. 应用进程的信号处理
测试代码如下:
import timeimport 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 RUNRUN = 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")
通过 supervisorctl 的 signal 向目标进程发送 signal.SIGTERM(15) 信号。
supervisor> statustest RUNNING pid 2855, uptime 0:00:06supervisor> signal 15 testtest: signalledsupervisor> statustest EXITED Apr 03 03:51 AM
可以看到目标进程正常退出了,再查看日志验证是否执行了 exit 函数的逻辑:
2024-04-03 03:51:342024-04-03 03:51:352024-04-03 03:51:362024-04-03 03:51:372024-04-03 03:51:38processing signal(SIGTERM)update task statusclear cache dataexited
5.5. 可视化操作模式
除了使用 supervisorctl 以交互式命令行终端的形式连接 supervisord 外,还支持以可视化 web 页面的方式来操作。修改 服务配置文件 (/etc/supervisord.conf) 并启用以下配置:
[inet_http_server]port=0.0.0.0:9001username=userpassword=123
6. 自动重启机制的简单分析
控制自动重启机制的关键参数是:
autorestart=unexpected
autorestart=unexpected : 这是默认选项,当目标进程 “异常” 退出时,服务进程将自动重启目标进程,这里的 “异常” 指的是目标进程的 exitcode 与 exitcodes 配置的参数不一致时。_exitcodes_ 配置用于指定程序 “预期” 的退出代码列表,默认为 exitcodes=0。
用以下代码来进行测试行为:
import timedef worker(end_count=5, exit_mode=1):count = 0while True:print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))))time.sleep(1)if count >= end_count:if exit_mode == 1:breakelif exit_mode == 2:raise Exception("test")elif exit_mode == 3:exit(1)else:passcount += 1worker(exit_mode=1)# worker(exit_mode=2)# worker(exit_mode=3)# worker(exit_mode=4)
exit_mode == 1 : 通过 break 跳出循环正常结束
supervisor> statustest RUNNING pid 5965, uptime 0:00:05supervisor> statustest EXITED Apr 03 08:59 AM
exit_mode == 2 : 通过 Exception 抛出异常,模拟内部异常导致的退出。
supervisor> statustest RUNNING pid 6056, uptime 0:00:05supervisor> statustest STARTINGsupervisor> statustest RUNNING pid 6103, uptime 0:00:02
exit_mode == 3 : 通过 exit(1) 方法返回与 exitcodes=0 不一致的退出代码来测试。
supervisor> statustest RUNNING pid 6209, uptime 0:00:05supervisor> statustest STARTINGsupervisor> statustest RUNNING pid 6240, uptime 0:00:01
exit_mode == 4 : 通过手动 kill 目标进程来测试,发现与上述结果一致。
autorestart=true : 当使用这种模式时,就算程序正常退出也会自动重启。 autorestart=false : 当使用这种模式时,将停用自动重启机制。
自动重启机制的相关源码片段如下:
# supervisor.process@functools.total_orderingclass Subprocess(object):...def transition(self):now = time.time()state = self.stateself._check_and_adjust_for_system_clock_rollback(now)logger = self.config.options.loggerif self.config.options.mood > SupervisorStates.RESTARTING:# dont start any processes if supervisor is shutting downif state == ProcessStates.EXITED:if self.config.autorestart:if self.config.autorestart is RestartUnconditionally:# EXITED -> STARTINGself.spawn()else: # autorestart is RestartWhenExitUnexpectedif self.exitstatus not in self.config.exitcodes:# EXITED -> STARTINGself.spawn()elif state == ProcessStates.STOPPED and not self.laststart:if self.config.autostart:# STOPPED -> STARTINGself.spawn()elif state == ProcessStates.BACKOFF:if self.backoff <= self.config.startretries:if now > self.delay:# BACKOFF -> STARTINGself.spawn()...
这意味着就算目标进程是正常退出的 (exitcodes=0),如果其运行时间小于设置的参数,也会判断成失败。
import timeprint(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))))
supervisor> statustest BACKOFF Exited too quickly (process log may have details)supervisor> statustest STARTINGsupervisor> statustest FATAL Exited too quickly (process log may have details)
import timeprint(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))))time.sleep(3)
supervisor> statustest RUNNING pid 7049, uptime 0:00:02supervisor> statustest EXITED Apr 03 09:41 AM
startretries 参数需要与 startsecs 参数配合使用,用于控制目标进程的重启尝试次数,并且每次重试花费的时间间隔越来越长。可以通过以下代码测试一下:
startsecs=1startretries=5
import timeprint(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))))
测试代码的输出结果如下,可以看到每次重试的间隔时间呈 1, 2, 3, ... 增长。
2024-04-03 09:59:202024-04-03 09:59:212024-04-03 09:59:232024-04-03 09:59:262024-04-03 09:59:302024-04-03 09:59:35
7. 总结
热门推荐