如何使用supervisor管理gunicorn启动的python项目

supervisor 是基于 python 的任务管理工具,用来自动运行各种后台任务,当然你也能直接利用 nohup 命令使任务自动后台运行,但如果要重启任务,每次都自己手动 kill 掉任务进程,这样很繁琐,而且一旦程序错误导致进程退出的话,系统也无法自动重载任务。

简述

之前关于使用uwsgi启动python项目的文章中我们已经大概讲过如何使用了,想了解用uwsgi启动python项目的可转至**uwsgi配置发布web服务器**。

首先gunicorn和uwsgi都是实现WSGI协议的Web服务器,并且都是基于Perfork模型。
其次Uwsgi是通过C语言编写的,
Gunicorn是通过Python语言编写的,
相对于Uwsgi,Gunicorn相对于简单,启动也十分方便

WSGI英文全称:Web Server Gateway Interface,Web服务网管接口,简单来说它是一种Web服务器和应用程序间的通信规范

uWSGI是一个Web Server,并且独占uwsgi协议,但是同时支持WSGI协议、HTTP协议等,它的功能是把HTTP协议转化成语言支持的网络协议供python使用。

gunicorn是一个python Wsgi http server,只支持在Unix系统上运行,来源于Ruby的unicorn项目。Gunicorn使用prefork master-worker模型(在gunicorn中,master被称为arbiter),能够与各种wsgi web框架协作。

其实本篇文章主要说的是gunicorn的使用方式,有的文章里讲在高并发情况下uwsgi性能要优于gunicorn的说法,我这里没有做过验证而且也不是这边文章要讲的重点。

gunicorn

安装与启动

  1. 安装对应异步模块

    1
    pip install gevent
  2. 安装gunicorn

    网上文章大部分是让你使用pip安装,如下:

    1
    pip install gunicorn

    实际验证,你的linux安装了gunicorn也可以的,如Ubuntu下:

    1
    sudo apt-get install supervisor
  3. 使用命令行参数启动项目

    1
    gunicorn -w 进程数量 -b 域名:端口 启动文件名:应用名
  4. 使用配置文件启动项目

    1
    gunicorn -c 配置文件名 启动文件名:应用名

配置文件

命令行的相关参数这里如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1)-c CONFIG,–config=CONFIG
指定一个配置文件(py文件)
2)-b BIND,–bind=BIND
与指定socket进行绑定
3)-D,–daemon
后台进程方式运行gunicorn进程
4)-w WORKERS,–workers=WORKERS
工作进程的数量
5)-k WORKERCLASS,–worker-class=WORKERCLASS
工作进程类型,包括sync(默认),eventlet,gevent,tornado,gthread,gaiohttp
6)–backlog INT
最大挂起的连接数
7)–log-level LEVEL
日志输出等级
8)–access-logfile FILE
访问日志输出文件
9)–error-logfile FILE
错误日志输出文件

这里直接上示例配置文件,都加过了注释,gunicorn_config.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import multiprocessing
# 项目一旦启动会出现一个master进程和4个worker进程
# master进程负责管理worker的, 并不处理请求
# workers才是真正的处理请求的
workers = multiprocessing.cpu_count() * 2 + 1
# 指定每个工作者的线程数
threads = 3
# 监听的地址和端口
bind = '0.0.0.0:5000'
# 设置守护进程,将进程交给supervisor管理
daemon = 'false'
# 工作模式协程
worker_class = 'gevent'
# 设置最大并发量(这个值会影响协程的效率)
worker_connections = 2000
# 设置进程文件目录(存储的是master进程的ID),这里直接放在了项目根目录
pidfile = './gunicorn.pid'
# 设置访问日志和错误信息日志路径(当前项目logs下)
accesslog = './logs/gunicorn_access.log'
# 成功请求日志格式
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'
"""
其每个选项的含义如下:
h remote address
l '-'
u currently '-', may be user name in future releases
t date of the request
r status line (e.g. ``GET / HTTP/1.1``)
s status
b response length or '-'
f referer
a user agent
T request time in seconds
D request time in microseconds
L request time in decimal seconds
p process ID
"""
# 日志记录位置,当前项目logs目录下
errorlog = './logs/gunicorn_error.log'
# 设置日志记录水平
loglevel = 'warning'
# 环境变量
raw_env = 'FLASK_DEBUG=0'
# 设置gunicorn使用的python虚拟环境
pythonpath='/home/tony/test/py3/bin/python'
# 连接请求等待最长时间,默认是2
keepalive = 3
# 限制请求头的大小: 默认是8190
limit_request_field_size = 8190
# 限制请求头的数量:默认是100,不能超过32768
limit_request_fields = 101
# 限制请求行大小:默认是4094
limit_request_line = 5120

启动django项目示例

我这里是一个空的django项目,代码项目结构大概如下:

1
2
3
4
5
6
7
8
9
10
MyDjango
|—— MyDjango/
|—— __init__.py
|—— settings.py
|—— asgi.py
|—— urls.py
|—— wsgi.py
|—— manage.py
|—— gunicorn_config.py
|—— logs/
  1. 进入项目目录

    1
    cd MyDjango
  2. 使用gunicorn启动项目

    1
    gunicorn -c gunicorn_config.py MyDjango.wsgi:application
  3. 访问项目

    我这里项目是部署在了wsl里,所以我本地使用wsl内网ip访问

    1
    http://172.24.137.156:5000/

    正常看到django默认页面。

    注:有人发现这种方式访问django admin发现静态文件都是404,有的文章给出的方案如下:

    修改MyDjango/wsgi.py文件

    1
    2
    3
    from django.contrib.staticfiles.handlers import StaticFilesHandler # 添加模块
    # 修改 application = get_wsgi_application()
    application = StaticFilesHandler(get_wsgi_application())

    本地经过测试,这种方式是可用的。

  4. 对比项目结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    MyDjango
    |—— MyDjango/
    |—— __init__.py
    |—— settings.py
    |—— asgi.py
    |—— urls.py
    |—— wsgi.py
    |—— manage.py
    |—— gunicorn_config.py
    |—— gunicorn.pid
    |—— logs/
    |—— gunicorn_access.log
    |—— gunicorn_error.log

    有没有看出来相比之前根目录多了gunicorn.pid,即gunicorn进程文件;logs里也多了两个gunicorn的日志记录文件,每当访问一次网站可以在gunicorn_access.log中看到请求记录。

以上便是gunicorn启动django项目的方式,flask类似。

supervisor

安装

同样可以通过pip和apt-get方式安装,我这里生产环境是Ubuntu,所以基本也只说了apt-get。

1
2
3
pip install supervisor
或者
apt-get install supervisor

生成supervisor配置

1
root@Tony-PC:/opt/pysupervisor# echo_supervisord_conf > ./supervisord.conf

常用参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
supervisord.conf配置文件参数解释
[program:xx]是被管理的进程配置参数,xx是进程的名称
[program:xx]
command=gunicorn -c gunicorn_config.py MyDjango.wsgi:application ; 程序启动命令
autostart=true ; 在supervisord启动的时候也自动启动
startsecs=10 ; 启动10秒后没有异常退出,就表示进程正常启动了,默认为1秒
autorestart=true ; 程序退出后自动重启,可选值:[unexpected,true,false],默认为unexpected,表示进程意外杀死后才重启
startretries=3 ; 启动失败自动重试次数,默认是3
user=tomcat ; 用哪个用户启动进程,默认是root
priority=999 ; 进程启动优先级,默认999,值小的优先启动
redirect_stderr=true ; 把stderr重定向到stdout,默认false
stdout_logfile_maxbytes=20MB ; stdout 日志文件大小,默认50MB
stdout_logfile_backups = 20 ; stdout 日志文件备份数,默认是10
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile=./logs/super_main.log
stopasgroup=false ;默认为false,进程被杀死时,是否向这个进程组发送stop信号,包括子进程
killasgroup=false ;默认为false,向进程组发送kill信号,包括子进程

注:原始生成的配置文件参数很多,这里不写不代表不启用,仅仅标识不写的参数使用默认值,所以最好还是使用原始配置文件,不用的注释掉就行了。

我这里./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
; 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 ".

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

;[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=chrism ; default is current user, required 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,2 ; 'expected' exit codes used with autorestart (default 0,2)
;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)
;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)
;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,2 ; 'expected' exit codes used with autorestart (default 0,2)
;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)
;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)
;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 = /etc/supervisord.d/*.ini


[program:main]
;启动命令
command=gunicorn -c gunicorn_config.py MyDjango.wsgi:application
;程序的启动目录
;directory=/code/
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile =./logs/super_main.log
stderr_logfile =./logs/super_main_error.log
stdout_logfile_maxbytes = 50MB
stdout_logfile_backups = 3

使用supervisor启动项目

1
supervisord -c supervisord.conf

查看项目状态

1
2
(py3) tony@UYYNOT:~/test/MyDjango$ supervisorctl
main RUNNING pid 2957, uptime 0:00:53

可以看到项目已经运行起来了,访问下试试已经成功(之前createsuperuser过,所以可以进后台哈)

再看下项目目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MyDjango
|—— MyDjango/
|—— __init__.py
|—— settings.py
|—— asgi.py
|—— urls.py
|—— wsgi.py
|—— manage.py
|—— gunicorn_config.py
|—— gunicorn.pid
|—— logs/
|—— gunicorn_access.log
|—— gunicorn_error.log
|—— super_main.log

可以看到logs多了一个super_main.log的supervisor的日志

以上便是使用supervisor管理启动gunicorn之django项目的过程,关于supervisor其他几个常用命令如下:

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
supervisord -c /etc/supervisord.conf #启动supervisor

supervisorctl -c /etxc/supervisord.conf restart my #重启my项目
supervisorctl -c /etc/supervisord.conf [start|stop|restart] [program-name|all]


supervisorctl
进入更新新的配置到supervisord交互命令行
命令行里可以使用status查看项目状态、start all和restart all等命令跟下面单条指令类似

二、更新新的配置到supervisord
supervisorctl update

三、重新启动配置中的所有程序
supervisorctl reload

四、启动某个进程(program_name=你配置中写的程序名称)
supervisorctl start program_name

五、查看正在守候的进程
supervisorctl

六、停止某一进程 (program_name=你配置中写的程序名称)
pervisorctl stop program_name

七、重启某一进程 (program_name=你配置中写的程序名称)
supervisorctl restart program_name

八、停止全部进程
supervisorctl stop all
注意:显示用stop停止掉的进程,用reload或者update都不会自动重启。