概述
如果你用过 Python 开发 Web 应用,例如利用 Flask 框架开发过 Web 应用的话,那么,你在启动 Python Web 服务时,一定看到过如下提示:
* Serving Flask app "flask_server" (lazy loading)* Environment: productionWARNING: This is a development server. Do not use it in a production deployment.Use a production WSGI server instead.* Debug mode: off
看到 WARNING 信息了吧~
提示这是一个开发服务器,不要直接用于生产环境部署,在生产环境下应该使用 WSGI 服务器。
那么,什么是 WSGI 服务器呢?
本文要介绍的 Gunicorn 就是其中的佼佼者。
WSGI 服务器
Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口)。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。
WSGI 区分了两个部分:
- 服务器/网关
- 应用程序/框架
在处理一个WSGI请求时,服务器会为应用程序提供环境信息及一个回调函数(Callback Function)。当应用程序完成处理请求后,透过前述的回调函数,将结果回传给服务器。
所谓的“WSGI 中间件”同时实现了API的两方,因此可以在WSGI服务器和WSGI应用之间起调解作用:从Web服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。“中间件”组件可以执行以下功能:
- 重写环境变量后,根据目标URL,将请求消息路由到不同的应用对象。
- 允许在一个进程中同时运行多个应用程序或应用框架。
- 负载均衡和远程处理,通过在网络上转发请求和响应消息。
- 进行内容后处理,例如应用XSLT样式表。
gunicorn 介绍
Gunicorn 是一个用于 UNIX 的 Python WSGI HTTP 服务器,这是一个 pre-fork worker 模型。
Gunicorn 服务器与各种 Web 框架广泛兼容,实现简单,服务器资源消耗少,速度相当快。
gunicorn 实战
安装
开始 gunicorn 学习的第一步,我们还是来安装 gunicorn 。和大部分的 Python 库一样,gunicorn 的安装非常简单,只需要使用 pip 安装即可:
pip install gunicorn
QuickStart
下面,我们来用一个最简示例演示一下 gunicorn 的使用:
编写一个最简单的应用程序 myapp.py 文件:
def app(environ, start_response):data = b"Hello, World!\n"start_response("200 OK", [("Content-Type", "text/plain"),("Content-Length", str(len(data)))])return iter([data])
可以看到,其中定义了一个 app 的函数。
下面,我们可以直接使用 gunicorn 来启动对应的 WEB 服务了:
gunicorn -w 4 myapp:app# [2021-09-15 11:51:01 +0800] [10495] [INFO] Starting gunicorn 20.1.0# [2021-09-15 11:51:01 +0800] [10495] [INFO] Listening at: http://127.0.0.1:8000 (10495)# [2021-09-15 11:51:01 +0800] [10495] [INFO] Using worker: sync# [2021-09-15 11:51:01 +0800] [10497] [INFO] Booting worker with pid: 10497# [2021-09-15 11:51:01 +0800] [10498] [INFO] Booting worker with pid: 10498# [2021-09-15 11:51:01 +0800] [10499] [INFO] Booting worker with pid: 10499# [2021-09-15 11:51:01 +0800] [10500] [INFO] Booting worker with pid: 10500
此时,我们可以打开浏览器,访问 http://127.0.0.1:8000 来看一下:
好了,Web 应用服务器已经可以正常运行了。
Flask 应用集成
首先,我们准备一个简单的 Flask 应用程序代码:flask_demo.py
from flask import Flaskapp = Flask(__name__)@app.route("/")def hello_world():return "<p>Hello, World!</p>"if __name__ == '__main__':"""# 主程序"""app.run(host="0.0.0.0", port=8080)
此时,我们可以直接使用 gunicorn 来启动应用:
gunicorn -w 4 -b 127.0.0.1:4000 flask_demo:app# [2021-09-15 12:41:53 +0800] [13030] [INFO] Starting gunicorn 20.1.0# [2021-09-15 12:41:53 +0800] [13030] [INFO] Listening at: http://127.0.0.1:4000 (13030)# [2021-09-15 12:41:53 +0800] [13030] [INFO] Using worker: sync# [2021-09-15 12:41:53 +0800] [13032] [INFO] Booting worker with pid: 13032# [2021-09-15 12:41:53 +0800] [13033] [INFO] Booting worker with pid: 13033# [2021-09-15 12:41:53 +0800] [13034] [INFO] Booting worker with pid: 13034# [2021-09-15 12:41:53 +0800] [13035] [INFO] Booting worker with pid: 13035
此时,服务已经可以正常启动起来了。
配置文件说明
Gunicorn 服务器在启动时,会依次从上到下尝试读取相关配置(优先级依次降低):
- 命令行参数。
- 环境变量GUNICORN_CMD_ARGS中设置的命令行参数。
- 在当前工作目录中或使用命令行参数指定的可选配置文件gunicorn.conf.py。
- 从特定于框架的配置文件中读取配置。
- 读取基础环境变量。
Ps: gunicorn 可以使用如下命令来查询当前解析到的配置:
gunicorn --print-config APP_MODULE
关于 gunicorn 支持哪些命令行参数,可以使用如下命令进行查询:
gunicorn -h
而 gunicorn 的配置文件则是一个以 .py 为扩展名的 Python 源文件(默认为 gunicorn.conf.py),在每次启动 Gunicorn 服务时,该配置文件都会被执行,示例如下:
import multiprocessingbind = "127.0.0.1:8000"workers = multiprocessing.cpu_count() * 2 + 1
下面,我们给出一个常用的 Gunicorn 的配置文件示例:
import multiprocessingbind = "0.0.0.0:8000"workers = multiprocessing.cpu_count() * 2 + 1pidfile = "gunicorn.pid"daemon = "true"accesslog = 'gunicorn_acess.log'errorlog = 'gunicorn_error.log'loglevel = 'warning'access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'
nginx 代理
在生产环境下,我们强烈建议将 Gunicorn 作为一个反向代理的后端服务。
例如,Gunicorn 前应该部署一个 Nginx 来接收请求流量。
一个示例的 Nginx 配置如下:
worker_processes 1;user nobody nogroup;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events {worker_connections 1024; # increase if you have lots of clientsaccept_mutex off; # set to 'on' if nginx worker_processes > 1# 'use epoll;' to enable for Linux 2.6+}http {include mime.types;default_type application/octet-stream;access_log /var/log/nginx/access.log combined;sendfile on;upstream app_server {# fail_timeout=0 means we always retry an upstream even if it failed to return a good HTTP response# for UNIX domain socket setupsserver unix:/tmp/gunicorn.sock fail_timeout=0;# for a TCP configuration# server 192.168.0.7:8000 fail_timeout=0;}server {# if no Host match, close the connection to prevent host spoofinglisten 80 default_server;return 444;}server {# use 'listen 80 deferred;' for Linux# use 'listen 80 accept_filter=httpready;' for FreeBSDlisten 80;client_max_body_size 4G;# set the correct host(s) for your siteserver_name example.com www.example.com;keepalive_timeout 5;# path for static filesroot /path/to/app/current/public;location / {# checks for static file, if not found proxy to apptry_files $uri @proxy_to_app;}location @proxy_to_app {proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header Host $http_host;# we don't want nginx trying to do something clever with# redirects, we set the Host: header above already.proxy_redirect off;proxy_pass http://app_server;}error_page 500 502 503 504 /500.html;location = /500.html {root /path/to/app/current/public;}}}
信号处理
接下来,我们介绍一下 Gunicorn 服务常用的一些接收信号,主要围绕它的 Master 进程来说明:
- QUIT/INT:快速退出
- TERM:优雅退出
- HUP:重新加载配置,使用新配置启动新的工作进程并优雅地关闭旧的工作进程。 如果应用程序没有预加载(使用 preload_app 选项),Gunicorn 也会加载它的新版本。
- USR1:重新打开一个新的日志文件
例如,我们更新了应用程序的代码后,可以执行如下命令来重新启动服务:
kill -HUP `cat gunicorn.pid`
