目标

在虚拟环境(virtual environment)中运行 Flask 应用,由 Gunicorn 托管,通过 Nginx 进行反向代理最后由 Systemd 管理进程。

Why

在使用 Python 时,我们可能会在不同项目中使用同一个软件包或模块的不同版本,如果直接安装必然会导致冲突。而解决方案是通过创建一个虚拟环境,其中安装有特定的 Python 版本和其他包,我们就可以在不同的虚拟环境中运行各个项目而不产生冲突。

而 Flask 只是一个 web 框架,直接运行的 web 服务只适合开发使用,在生产环境中无法承受大量的并发请求,所以我们使用 Gunicorn 拉起 Flask 应用并由 Nginx 转发 Gunicorn,并且 Nginx 还能托管项目中的静态文件,减小了服务器压力。而且 Nginx 还能让我们使用域名通过 80 或 443 端口访问,而不用指定特定端口。

How

虚拟环境(virtual environment)

进入项目根目,创建虚拟环境

python3 -m venv venv

接下来执行

source env/bin/activate

这样终端前面应该会有一个 (venv) 标识,表示我们已经进入了虚拟环境。此时我们需要重新安装之前的各种软件包,并且在这里安装的软件包只会对该虚拟环境有效。

最后可以使用该命令来退出虚拟环境

deactivate

Gunicorn

Gunicorn 是一个高性能的 Python WSGI UNIX HTTP 服务器。可以直接通过 pip 进行安装。

$ pip install gunicorn
$ cat 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])
$ gunicorn -w 4 myapp:app
[2014-09-10 10:22:28 +0000] [30869] [INFO] Listening at: http://127.0.0.1:8000 (30869)
[2014-09-10 10:22:28 +0000] [30869] [INFO] Using worker: sync
[2014-09-10 10:22:28 +0000] [30874] [INFO] Booting worker with pid: 30874
[2014-09-10 10:22:28 +0000] [30875] [INFO] Booting worker with pid: 30875
[2014-09-10 10:22:28 +0000] [30876] [INFO] Booting worker with pid: 30876
[2014-09-10 10:22:28 +0000] [30877] [INFO] Booting worker with pid: 30877

其中 -w 为工作进程的数量,还可以通过 -d 0.0.0.0:8000 来指定端口号。

Nginx

创建一个 Nginx 配置文件,其中增加一段来进行反向代理。

location / {
    proxy_pass         http://localhost:8000/;
    proxy_redirect     off;

    proxy_set_header   Host             $http_host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
}

其中 proxy_pass 则为 Gunicorn 运行时所指定的地址和端口。

Systemd

最开始我不知道如何通过 Systemd 在虚拟环境中来运行项目,因为要预先进入虚拟环境再运行,后来在 Stack Overflow 找到了答案:How to enable a virtualenv in a systemd service unit?,直接通过 env/bin/ 中的 Python 解释器运行,会自动进入虚拟环境,无需额外手动进入。所以我们可以在虚拟环境中安装 Gunicorn 来实现我们的目标。

Systemd 默认从 /etc/systemd/system/ 读取配置文件,最简单的配置文件大致是这个样子。

[Unit]
Description=TEST
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/wwwroot/test/
ExecStart=/home/wwwroot/test/venv/bin/gunicorn -w 4 test:app -b 0.0.0.0:8000
Restart=on-failure

[Install]
WantedBy=multi-user.target

其中 [Unit] 通常为配置文件的第一个区块,用于定义 Unit 的元数据以及配置与其他 Unit 的关系。其中的 Description 为该 Unit 的描述,After 表示该字段中的 Unit 必须在此 Unit 之前启动。

[Service] 用于定义 Service 的配置,只有 Service 类型的 Unit 才有这个区块。

Type 定义启动时的进程行为。simple 为默认值,执行 ExecStart 指定的命令,启动主进程。

WorkingDirectory 为项目所在目录。

ExecStart 就是启动项目的命令。

Restart=on-failure 表示在失败时重启当前服务。

[Install] 通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动。

具体内容可以参考 Systemd 入门教程:命令篇

保存配置文件后重载 daemon

systemctl daemon-reload

这样我们就完成了所有的配置工作。之后就可以通过 systemctl start test.service 来运行我们的项目了。

If you think my article is useful to you, please feel free to appreciate