Supervisor 进程管理工具

简介

Supervisor 是用 Python 开发的一个 C/S 架构的进程管理工具,只可以用 Linux/Unix 系统下。在 Linux 系统上,通常的做法是为每个需要控制的服务编写 rc.d 脚本,之后通过 service 或者 systemctl 来管理和控制服务,但是在程序崩溃的时候却无法自动重启项目。Supervisor 将它管理的服务作为它的子进程,所以可以简单且精确的管理和控制这些服务。官方文档

环境

Supervisor 仅支持 Linux/Unix 系统,推荐使用 Python 2.7 或 Python 3.4 之上的版本来运行 Supervisor。

系统环境

系统类型 发行商 版本号
Linux Ubuntu 18.04.3

软件环境

软件 版本
Python 3.6.8
Supervisor 4.0.4

教程

安装

Ubuntu 可以直接通过 apt install supervisor 进行安装,但是这样安装的版本不会是最新版,并且还会安装一些其他依赖。这里选择适用性更广的虚拟环境来安装。

1
2
3
4
5
6
apt install python3-venv -y     # 安装 python3 虚拟环境库
mkdir -p /data/venv # 创建一个目录供放置虚拟环境
cd /data/venv
python3 -m venv supervisor # 创建一个叫 supervisor 的虚拟环境
source /data/venv/supervisor/bin/activate
pip install supervisor

安装好之后,执行 supervisord -v 可以看到当前安装的版本号。

(supervisor) root@ubuntu:/data/venv# supervisord -v
4.0.4
(supervisor) root@ubuntu:/data/venv#

快速开始

接下来演示 Supervisor 最基本的使用

提供一个配置文件

Supervisor 会首先在当前目录查找 supervisord.conf,如果没有找到 则会使用 /etc/supervisord.conf,如果此文件依然不存在,则会使用程序内置的配置。或者,我们可以用 -c 参数,来为 supervisor 指明一个配置文件。为了让进程运行的更清晰,建议通过 -c 参数来指明配置文件。

接下来就是提供一个配置文件给 supervisor。supervisor 提供了一个 echo_supervisord_conf 的命令,此命令的执行会打印一个 supervisor 的模板示例。

1
echo_supervisord_conf

以下是打印的配置文件示例内容:

点击显示/隐藏代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
; - Shell expansion ("~" or "$HOME") is not supported. Environment
; variables can be expanded using this syntax: "%(ENV_HOME)s".
; - Quotes around values are not supported, except in the case of
; the environment= options as shown below.
; - Comments must have a leading space: "a=b ;comment" not "a=b;comment".
; - Command will be truncated if it looks like a config file comment, e.g.
; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ".
;
; Warning:
; Paths throughout this example file use /tmp because it is available on most
; systems. You will likely need to change these to locations more appropriate
; for your system. Some systems periodically delete older files in /tmp.
; Notably, if the socket file defined in the [unix_http_server] section below
; is deleted, supervisorctl will be unable to connect to supervisord.

[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)

; Security Warning:
; The inet HTTP server is not enabled by default. The inet HTTP server is
; enabled by uncommenting the [inet_http_server] section below. The inet
; HTTP server is intended for use within a trusted environment only. It
; should only be bound to localhost or only accessible from within an
; isolated, trusted network. The inet HTTP server does not support any
; form of encryption. The inet HTTP server does not use authentication
; by default (see the username= and password= options to add authentication).
; Never expose the inet HTTP server to the public internet.

;[inet_http_server] ; inet (TCP) server disabled by default
;port=127.0.0.1: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 ; 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
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 section must remain in the config file for
; RPC (supervisorctl/web interface) to work. Additional interfaces may be
; added by defining them in separate [rpcinterface:x] sections.

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; The supervisorctl section configures how supervisorctl will connect to
; supervisord. configure it match the settings in either the unix_http_server
; or 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

; The sample program section below shows all possible program subsection values.
; Create one or more 'real' program: sections to be able to control them under
; supervisor.

;[program:theprogramname]
;command=/bin/cat ; 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)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=999 ; the relative start priority (default 999)
;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 ; when to restart 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=true ; redirect proc stderr to stdout (default 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)

; The sample group section below shows all possible group values. Create one
; or more 'real' group: sections to create "heterogeneous" process groups.

;[group:thegroupname]
;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions
;priority=999 ; the relative start priority (default 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 = relative/directory/*.ini

为了方便查看配置,这里只写入没有被注释掉的配置项到配置文件:

1
2
echo_supervisord_conf |grep -v '^;' |uniq > /etc/supervisord.conf
cat /etc/supervisord.conf

以下是配置文件内容:

[unix_http_server]
file=/tmp/supervisor.sock   ; the path to the socket file

[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
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

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket

启动 Supervisord

如果已经进入 supervisor 的虚拟环境,可以直接使用 supervisord 命令启动:

1
supervisord -c /etc/supervisord.conf

或者没有进入虚拟环境的话,可以使用 supervisord 命令的绝对路径启动:

1
/data/venv/supervisor/bin/supervisord -c /etc/supervisord.conf

可以看到 supervisord 已经运行起来了:

(supervisor) root@ubuntu:~# pgrep supervisord 
1990
(supervisor) root@ubuntu:~# ls /tmp/
supervisor.sock  supervisord.log  supervisord.pid
(supervisor) root@ubuntu:~#

管理一个进程

接下来为 supervisor 添加被它管理的第一个进程。将下面配置项追加到 /etc/supervisord.conf

[program:pyhttp]
command=/usr/bin/python3 -m http.server 8080
directory=/tmp/
stdout_logfile=/tmp/pyhttp.log
autostart=true
autorestart=true

接着执行 supervisorctl 命令,这个命令是 supervisord 的命令行管理工具,也就是 C/S 架构中的 C 了。进入 supervisorctl 交互命令行后,执行 update 命令会通知 supervisord 进程更新进程管理相关的配置项,这样刚才添加的配置项就生效了:

(supervisor) root@ubuntu:~# supervisorctl -c /etc/supervisord.conf
supervisor> update
pyhttp: added process group
supervisor> status
pyhttp                           RUNNING   pid 2107, uptime 0:00:11
supervisor>

使用 status 命令可以查看当前被 supervisord 管理的所有进程状态,exit 命令可以退出交互模式。

注意: 也可以直接使用 supervisorctl update 或者 supervisorctl status 来达到相同的效果而不需要进入 supervisorctl 的交互模式。
运行 supervisorctl 命令也应当指定 supervisord 所使用的配置文件。

进程控制

通过 stopstartrestart 命令可以对进程进行 停止启动重启 的操作:

(supervisor) root@ubuntu:~# supervisorctl -c /etc/supervisord.conf
pyhttp                           RUNNING   pid 2107, uptime 0:09:31
supervisor> stop pyhttp 
pyhttp: stopped
supervisor> status
pyhttp                           STOPPED   Aug 28 06:00 AM
supervisor> start pyhttp
pyhttp: started
supervisor> restart pyhttp
pyhttp: stopped
pyhttp: started
supervisor> status
pyhttp                           RUNNING   pid 2135, uptime 0:00:03
supervisor> 

停止 Supervisord

在停止 supervisord 进程前,我们应该先停止 supervisord 管理的所有子进程,以免造成 supervisord 停止后,子进程没有主动停止被 init 进程接管的现象。下面演示下强制杀掉 supervisord 进程后造成子进程逃逸的现象:

(supervisor) root@ubuntu:~# netstat -anpt |grep python3
tcp    0    0 0.0.0.0:8080      0.0.0.0:*       LISTEN    2221/python3        
(supervisor) root@ubuntu:~# pgrep supervisord 
2191
(supervisor) root@ubuntu:~# cat /proc/2221/status |grep PPid
PPid:    2191
(supervisor) root@ubuntu:~# kill -9 2191
(supervisor) root@ubuntu:~# pgrep supervisord 
(supervisor) root@ubuntu:~# cat /proc/2221/status |grep PPid
PPid:    1
(supervisor) root@ubuntu:~# 

supervisor 提供了安全的方式关闭 supervisord 进程,在 supervisorctl 中使用 shutdown 命令即可:

(supervisor) root@ubuntu:~# supervisorctl -c /etc/supervisord.conf
pyhttp                           RUNNING   pid 2251, uptime 0:00:10
supervisor> shutdown 
Really shut the remote supervisord process down y/N? y
Shut down
supervisor> status
unix:///tmp/supervisor.sock no such file
supervisor> 
(supervisor) root@ubuntu:~# netstat -anpt |grep 8080
(supervisor) root@ubuntu:~#

使用 Web 管理

Supervisor 提供了一套简易的 Web 页面来远程管理进程,但是由于安全考虑默认是关闭的。将以下配置项加入配置文件则可以开启这个功能:

[inet_http_server]
port=0.0.0.0:9001
username=admin
password=123456

服务监听在 0.0.0.0:9001,登录用户名为 admin,密码为 123456。接着重启 supervisord 进程:

1
kill -HUP `pgrep supervisord`

向 supervisord 进程发送 HUP 信号会通知进程重新初始化,supervisord 会重新加载配置文件,已经启动的子进程会重启!还有一点需要注意,pgrep 命令会列出所有的 supervisord 进程,如果主机上正运行着多个 supervisord 实例,这会触发所有 supervisord 重新初始化!

(supervisor) root@ubuntu:~# netstat -anpt |grep python3
tcp    0   0 0.0.0.0:9001        0.0.0.0:*        LISTEN     2264/python3        
tcp    0   0 0.0.0.0:8080        0.0.0.0:*        LISTEN     2644/python3        
(supervisor) root@ubuntu:~# kill -HUP `pgrep supervisord`
(supervisor) root@ubuntu:~# netstat -anpt |grep python3
tcp    0   0 0.0.0.0:9001        0.0.0.0:*        LISTEN     2264/python3        
tcp    0   0 0.0.0.0:8080        0.0.0.0:*        LISTEN     2650/python3    

可以看到,当我又一次重启 supervisord 后,监听 8080 端口的 python3 进程的 PID 发生了变化,而 supervisord 的没有。

接着通过浏览器访问 supervisord 的 Web 页面:

我们可以通过 Web 页面对 supervisord 做一些简单的操作。

注意: 生产环境请勿开启此项,会有安全隐患。

命令行程序

supervisor 安装完毕后会提供四个命令行工具:supervisordsupervisorctlecho_supervisord_confpidproxy。下面对这些命令行工具进行详细说明。

supervisord

命令帮助

这个就是 supervisor 最重要的进程了,这个命令会在后台启动一个守护进程,对子进程的管理实际上就是通过这个进程。执行 supervisord --help 可以看到此命令的所有参数:

(supervisor) root@ubuntu:~# supervisord --help
supervisord -- run a set of applications as daemons.

Usage: /data/venv/supervisor/bin/supervisord [options]

Options:
-c/--configuration FILENAME -- configuration file path (searches if not given)
-n/--nodaemon -- run in the foreground (same as 'nodaemon=true' in config file)
-h/--help -- print this usage message and exit
-v/--version -- print supervisord version number and exit
-u/--user USER -- run supervisord as this user (or numeric uid)
-m/--umask UMASK -- use this umask for daemon subprocess (default is 022)
-d/--directory DIRECTORY -- directory to chdir to when daemonized
-l/--logfile FILENAME -- use FILENAME as logfile path
-y/--logfile_maxbytes BYTES -- use BYTES to limit the max size of logfile
-z/--logfile_backups NUM -- number of backups to keep when max bytes reached
-e/--loglevel LEVEL -- use LEVEL as log level (debug,info,warn,error,critical)
-j/--pidfile FILENAME -- write a pid file for the daemon process to FILENAME
-i/--identifier STR -- identifier used for this instance of supervisord
-q/--childlogdir DIRECTORY -- the log directory for child process logs
-k/--nocleanup --  prevent the process from performing cleanup (removal of
                old automatic child log files) at startup.
-a/--minfds NUM -- the minimum number of file descriptors for start success
-t/--strip_ansi -- strip ansi escape codes from process output
--minprocs NUM  -- the minimum number of processes available for start success
--profile_options OPTIONS -- run supervisord under profiler and output
                            results based on OPTIONS, which  is a comma-sep'd
                            list of 'cumulative', 'calls', and/or 'callers',
                            e.g. 'cumulative,callers')

(supervisor) root@ubuntu:~#

由于我们几乎不会通过命令行参数来运行 supervisord,所以各个参数的含义都不再说明,只需要知道通过 -c 或者 --configuration 来指明 supervisord 的配置文件即可。

信号处理

当 supervisord 进程接收到不同的信号时,会有不同的反应,我们在外部还可以通过信号来控制 supervisord 的一些行为:

  • SIGTERM:这个信号也是 kill 命令默认的信号,supervisord 会关闭所有子进程,然后退出。这可能需要几秒钟的时间。
  • SIGINT:相当于将 supervisord 的进程 Ctrl-C 掉,supervisord 的处理同对 SIGTERM 信号的处理。
  • SIGQUIT:同对 SIGTERM 信号的处理。
  • SIGHUP:supervisord 将停止所有进程,然后重新加载配置文件,并启动所有进程。
  • SIGUSR2:supervisord 将关闭并重新打开主日志文件和子日志文件。

supervisorctl

命令帮助

这个命令是 supervisord 守护进程的命令行管理工具,我们大多时候都是通过这个命令来管理 supervisord 的。下面是这个命令的帮助:

(supervisor) root@ubuntu:~# supervisorctl --help
supervisorctl -- control applications run by supervisord from the cmd line.

Usage: /data/venv/supervisor/bin/supervisorctl [options] [action [arguments]]

Options:
-c/--configuration FILENAME -- configuration file path (searches if not given)
-h/--help -- print usage message and exit
-i/--interactive -- start an interactive shell after executing commands
-s/--serverurl URL -- URL on which supervisord server is listening
    (default "http://localhost:9001").
-u/--username USERNAME -- username to use for authentication with server
-p/--password PASSWORD -- password to use for authentication with server
-r/--history-file -- keep a readline history (if readline is available)

action [arguments] -- see below

Actions are commands like "tail" or "stop".  If -i is specified or no action is
specified on the command line, a "shell" interpreting actions typed
interactively is started.  Use the action "help" to find out about available
actions.
连接 supervisord

supervisorctl 默认是通过 unix 套接字跟 supervisord 通信,也就是用之前使用 supervisorctl 的方式。如果 supervisord 开启了远程监听(web访问),则 supervisorctl 也可以远程连接其他主机的 supervisor,连接方式主要是以下两种:

(supervisor) root@ubuntu:~# supervisorctl -s http://10.0.1.15:9001 -u admin -p 123456 status
pyhttp                           RUNNING   pid 2728, uptime 0:00:48
(supervisor) root@ubuntu:~# supervisorctl -s http://10.0.1.15:9001 
Server requires authentication
Username:admin
Password:

pyhttp                           RUNNING   pid 2728, uptime 0:03:48
supervisor> 
(supervisor) root@ubuntu:~#

使用 -s 指定服务监听地址,-u 提供登录的用户名,-p 提供密码,如果是进入交互模式,没有在命令行提供用户名和密码,supervisorctl 则会询问。

控制命令

除了最常用的 stopstartrestart 命令,supervisorctl 还支持非常多的控制命令,通过 help 命令可以获取所有支持的命令:

(supervisor) root@ubuntu:~# supervisorctl -c /etc/supervisord.conf 
pyhttp                           RUNNING   pid 2728, uptime 0:36:59
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>

通过 help <command> 可以查看命令帮助:

supervisor> help help
help        Print a list of available actions
help <action>    Print help for <action>
supervisor>

下面对所以支持的命令进行一一介绍

update

重新读取配置文件

用法:

  • update 根据配置文件的改动添加或移除项目,将会重启或停止受影响的项目
  • update all 和 update 一样,根据配置文件的改动添加或移除项目,将会重启或停止受影响的项目
  • update <gname> [...] 更新指定的组,可以提供多个组名

示例:

为了演示效果,我们修改配置文件,添加一个进程,并为它们创建一个进程组。将以下配置项写入配置文件,并更新 supervisord:

[program:pyhttp1]
command=/usr/bin/python3 -m http.server 8081
directory=/tmp/
stdout_logfile=/tmp/pyhttp1.log

[program:pyhttp2]
command=/usr/bin/python3 -m http.server 8082
directory=/tmp/
stdout_logfile=/tmp/pyhttp2.log

[group:simpleHttp]
programs=pyhttp1,pyhttp2
1
supervisorctl -c /etc/supervisord.conf update
(supervisor) root@ubuntu:~# supervisorctl -c /etc/supervisord.conf update
simpleHttp: added process group

注意:update 方法不会影响没有修改配置的进程,所以重复执行也是安全的

status

查看进程的状态信息

用法:

  • status:获取所有进程状态信息
  • status <name>: 获取指定名称的进程状态信息
  • status <gname>:*: 获取指定进程组名称的所有进程信息
  • status <name> <name>: 获取指定的多个名称的进程信息

示例:

(supervisor) root@ubuntu:~# supervisorctl -c /etc/supervisord.conf 
pyhttp                           RUNNING   pid 2851, uptime 0:02:20
simpleHttp:pyhttp1               RUNNING   pid 2860, uptime 0:00:29
simpleHttp:pyhttp2               RUNNING   pid 2861, uptime 0:00:29
supervisor> status pyhttp 
pyhttp                           RUNNING   pid 2851, uptime 0:02:25
supervisor> status simpleHttp:*
simpleHttp:pyhttp1               RUNNING   pid 2860, uptime 0:00:39
simpleHttp:pyhttp2               RUNNING   pid 2861, uptime 0:00:39
supervisor>
stop

停止进程

用法:

  • stop <name> 停止指定名称的进程
  • stop <gname>:* 停止指定进程组内的所有进程
  • stop <name> <name> 停止指定的多个进程或进程组
  • stop all 停止所有进程

示例:

supervisor> stop pyhttp
pyhttp: stopped
supervisor> stop simpleHttp:*
simpleHttp:pyhttp1: stopped
simpleHttp:pyhttp2: stopped
supervisor> status
pyhttp                           STOPPED   Aug 28 01:23 PM
simpleHttp:pyhttp1               STOPPED   Aug 28 01:24 PM
simpleHttp:pyhttp2               STOPPED   Aug 28 01:24 PM
supervisor>
start

启动进程

用法:

  • start <name> 启动指定名称的进程
  • start <gname>:* 启动指定进程组内的所有进程
  • start <name> <name> 启动指定的多个进程或进程组
  • start all 启动所有进程

示例:

supervisor> start pyhttp
pyhttp: started
supervisor> start all
simpleHttp:pyhttp1: started
simpleHttp:pyhttp2: started
supervisor> status
pyhttp                           RUNNING   pid 3351, uptime 0:00:06
simpleHttp:pyhttp1               RUNNING   pid 3352, uptime 0:00:03
simpleHttp:pyhttp2               RUNNING   pid 3353, uptime 0:00:03
supervisor>
restart

重启进程

用法:

  • restart <name> 重启指定名称的进程
  • restart <gname>:* 重启指定进程组内的所有进程
  • restart <name> <name> 重启指定的多个进程或进程组
  • restart all 重启所有进程

示例:

supervisor> restart all
pyhttp: stopped
simpleHttp:pyhttp1: stopped
simpleHttp:pyhttp2: stopped
pyhttp: started
simpleHttp:pyhttp1: started
simpleHttp:pyhttp2: started
supervisor>
pid

获取 PID

用法:

  • pid 获取 supervisord 的 PID
  • pid <name> 获取指定名称的进程的 PID
  • pid all 获取所有进程的 PID

示例:

supervisor> pid all
3357
3358
3359
supervisor> pid
2727
supervisor> pid pyhttp 
3357
supervisor> 
reload

通知 supervisord 进程重新重新初始化。会关闭所有进程,然后重新读取配置文件后启动进程

用法:

  • reload 重启远程的 supervisord

此命令类似于直接向 supervisord 进程发送 HUP 信号一样,会通知 supervisord 进程重新初始化。为了演示效果,我们修改下进程组 simpleHttp 的成员,将 pyhttp2 换成 pyhttp

[group:simpleHttp]
programs=pyhttp1,pyhttp

示例:

supervisor> reload
Really restart the remote supervisord process y/N? y
Restarted supervisord
supervisor> status
pyhttp2                          RUNNING   pid 3502, uptime 0:00:07
simpleHttp:pyhttp                RUNNING   pid 3504, uptime 0:00:07
simpleHttp:pyhttp1               RUNNING   pid 3503, uptime 0:00:07
supervisor>

注意:生产环境应该尽量避免执行这个命令,因为会重启所有的进程,除非在修改了 supervisord 服务本身的配置项的时候,比如修改了 supervisord 服务监听地址的时候,才需要使用 reload 命令,修改了项目相关配置项的时候 应当使用 update 代替。关于 supervisor 的配置文件,下面会有详细说明。

remove

移除一个已停止的进程,相当于禁用

用法:

  • remove <name> [...] 从当前活跃的配置中,移除指定的进程名或进程组

示例:

supervisor> stop pyhttp2
pyhttp2: stopped
supervisor> remove pyhttp2 
pyhttp2: removed process group
supervisor> status
simpleHttp:pyhttp                RUNNING   pid 3510, uptime 0:05:42
simpleHttp:pyhttp1               RUNNING   pid 3509, uptime 0:05:42
supervisor> 

必须要先停止要移除的进程,移除后再执行 status 被移除的进程就不可见了。

avail

显示所有已配置的进程

用法:

  • avail 显示所有已配置的进程

示例:

supervisor> avail 
simpleHttp:pyhttp                in use    auto      999:999
simpleHttp:pyhttp1               in use    auto      999:999
pyhttp2                          avail     auto      999:999
supervisor>

可以看到所有的进程,包括正在使用的,和可用的。

add

添加一个被移除后的进程,相当于启用

用法:

  • add <name> [...] 从已移除的项目中,重新启用指定的进程名或进程组

示例:

supervisor> add pyhttp2
pyhttp2: added process group
supervisor> status
pyhttp2                          RUNNING   pid 3613, uptime 0:00:07
simpleHttp:pyhttp                RUNNING   pid 3510, uptime 0:11:37
simpleHttp:pyhttp1               RUNNING   pid 3509, uptime 0:11:37
supervisor>

注意:用 update 也会使已禁用的进程重新启用。但是和 add 还是有本质上的区别,如果禁用这个进程后,修改了这个进程的配置文件,用 add 只能启用未修改前这个进程的配置,而 update 则会重新读取新的配置。

reread

重新读取项目的配置文件

用法:

  • reread 重新加载配置文件,但是不会自动执行 add 和 remove

也就是说,reread 会比 update 柔和,当修改了正在运行的进程的配置项后,它也不会停止被修改的进程。即使添加了新的进程配置项,它也不会主动启动新进程。现在我们修改配置文件,将 pyhttp2 的名称改为 pyhttp3

[program:pyhttp3]
command=/usr/bin/python3 -m http.server 8082
directory=/tmp/
stdout_logfile=/tmp/pyhttp2.log

示例:

supervisor> reread
pyhttp2: disappeared
pyhttp3: available
supervisor> status
pyhttp2                          RUNNING   pid 3616, uptime 0:01:12
simpleHttp:pyhttp                RUNNING   pid 3510, uptime 0:24:57
simpleHttp:pyhttp1               RUNNING   pid 3509, uptime 0:24:57
supervisor> avail 
simpleHttp:pyhttp                in use    auto      999:999
simpleHttp:pyhttp1               in use    auto      999:999
pyhttp3                          avail     auto      999:999
supervisor>

可以看到,pyhttp2 进程依然在运行,pyhttp3 只存在于 avail 中。接下来复原配置文件,并执行 update :

[program:pyhttp2]
command=/usr/bin/python3 -m http.server 8082
directory=/tmp/
stdout_logfile=/tmp/pyhttp2.log
tail

显示进程的标准输出或错误输出,用法类似于 linux 的 tail 命令

命令格式:tail [-f] <name> [stdout|stderr] (default stdout)

用法:

  • tail -f <name> 连续的获取指定进程名的标准输出,Ctrl-C 退出。
  • tail -100 <name> 获取指定进程名的最后 100 个字节的标准输出。
  • tail <name> stderr 获取指定进程名的最后 1600 个字节的错误输出。

由于 supervisord 没有获取到 python -m http.server 的输出流,所以 tail 命令看不到任何数据,具体原因我也懒得排查了。。。

maintail

显示 supervisord 进程的日志,用法和 tail 命令类似

用法:

  • maintail -f 连续的获取 supervisord 的日志,Ctrl-C 退出。
  • maintail -100 获取 supervisord 的日志的最后 100 个字节。
  • maintail last 获取 supervisord 的日志的最后 1600 个字节。

示例:

supervisor> maintail -100 
INFO success: pyhttp2 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
clear

清理进程日志

用法:

  • clear <name> 清理指定进程名的日志文件。
  • clear <name> <name> 清理指定的多个进程名的日志文件
  • clear all 清理所有进程的日志文件。

示例:

supervisor> clear all
simpleHttp:pyhttp1: cleared
simpleHttp:pyhttp: cleared
pyhttp2: cleared
supervisor>
version

显示 supervisord 的版本

用法:

  • version 显示远程 supervisord 进程的版本。

示例:

supervisor> version
4.0.4
supervisor>
fg

在前台模式连接一个进程,本质上是连接到了进程的标准输入

用法:

  • fg <process> 在前台模式连接一个进程

首先我们需要先编写一个可以获取标准输入的程序,然后才可以用 fg 命令来做测试。

编辑 /root/fgtest.py 写入以下内容:

1
2
while True:
print("hello ", input())

编辑 /etc/supervisord.conf 添加如下内容:

[program:fgtest]
command=/usr/bin/python3 /root/fgtest.py
stdout_logfile=/tmp/fgtest.log

接着更新 supervisord,并测试 fg 命令:

supervisor> update
fgtest: added process group
supervisor> status
fgtest                           RUNNING   pid 3722, uptime 0:00:06
pyhttp2                          RUNNING   pid 3699, uptime 0:03:17
simpleHttp:pyhttp                RUNNING   pid 3701, uptime 0:03:17
simpleHttp:pyhttp1               RUNNING   pid 3700, uptime 0:03:17
supervisor> fg fgtest 
==> Press Ctrl-C to exit <==
aaa
hello  aaa
Exiting foreground
supervisor> tail fgtest 
hello  aaa

supervisor>

的确如我们所愿,当我们输入了 aaa,进程成功的回显了 hello aaa,并且 tail 命令也正确的输出了结果。

signal

给子进程发送信号,类似于 linux 的 kill 命令

用法:

  • signal <signal name> <name> 向指定的进程名发送指定的信号
  • signal <signal name> <gname>:* 向指定的进程组发送指定的信号
  • signal <signal name> <name> <name> 向指定的多个进程名发送指定的信号
  • signal <signal name> all 向所有进程发送指定的信号

linux 可用的信号如下所示:

root@ubuntu:~# kill -l
1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
2) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
3)  SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
4)  SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
5)  SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
6)  SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
7)  SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
8)  SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
9)  SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
10) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
11) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
12) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
13) SIGRTMAX-1    64) SIGRTMAX    

我们前面已经了解到,可以通过给 supervisord 发送 HUP(1) 信号来通知进程重新初始化。平常我们通过 Ctrl-C 来终止程序本质上就是发送 INT(2) 信号给进程。以及最常用的也是 kill 命令默认发出的 TERM(15) 信号,还有六亲不认的 KILL(9) 信号。

示例:

supervisor> status fgtest 
fgtest                           RUNNING   pid 3737, uptime 0:00:36
supervisor> signal INT fgtest 
fgtest: signalled
supervisor> status fgtest 
fgtest                           RUNNING   pid 3738, uptime 0:00:03
supervisor> signal 9 fgtest 
fgtest: signalled
supervisor> status fgtest 
fgtest                           STARTING  
supervisor> status fgtest 
fgtest                           RUNNING   pid 3739, uptime 0:00:03
supervisor> 

我们可以通过信号的名称,或者信号的编号来用作发送给进程,可以看到,给进程发送了终止,或者强制杀死的信号后,supervisord 把挂掉的进程重启了!这也是 supervisord 的特性之一。

help

查看帮助

用法:

  • help 列出所有的可用命令
  • help <command> 查看指定的命令的帮助

示例:

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> help start
start <name>        Start a process
start <gname>:*        Start all processes in a group
start <name> <name>    Start multiple processes or groups
start all        Start all processes
supervisor> 
open

连接远程的 supervisord 进程,可以通过 unix 套接字,也可以通过 http 协议。

用法:

  • open <url> 连接远程的 supervisord 进程。

示例:

supervisor> open http://127.0.0.1:9001
Server requires authentication
Username:admin
Password:

pyhttp2                          RUNNING   pid 3616, uptime 0:33:55
simpleHttp:pyhttp                RUNNING   pid 3510, uptime 0:57:40
simpleHttp:pyhttp1               RUNNING   pid 3509, uptime 0:57:40
supervisor> open unix:///tmp/supervisor.sock
pyhttp2                          RUNNING   pid 3616, uptime 0:34:58
simpleHttp:pyhttp                RUNNING   pid 3510, uptime 0:58:43
simpleHttp:pyhttp1               RUNNING   pid 3509, uptime 0:58:43
supervisor>
quit

退出当前 supervisorctl 交互界面。

用法:

  • quit 退出 supervisorctl 交互。

示例:

supervisor> quit

(supervisor) root@ubuntu:~#
exit

退出当前 supervisorctl 交互界面,同 quit。

用法:

  • exit 退出 supervisorctl 交互。

示例:

supervisor> exit

(supervisor) root@ubuntu:~#
shutdown

关闭 supervisord 以及所有正在被管理的子进程。

用法:

  • shutdown 关闭远程 supervisord 进程。

示例:

supervisor> shutdown 
Really shut the remote supervisord process down y/N? y
Shut down
supervisor>
supervisor> status
unix:///tmp/supervisor.sock no such file
supervisor>

echo_supervisord_conf

这个命令很简单,就是打印 supervisord.conf 的模板示例。

pidproxy

对于一些启动比较特殊的服务,例如 mysqld,当我们启动 mysqld 的时候,通常执行的是 mysqld_safe 脚本,然后它再去调用 mysqld 程序来启动服务。

supervisord 是通过给进程发送信号的方式来关闭程序的,但是 supervisord 只能获取 mysqld_safe 的 PID,而 mysqld_safe 会忽略进程退出相关的信号,只有它的子进程 mysqld 才会处理信号,所以如果使用 supervisord 来管理 mysqld,就需要让 supervisord 知道 mysqld 进程的实际 PID。

幸运的是,大多数进程启动后,都会创建一个 .pid 的文件,文件里保存进程的实际 PID,pidproxy 可以帮助我们启动进程后,根据提供的 .pid 文件位置来获取实际要控制的进程。

用法:

  • pidproxy <pidfile name> <command> [<cmdarg1> ...]

示例:

[program:mysqld] 
command = /data/venv/supervisor/bin/pidproxy /var/run/mysqld.pid mysqld_safe

配置文件详解

默认配置文件查找

supervisord 和 supervisorctl 共同使用 supervisord.conf 这个配置文件,如果没有使用 -c 参数指定配置文件的话,程序将依次从以下位置查找并使用找到的第一个配置文件:

  1. ./supervisord.conf 当前用户所在路径下的 supervisord.conf 文件。
  2. ./etc/supervisord.conf 当前用户所在路径下的 etc/supervisord.conf 文件。
  3. /etc/supervisord.conf 系统绝对路径 /etc/supervisord.conf 文件。
  4. /etc/supervisor/supervisord.conf 系统绝对路径,但是从 3.3.0 版本才开始支持。
  5. ../etc/supervisord.conf 相对于程序所在路径的 ../etc/supervisord.conf。
  6. ../supervisord.conf 相对于程序所在路径的 ../supervisord.conf。

配置文件格式

supervisord.conf 使用 Windows-INI 风格的配置文件。由不同的配置单元,配置单元中通过键值对的方式记录配置信息,格式如下:

1
2
3
4
5
6
7
8
9
[program:pyhttp1]
command=/usr/bin/python3 -m http.server 8081
directory=/tmp/
stdout_logfile=/tmp/pyhttp1.log

[program:pyhttp2]
command=/usr/bin/python3 -m http.server 8082
directory=/tmp/
stdout_logfile=/tmp/pyhttp2.log

每一个 [] 开始都表示一个配置单元,直到下一个配置单元为止。在 supervisord.conf 中,配置单元中的值还可以使用 %(ENV_ENVNAME)s 的方式。supervisord 启动的时候,会将其中的值根据对应的环境变量替换成实际的值,例如在配置文件中使用了 %(ENV_ENVNAME)s,则对应环境变量 ENVNAME 的值会替换了配置文件中变量的部分。下面举个例子来看:

配置文件中追加新的配置项:

[program:envtest]
command=/usr/bin/python3 -m http.server 8083
stdout_logfile=/tmp/%(ENV_LOGFILE)s

因为通过环境变量传递值给 supervisord.conf 的方式,必须保证 supervisord 进程可以继承到这些环境变量,我们在添加 LOGFILE 这个环境变量后必须重新启动 supervisord 进程才可以

(supervisor) root@ubuntu:~# supervisorctl shutdown
Shut down
(supervisor) root@ubuntu:~# env LOGFILE="log_from_env.log" supervisord -c /etc/supervisord.conf
(supervisor) root@ubuntu:~# supervisorctl status
envtest                          RUNNING   pid 4944, uptime 0:00:04
fgtest                           RUNNING   pid 4945, uptime 0:00:04
pyhttp2                          RUNNING   pid 4946, uptime 0:00:04
simpleHttp:pyhttp                RUNNING   pid 4948, uptime 0:00:04
simpleHttp:pyhttp1               RUNNING   pid 4947, uptime 0:00:04
(supervisor) root@ubuntu:~# ls /tmp/log_from_env.log 
/tmp/log_from_env.log
(supervisor) root@ubuntu:~#

我们配置了一个只针对 supervisord 进程生效的环境变量 LOGFILE,可以看到 log_from_env.log 文件被成功读取并替换到配置文件中。

配置单元

下面详细讲解 supervisord.conf 都支持哪些配置项。

[unix_http_server]

该配置项用于定义 supervisord 监听 UNIX 套接字提供 HTTP/XML-RPC 服务的相关配置。

file

supervisord 将监听 UNIX 套接字的路径。

默认值:无
是否必填:否
例子:file = /tmp/supervisor.sock

chmod

设置 UNIX 套接字文件的权限。

默认值:0700
是否必填:否
例如:chmod = 0777

chown

设置 UNIX 套接字文件的属主和属组。可以是用户名,也可以用冒号分割用户名和组 例如:nobody:nogroup

默认值:默认使用启动 supervisord 进程的用户名和组。
是否必填:否
例如:chown= nobody:nogroup

username

HTTP/XML-RPC 服务的认证用户名,如果提供,supervisorctl 则需要用户认证才可连接。

默认值:无
是否必填:否
例如:username = admin

password

HTTP/XML-RPC 服务的认证密码。可以是明文密码,也可以使用密码经过 sha-1 哈希后的值,需要用 {SHA} 标识是一个哈希串,例如: {SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d

默认值:无
是否必填:否
例如:password = password 或者 password = {SHA}5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8

[inet_http_server]

该项用于定义 supervisord 监听 TCP 套接字提供 HTTP/XML-RPC 服务的相关配置。启动此监听后 supervisord 可以通过网络来管理和控制。默认 supervisord 不会启动 TCP 的监听,因为这非常的不安全。

port

监听的地址和端口

默认值:如果有 [inet_http_server] 配置项则必填
是否必填:否
例如:port = 127.0.0.1:9001

username

HTTP/XML-RPC 服务的认证用户名,如果提供,supervisorctl 则需要用户认证才可连接。

默认值:无
是否必填:否
例如:username = admin

password

HTTP/XML-RPC 服务的认证密码。可以是明文密码,也可以使用密码经过 sha-1 哈希后的值,需要用 {SHA} 标识是一个哈希串,例如: {SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d

默认值:无
是否必填:否
例如:password = password 或者 password = {SHA}5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8

[supervisord]

此配置项定义 supervisord 进程的一些配置信息

logfile

supervisord 进程的日志输出文件。

默认值:当前目录下的 supervisord.log 文件
是否必填:否
例如:logfile = /tmp/supervisord.log

注意:如果 logfile 的值是特殊文件,比如 /dev/stdout 则必须设置 logfile_maxbytes = 0 来禁止日志轮转。

logfile_maxbytes

日志超过多大的尺寸将会被分割轮转,如果设置为 0 则表示无限大。

默认值:50MB
是否必填:否
例如:logfile_maxbytes = 50MB

logfile_backups

由于日志文件轮转导致的备份数量,如果设置为 0 则不会对因为轮转被分割的日志文件进行备份。

默认值:10
是否必填:否
例如:logfile_backups = 10

loglevel

supervisord 进程记录日志的级别,可选值为 critical, error, warn, info, debug, trace, blather

默认值:info
是否必填:否
例如:loglevel = info

pidfile

supervisord 进程 pid 文件的位置。

默认值:当前路径下的 supervisord.pid
是否必填:否
例如:pidfile = /tmp/supervisord.pid

umask

supervisord 进程的 umask。将会影响 supervisord 进程创建文件的权限,包括记录子进程日志的文件权限。

默认值:022
是否必填:否
例如:umask = 022

nodaemon

是否以非守护进程方式运行 supervisord 进程,如果 true,supervisord 进程将在前台运行。

默认值:false
是否必填:否
例如:nodaemon = false

minfds

supervisord 进程在成功运行之前会调整自身的最小可用文件描述符的数量。

默认值:1024
是否必填:否
例如:minfds = 1024

minprocs

supervisord 进程在成功运行之前会调整自身的最小进程描述符的数量。

默认值:200
是否必填:否
例如:minprocs = 200

nocleanup

子进程的 stdout 和 stderr 的输出如果没有配置重定向则默认保存为临时文件(子进程日志路径为 AUTO 的情况),此项配置 supervisord 启动时是否清理这些临时文件。

默认值:false
是否必填:否
例如:nocleanup = false

childlogdir

当子进程日志路径为 AUTO 的时候,子进程日志文件的存放路径。

默认值:/tmp
是否必填:否
例如:childlogdir = /tmp

user

在使用 root 用户运行 supervisord 进程时,进程将切换到此配置指定的用户。

默认值:不切换用户
是否必填:否
例如:user = nobody

directory

运行 supervisord 守护进程时切换到此目录。

默认值:不切换
是否必填:否
例如:directory = /tmp

strip_ansi

是否清除子进程日志中的 ansi 序列,比如 \n, \t 这些字符。

默认值:false
是否必填:否
例如:strip_ansi = false

environment

用于设置 supervisord 进程启动时的环境变量。supervisord 进程启动时会从 linux 系统继承当前的环境变量,这里可以设置 supervisord 进程特有的一些环境变量。当 supervisord 进程启动子进程时,子进程也会继承系统的和这些特有的环境变量。

默认值:无
是否必填:否
例如:environment = LOGFILE="/tmp/process.log"

注意:多个环境变量用逗号隔开。

identifier

supervisord 进程的标识符,主要用于通过 XML_RPC 统一管理时,用于区分不同 supervisord 进程的标识符。

默认值:supervisor
是否必填:否
例如:identifier = supervisor

[supervisorctl]

此配置项主要是针对 supervisorctl 的一些配置。

serverurl

supervisorctl 默认连接的 supervisord 地址,可以是 unix 套接字地址,也可以是 http 地址。

默认值:http://localhost:9001
是否必填:否
例如:serverurl = unix:///tmp/supervisor.sock

注意:没看错,默认值的确是 http 的连接。

username

supervisorctl 认证 HTTP/XML-RPC 服务时使用的用户名。

默认值:无
是否必填:否
例如:username = admin

password

supervisorctl 认证 HTTP/XML-RPC 服务时使用的密码,这个值必须是明文的。

默认值:无
是否必填:否
例如:password = password

prompt

supervisorctl 命令交互页面的提示符,默认是 supervisor,所以进入 supervisorctl 后提示符都是 supervisor>

默认值:supervisor
是否必填:否
例如:prompt = supervisor

history_file

保存 supervisorctl 命令输入历史的文件,和 shell 的 hisotry 类似。默认是没有开启,开启后我们则可以用过上下键来查找之前执行过的命令。

默认值:没有文件
是否必填:否
例如:history_file = ~/.sc_history

[program:xxx]

这个配置项就是定义子进程的配置了,[program:xxx] 中的 xxx 可以是任意名字。

command

进程的启动命令,进程必须在前台运行模式才可以被 supervisord 进程管理。

默认值:无
是否必填:是
例如:command = python3 -m http.server

process_name

进程名,如果下面的 numprocs 参数才 1,就不需要管这个参数。它的值默认是 %(program_name)s 也就是 [program:xxx] 中的 xxx。如果 numprocs 有多个,那不同的进程名就必须是不同的名字了。

默认值:$(program_name)s
是否必填:否
例如:process_name = $(program_name)s

numprocs

supervisord 进程将启动此子进程的多个实例。如果 numprocs 大于 1,则 process_name 的值中必须包含 $(process_num)s 的表达式。

默认值:1
是否必填:否
例如:numprocs = 1

备注:如果 numproces 的值大于 1,配置文件可以这样写:

[program:fgtest]
command=/usr/bin/python3 /root/fgtest.py
stdout_logfile=/tmp/fgtest.log
process_name = %(program_name)s-%(process_num)s
numprocs=10

update 后可以看到:

supervisor> status
envtest                          RUNNING   pid 8319, uptime 1:18:29
fgtest:fgtest-0                  RUNNING   pid 8580, uptime 0:00:02
fgtest:fgtest-1                  RUNNING   pid 8581, uptime 0:00:02
fgtest:fgtest-2                  RUNNING   pid 8582, uptime 0:00:02
fgtest:fgtest-3                  RUNNING   pid 8583, uptime 0:00:02
fgtest:fgtest-4                  RUNNING   pid 8584, uptime 0:00:02
fgtest:fgtest-5                  RUNNING   pid 8585, uptime 0:00:02
fgtest:fgtest-6                  RUNNING   pid 8586, uptime 0:00:02
fgtest:fgtest-7                  RUNNING   pid 8587, uptime 0:00:02
fgtest:fgtest-8                  RUNNING   pid 8588, uptime 0:00:02
fgtest:fgtest-9                  RUNNING   pid 8589, uptime 0:00:02
pyhttp2                          RUNNING   pid 8321, uptime 1:18:29
simpleHttp:pyhttp                RUNNING   pid 8323, uptime 1:18:29
simpleHttp:pyhttp1               RUNNING   pid 8322, uptime 1:18:29
supervisor>
numprocs_start

上面可以看到,%(process_num)s 是从 0 开始计算。这个配置用于更改 %(process_num)s 的偏移量,例如 numproces_start 等于 1,则会生成 fgtest:fgtest-1fgtest:fgtest-10

默认值:0
是否必填:否
例如:numprocs_start = 0

priority

进程的优先级,高优先级的程序将最后一个启动和第一个关闭。主要可以用于处理进程之间的依赖关系。

默认值:999
是否必填:否
例如:priority = 999

autostart

子进程是否随着 supervisord 进程的启动而启动。

默认值:true
是否必填:否
例如:autostart = true

startsecs

子进程启动多少秒之后,如果状态依然是 RUNNGIN,则我们认为它启动成功了。

默认值:1
是否必填:否
例如:startsecs = 1

startretries

当进程启动失败后,最大尝试启动的次数。默认是 3 次,当超过 3 次后,supervisord 将把此进程的状态设置为 FAIL。

默认值:3
是否必填:否
例如:startretries = 3

autorestart

如果进程已处于 RUNNING 状态后进程退出,supervisord 是否应该自动重启子进程。有三种选项:false 表示不自动重启, unexpected 表示只有进程退出码为下面 exitcodes 里定义的退出码时才重启, true 表示总是重启。

默认值:unexpected
是否必填:否
例如:autorestart = unexpected

exitcodes

进程退出码列表,与 autorestart 配合使用。

默认值:0
是否必填:否
例如:exitcodes = 0 或者 exitcodes = 0,2

stopsignal

进程停止信号,可以为 TERM, HUP, INT, QUIT, KILL, USR1, USR2 等信号。将执行 stop 命令的时候,supervisord 将用指定的信号杀死进程。

默认值:TERM
是否必填:否
例如:stopsignal = TERM

stopwaitsecs

向子进程发送 stopsignal 后,到子进程退出,系统返回信息到 supervisord 所等待的时间。如果超时,supervisord 进程将强制杀死子进程。

默认值:10
是否必填:否
例如:stopwaitsecs = 10

stopasgroup

如果子进程也有子进程,supervisord 仅仅干掉子进程的话,子进程的子进程可能会变成孤儿进程。该选项定义是否把整个子进程组全部停止。该选项发送的是终止信号。

默认值:false
是否必填:否
例如:stopasgroup = false

killasgroup

与 stopasgroup 道理相同,不过 killasgroup 会强制杀死子进程。

默认值:false
是否必填:否
例如:killasgroup = false

user

supervisord 进程启动子进程时,使用哪个用户身份运行子进程。只有 supervisord 进程以 root 用户启动才生效。

默认值:不切换
是否必填:否
例如:user = user

注意:supervisord 仅仅使用 setuid 更改用户,并不会处理用户的环境变量,所以不会更改 USERHOME 等环境变量的值。如果子进程依赖这些环境变量的话,可以通过下面的 environment 选项来配置。

redirect_stderr

将子进程的错误输出也写入标准输出的日志文件中。

默认值:false
是否必填:否
例如:redirect_stderr = false

stdout_logfile

将子进程的标准输出写入到指定的文件,如果配置了 redirect_stderr 则错误输出也会写入到这个文件。如果未设置 stdout_logfile 或者此设置的值为 AUTO,supervisord 将自动选择文件位置,并在重启时清理这些文件。如果设置为 NONE,则 supervisord 不会创建任何日志文件。

默认值:AUTO
是否必填:否
例如:stdout_logfile = /tmp/process.log

注意:如果将 stdout_logfile 设置为 /dev/stdout 等特殊文件,则必须通过设置 stdout_logfile_maxbytes = 0 来禁用日志轮转。

stdout_logfile_maxbytes

子进程日志文件在被轮转前最大字节数,将此值设置为 0 表示无限制。

默认值:50MB
是否必填:否
例如:stdout_logfile_maxbytes = 50MB

stdout_logfile_backups

由于日志文件轮转导致的备份数量,如果设置为 0 则不会对因为轮转被分割的日志文件进行备份。

默认值:10
是否必填:否
例如:stdout_logfile_backups = 10

stdout_capture_maxbytes

设置捕获模式管道的大小,当值不为 0 的时候,子进程可以从 stdout 发送消息,而 supervisord 可以根据信息发送相应的事件。

默认值:0
是否必填:否
例如:stdout_capture_maxbytes = 1MB

stdout_events_enabled

当设置为 true,子进程向 stdout 文件描述符写数据的时候,supervisord 将发送 PROCESS_LOG_STDOUT 类型的事件。

默认值:0
是否必填:否
例如:stdout_events_enabled = 0

stdout_syslog

如果为 true,则子进程标准输出与子进程的名字一同被定向到 syslog。

默认值:false
是否必填:否
例如:stdout_syslog = false

stderr_logfile

redirect_stderr = false 的情况下,子进程的错误输出写入指定的文件。

默认值:AUTO
是否必填:否
例如:stderr_logfile = /tmp/process-stderr.log

stderr_logfile_maxbytes

作用与 stdout_logfile_maxbytes 相同,只不过作用与子进程的错误输出。

默认值:50MB
是否必填:否
例如:stderr_logfile_maxbytes = 50MB

stderr_logfile_backups

作用与 stdout_logfile_backups 相同,只不过作用与子进程的错误输出。

默认值:10
是否必填:否
例如:stderr_logfile_backups = 10

stderr_capture_maxbytes

作用与 stdout_capture_maxbytes 相同,只不过作用与子进程的错误输出。

默认值:0
是否必填:否
例如:stderr_capture_maxbytes = 1MB

stderr_events_enabled

作用与 stdout_events_enabled 相同,只不过作用与子进程的错误输出。

默认值:0
是否必填:否
例如:stderr_events_enabled = 0

stderr_syslog

作用与 stdout_syslog 相同,只不过作用与子进程的错误输出。

默认值:false
是否必填:否
例如:stderr_syslog = false

environment

用于设置子进程启动时的环境变量。

默认值:无
是否必填:否
例如:environment = HOME="/home/user",USER="user"

注意:多个环境变量用逗号隔开,此环境变量是子进程独享的,不与其他进程共享。

directory

运行子进程前,会先切换到这个目录。

默认值:继承 supervisord 的目录
是否必填:否
例如:directory = /tmp

umask

子进程创建文件的 umask。

默认值:继承 supervisord 的 umask
是否必填:否
例如:umask = 022

serverurl

supervisord 将 serverurl 的值通过环境变量传递给子进程,子进程可以通过读取环境变量 SUPERVISOR_SERVER_URL 来获取值。

默认值:AUTO
是否必填:否
例如:serverurl = AUTO

[include]

此配置项用于定义包含其他配置文件。

files

定义其他配置文件的路径,可以使用通配符来匹配文件。

默认值:无
是否必填:否
例如:files = /etc/supervisord.d/*.conf

注意:我们应该养成良好的习惯,将子进程相关的配置放到 /etc/supervisord.d/ 下而不是和 supervisord.conf 放到一起。

[group:xxx]

进程组相关的配置。[group:xxx] 中的 xxx 是分组名称。

programs

配置哪些进程属于这个分组

默认值:无
是否必填:是
例如:programs = bar,foo

priority

进程组的优先级

默认值:999
是否必填:否
例如:priority = 999

[fcgi-program:xxx]

提供对 fast-cgi 程序的支持,这里不做过多讲解。

[eventlistener:xxx]

事件监听器相关配置,地位和 program 一样,也是 supervisord 的子进程,只不过主要功能用于订阅 supervisord 发送的事件,我们可以在事件监听器中做一系列的处理。supervisor 中事件监听相关的用法,后面的章节会详细介绍。

事件监听器除了支持 program 所有的配置外,还支持以下几个特有的配置

buffer_size

事件监听器的事件队列缓冲区大小,当事件缓冲区溢出时,将丢弃缓冲区中最旧的事件。

默认值:10
是否必填:否
例如:buffer_size = 10

events

事件的类型,只有在这里定义了的事件才会被发送给事件的订阅者。

默认值:无
是否必填:是
例如:events = PROCESS_STATE_STARTING

result_handler

一个 pkg_resources 入口点的字符串解析为 Python 可调用。默认值是 supervisor.dispatchers:default_handler

默认值:supervisor.dispatchers:default_handler
是否必填:否
例如:result_handler = supervisor.dispatchers:default_handler

[rpcinterface:supervisor]

这个配置项作用与 HTTP/XML-RPC,如果想使用 Web 管理或者 supervisorctl 管理 supervisord,则必须开启此项。如果想添加 RPC 接口来自定义 supervisor 的功能,可以查看官方文档获取更详细的教程。

supervisor.rpcinterface_factory

RPC 接口的入口工厂函数。

默认值:supervisor.rpcinterface:make_main_rpcinterface
是否必填:否
例如:supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

子进程状态

  • STOPPED:通过 stop 命令停止或从未启动过的进程状态。
  • STARTING:接收到 start 命令进程正在启动中。
  • RUNNING:进程正在运行。
  • BACKOFF:进程进入 STARTING 状态后退出太快而无法进入 RUNNING 状态。
  • STOPPING: 接收到 stop 命令进程正在停止中。
  • EXITED:进程意外的从 RUNNING 状态退出。
  • FATAL:程序无法启动成功。
  • UNKNOWN:未知状态,一般属于 supervisord 进程的错误。

事件处理

事件是 supervisor 的一个高级特性,常用于被管理的子进程崩溃的时候发送报警等额外操作。

supervisord 会在子进程运行的各各阶段发出不同的事件,只要定义了事件监听器,并订阅相应的事件,就可以在事件触发后对这些事件进行额外的处理。事件监听器是作为 supervisord 的子进程存在,事件通知协议是通过子进程的标准输入和标准输出来通信。supervisord 将特殊格式的字符串通过标准输入传递给子进程,子进程将结果通过标准输出返回给 supervisord。因此 事件监听器可以由任何语言来写,但是 supervisor 为 Python 提供了 supervisor.childutils 模块来简化了通信过程,因此会比其他语言更方便些。

事件通知消息格式

在编写事件监听器之前我们需要先知道 supervisord 和事件监听器交流的数据格式是什么。

消息 head

事件通知消息分为两部分 headbody。以下是 head 部分的格式样例:

ver:3.0 server:supervisor serial:15 pool:pyhttp_eventlistener poolserial:2 eventname:PROCESS_STATE_RUNNING len:66
描述 示例
ver 事件协议的版本 3.0
server supervisord 的标识符 supervisor
serial 事件编号 15
pool 生成此事件的事件侦听器池的名称 pyhttp_eventlistener
poolserial 事件池的编号 2
eventname 事件名称 PROCESS_STATE_RUNNING
len data长度,这个非常重要 66
消息 body

根据 head 中的 len 字段,可以得知 data 的长度,然后通过长度可以读取到完整的 data 内容了。以下是 data 部分示例:

processname:pyhttp2 groupname:pyhttp2 from_state:STARTING pid:9896
描述 示例
processname 进程名 pyhttp2
groupname 进程组名 pyhttp2
from_state 上一次的进程状态 STARTING
pid 进程ID 9896

事件监听器消息通知

事件监听器也有自己的状态和事件需要通知给 supervisord,例如在某个事件监听器正在工作,没法处理新事件时,supervisord 将不会发送通知给这个事件监听器。

事件监听器状态

事件监听器主要有三种状态:

描述
ACKNOWLEDGED 事件监听器已确认(接受或拒绝)事件发送
READY 事件监听器已就绪
BUSY 事件监听器正在工作

事件监听器首次启动时, supervisord 会自动将其设置为 ACKNOWLEDGED 状态,只有当监听器通过标准输出发送 READY\n 时,supervisord 才会将事件通知给这个监听器,此时监听器将处于 BUSY 状态。直到事件监听器向 supervisord 发送 RESULT 2\nOK 或者 RESULT 2\nFAIL 时,监听器才会重新回到 ACKNOWLEDGED 状态。当监听器再次向 supervisord 发送 READY\n 后才可以继续接收到新的事件。

事件监听器示例

编辑 /root/listener.py 写入如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
import os

def write_stdout(s):
sys.stdout.write(s)
sys.stdout.flush()

def main():
while 1:
# transition from ACKNOWLEDGED to READY
write_stdout('READY\n')

# read header line and print it to stderr
line = sys.stdin.readline()

with open('event.log', 'a') as f:
f.write(line)

headers = dict([ x.split(':') for x in line.split() ])
data = sys.stdin.read(int(headers['len']))

with open('event.log', 'a') as f:
f.write(data)
f.write('\n\n')

write_stdout('RESULT 2\nOK')

if __name__ == '__main__':
main()

这段代码简单的将从 supervisord 接收到的事件通知写入 event.log 文件,然后继续下一次的接受通知。

接着将以下配置项追加到 /etc/supervisord.conf

[eventlistener:pyhttp_eventlistener]
command=python3 /root/listener.py
events=PROCESS_STATE

这个事件监听器订阅了 PROCESS_STATE 事件,这个事件会在子进程状态改变时触发。更新 supervisord:

(supervisor) root@ubuntu:~# supervisorctl update
pyhttp_eventlistener: stopped
pyhttp_eventlistener: updated process group
(supervisor) root@ubuntu:~# supervisorctl status
pyhttp2                          RUNNING   pid 9896, uptime 0:55:57
pyhttp_eventlistener             RUNNING   pid 10056, uptime 0:00:05
simpleHttp:pyhttp                RUNNING   pid 9889, uptime 0:56:35
simpleHttp:pyhttp1               RUNNING   pid 9888, uptime 0:56:35
(supervisor) root@ubuntu:~#

接着我们重启一个子进程,就可以看到 event.log 的内容了:

(supervisor) root@ubuntu:~# supervisorctl restart simpleHttp:pyhttp
simpleHttp:pyhttp: stopped
simpleHttp:pyhttp: started
(supervisor) root@ubuntu:~# cat event.log 
ver:3.0 server:supervisor serial:76 pool:pyhttp_eventlistener poolserial:2 eventname:PROCESS_STATE_STOPPING len:67
processname:pyhttp groupname:simpleHttp from_state:RUNNING pid:9889

ver:3.0 server:supervisor serial:77 pool:pyhttp_eventlistener poolserial:3 eventname:PROCESS_STATE_STOPPED len:68
processname:pyhttp groupname:simpleHttp from_state:STOPPING pid:9889

ver:3.0 server:supervisor serial:78 pool:pyhttp_eventlistener poolserial:4 eventname:PROCESS_STATE_STARTING len:66
processname:pyhttp groupname:simpleHttp from_state:STOPPED tries:0

ver:3.0 server:supervisor serial:79 pool:pyhttp_eventlistener poolserial:5 eventname:PROCESS_STATE_RUNNING len:69
processname:pyhttp groupname:simpleHttp from_state:STARTING pid:10068

日志中记录了 simpleHttp:pyhttp 进程从退出到重启成功过程中所有的事件。

如果使用 Python 编写监听器,supervisor.childutils 可以更方便的帮助我们处理事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os
import sys
import json

from supervisor import childutils

def main():
while 1:
headers, payload = childutils.listener.wait(sys.stdin, sys.stdout)

with open('event.pro.log', 'a') as f:
f.write(json.dumps(headers))
childutils.listener.ok(sys.stdout)
if __name__ == '__main__':
main()

supervisord 将事件监听器也当作普通子进程管理,所以事件监听器的配置项包含了所有 [program:xxx] 的配置,不过事件监听器配置项必须存在一个 events 的配置用来定义监听器订阅的事件。

事件类型

以下是可以被订阅的所有事件名和它的作用。

EVENT

基本事件类型,所有事件的父类,订阅此事件会接收所有的事件类型,但永远不会接收到此事件。

PROCESS_STATE

表示进程由一种状态转为另一种状态,订阅者永远不会收到此类型的事件,但是订阅此事件可以接收所有此事件类型的子类。

PROCESS_STATE_STARTING

进程状态变成 STARTING 时会触发此事件。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:STOPPED tries:0
PROCESS_STATE_RUNNING

进程状态变成 RUNNING 时会触发此事件。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:STARTING pid:2766
PROCESS_STATE_BACKOFF

进程状态变成 BACKOFF 时会触发此事件,表示进程未成功启动。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:STOPPED tries:0

tries 字段表示已经尝试重启的次数。

PROCESS_STATE_STOPPING

进程状态变成 STOPPING 时会触发此事件。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:STARTING pid:2766
PROCESS_STATE_EXITED

进程状态变成 EXITED 时会触发此事件。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:RUNNING expected:0 pid:2766

expected 字段用来标识进程是否是正常退出,如果是非正常退出,它的值是 0。

PROCESS_STATE_STOPPED

进程状态变成 STOPPED 时会触发此事件。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:STOPPING pid:2766
PROCESS_STATE_FATAL

进程状态变成 FATAL 时会触发此事件。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:BACKOFF
PROCESS_STATE_UNKNOWN

进程状态变成 UNKNOWN 时会触发此事件,只有 supervisord 发生错误时才会导致此状态。父类是 PROCESS_STATE

事件内容示例:

processname:cat groupname:cat from_state:BACKOFF
REMOTE_COMMUNICATION

在 supervisord 的 RPC 接口上调用 supervisor.sendRemoteCommEvent() 方法时触发。父类是 EVENT。

事件内容示例:

type:type
data
PROCESS_LOG

进程写入 stdout 或 stderr 时触发。只有 stdout_events_enabledstderr_events_enabled 为 true 时且未处于捕获模式才生效。订阅者永远不会收到此类型的事件,但是订阅此事件可以接收所有此事件类型的子类。

PROCESS_LOG_STDOUT

表示子进程已经写入了 stdout 文件描述符。

事件内容示例:

processname:name groupname:name pid:pid
data
PROCESS_LOG_STDERR

表示子进程已经写入了 stderr 文件描述符。

事件内容示例:

processname:name groupname:name pid:pid
data
PROCESS_COMMUNICATION

只有 stdout_capture_maxbytesstderr_capture_maxbytes 的值不为 0 时,子进程在向 stdout 或者 stderr 中输出 <!--XSUPERVISOR:BEGIN--><!--XSUPERVISOR:END--> 标签时会触发此类型事件的子类。标签中的内容会被作为事件日志发送给订阅者。

PROCESS_COMMUNICATION_STDOUT

子进程通过 stdout 向 supervisord 发送消息。

例如子进程在 stdout 中输出了 <!--XSUPERVISOR:BEGIN-->message_from:fgtest<!--XSUPERVISOR:END-->,订阅此事件的监听者会收到:

processname:fgtest groupname:fgtest pid:10902
message_from:fgtest
PROCESS_COMMUNICATION_STDERR

子进程通过 stderr 向 supervisord 发送消息。

SUPERVISOR_STATE_CHANGE

当 supervisord 进程的状态发送变化时引发的事件类型。订阅者永远不会收到此类型的事件,但是订阅此事件可以接收所有此事件类型的子类。

SUPERVISOR_STATE_CHANGE_RUNNING

supervisord 进程启动会发送此事件,事件消息体为空字符串。

SUPERVISOR_STATE_CHANGE_STOPPING

supervisord 进程停止会发送此事件,事件消息体为空字符串。

TICK

监听者会隔 5、60、360 秒被事件唤醒,订阅者永远不会收到此类型的事件,但是订阅此事件可以接收所有此事件类型的子类。

TICK_5

每隔 5 秒会通过此类型事件唤醒监听者。

事件内容示例:

when:1201063880
TICK_60

每隔 60 秒会通过此类型事件唤醒监听者。

事件内容示例:

when:1201063880
TICK_3600

每隔 3600 秒会通过此类型事件唤醒监听者。

事件内容示例:

when:1201063880
PROCESS_GROUP

将进程组从 supervisord 中添加或删除会触发的事件类型。订阅者永远不会收到此类型的事件,但是订阅此事件可以接收所有此事件类型的子类。

PROCESS_GROUP_ADDED

添加进程组将会触发这个事件

事件内容示例:

groupname:cat
PROCESS_GROUP_REMOVED

删除进程组将会触发这个事件

事件内容示例:

groupname:cat

附录

配置开机自启 supervisord

/data/venv/supervisor/bin/supervisord -c /etc/supervisord.conf 添加到 /etc/rc.local 即可。