基于Gunicorn与Nginx部署Flask

1. 介绍

Flask 是一个 WSGI应用程序。WSGI服务器用于运行应用程序,将传入的 HTTP 请求转换为标准的 WSGI 环境,并将传出的 WSGI 响应转换为 HTTP 响应。

WSGI协议: Web框架致力于如何生成HTML代码,而Web服务器用于处理和响应HTTP请求。Web框架和Web服务器之间的通信,需要一套双方都遵守的接口协议。WSGI协议就是用来统一这两者的接口的。

WSGI容器: 常用的WSGI容器有Gunicorn和uWSGI,但Gunicorn直接用命令启动,不需要编写配置文件,相对uWSGI要容易很多,所以这里我也选择用Gunicorn作为容器。

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

nginx: 使用gunicorn时试图将其绑定到80或者443端口,发现无效。如果想绑定到这些端口,常见的有如下的几种方法:

  • 使用Nginx代理转发。
  • sudo启动gunicorn。
  • 安装额外的程序。

2. 环境

  • OS: Ubuntu 20.04.1
  • Python version: 3.8.10
  • Flask version: 2.0.1
  • Gunicorn version: 20.1.0
  • Nginx version: 1.18.0

3. 安装部署

3.1 python3与pip3安装

1
2
3
4
5
6
7
8
9
# 确定python3已经安装
python3 --version
sudo apt update
# 若python3未安装
sudo apt install python3.8
# 若提示 安装 python3.x-venv 才可创建虚拟环境
sudo apt install python3.8-venv
# 若pip3(python库管理工具)未安装
sudo apt install python-pip

3.2 创建虚拟环境

1
2
3
4
5
mkdir flask-gunicorn # 创建项目文件夹
cd flask-gunicorn/
python3 -m venv menv # 创建虚拟环境**menv**
source ./menv/bin/activate # 激活虚拟环境
deactivate # 退出

3.3 安装flask gunicorn

1
pip3 install flask gunicorn

3.4 创建Flask Web Application

1
mkdir flask-blog && cd flask-blog/

创建html模板文件夹

1
mkdir templates && cd templates/

在模板文件夹下创建index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello</title>
</head>
<body>
<h2>Hello world!</h2>
</body>
</html>

项目根目录下创建app.py脚本

1
2
3
4
5
6
7
8
9
10
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def main():
return render_template("index.html")

if __name__ == "__main__":
app.run(host='0.0.0.0',port=8999,debug=True)

启动应用

1
python3 app.py

3.5 配置Gunicorn

项目根目录下创建wsgi.py文件,作为应用程序的接入点

1
2
3
4
from app import app

if __name__ == "__main__":
app.run()

项目目录结构如下:

1
2
3
4
5
6
.
├── app.py
├── templates
│   └── index.html
└── wsgi.py

启动Gunicorn

option detail
-w 指定服务器worker的个数
–bind 指定接口和端口,接口0.0.0.0将是服务器的公网IP
1
2
3
4
5
6
7
8
9
gunicorn -w 4 --bind 0.0.0.0:8999 wsgi:app
###
[2022-08-30 08:38:34 +0800] [976060] [INFO] Starting gunicorn 20.1.0
[2022-08-30 08:38:34 +0800] [976060] [INFO] Listening at: http://0.0.0.0:8999 (976060)
[2022-08-30 08:38:34 +0800] [976060] [INFO] Using worker: sync
[2022-08-30 08:38:34 +0800] [976062] [INFO] Booting worker with pid: 976062
[2022-08-30 08:38:34 +0800] [976063] [INFO] Booting worker with pid: 976063
[2022-08-30 08:38:34 +0800] [976064] [INFO] Booting worker with pid: 976064
[2022-08-30 08:38:34 +0800] [976065] [INFO] Booting worker with pid: 976065

将Gunicorn配置为系统服务

use Gunicorn as systemd service 将gunicorn修改为系统服务,这样即可通过systemd启停服务,并跟随服务器开机自动启动.

首先,创建一个以.service结尾的 /etc/systemd/system/my-flask.service文件,添加内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=Flask Web Application Server using Gunicorn
After=network.target

[Service]
User=root
Group=root
WorkingDirectory=/home/ec2-user/work/flask-gunicorn/flask-blog
Environment="PATH=/home/ec2-user/work/flask-gunicorn/myenv/bin"
ExecStart=/bin/bash -c 'source /home/ec2-user/work/flask-gunicorn/menv/bin/activate; gunicorn -w 3 --bind unix:/tmp/my-server/ipc.sock wsgi:app'
Restart=always

[Install]
WantedBy=multi-user.target
  • 该系统服务会在network服务up之后启动
  • UserGroup可以根据全选需要适当选择,这里选择了最高权限的root用户用户组,与nginx通信也可只选择www-data用户组
  • WorkingDirecotry指定了 flask web app所在的路径
  • Environment指定了flask web app所需虚拟环境的路径
  • ExecStart包含通过gunicoron启动服务的命令,以及其他所需命令
  • unix:/tmp/my-server/ipc.sock wsgi:app是socket接口,用来与gunicron服务通信(interpersonal communication, IPC), 当服gunicorn启动服务的时候生成该socket文件,服务停止时即删除,也因此将产生的socket文件存放在/tmp目录下。同时,Nginx可以通过这个产生的socket文件与gunicorn通信
1
2
3
mkdir /tmp/flask-server
sudo systemctl enable my-flask --now
sudo systemctl status my-server.service

如果出现任何错误,可以通过journalctl -u my-server.service命令,并按Ctrl+G查看日志输出,可根据错误信息排查问题。

通过Supervisor 配置 Gunicorn(Configure Gunicorn as Supervisor)

除了将Gunicron服务配置为系统守护进程,可以选择Supervisor监控服务

1
2
3
4
5
6
# 先停systemd 守护进程
systemctl stop my-flask
systemctl disable my-flask

sudo apt install supervisor
python3 -m pip install supervisor supervisord-dependent-startup # pip3 安装

修改配置文件/etc/supervisord/supervisord.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[supervisord]
nodaemon=true
pidfile = /tmp/supervisord.pid
logfile = /tmp/supervisord.log
logfile_maxbytes = 10MB
logfile_backups=10
loglevel = debug

[unix_http_server]
file = /tmp/supervisor.sock

[supervisorctl]
serverurl = unix:///tmp/supervisor.sock

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

[include]
files = /etc/supervisord.d/*.conf

同时在/etc/supervisord/supervisord.d/路径下,添加gunicorn.conf配置文件:

1
2
3
4
5
6
7
8
9
[program:flask_catalog] 
command=/bin/bash -c 'source /home/ec2-user/work/flask-gunicorn/menv/bin/activate; gunicorn -w 3 --bind unix:/tmp/my-server/ipc1.sock wsgi:app'
directory=/home/ec2-user/work/flask-gunicorn/flask-blog
user=root
group=root
autostart=true
autorestart=true
stdout_logfile=/tmp/app.log
stderr_logfile=/tmp/error.log
1
2
3
4
5
6
supervisord # 启动
supervisorctl update # 更新配置
supervisorctl status # 查看进程
supervisorctl start xxx # 启动某个进程
supervisorctl stop xxxx # 停止某个进程
supervisorctl stop all

3.6 配置Nginx

1
sudo apt install nginx

Nginx相关文件存放在/etc/nginx/nginx路径下,需要修改nginx配置文件,将nginx作为flask web app 的代理。nginx.conf是主要配置文件,通常开发人员或系统管理员不会修改此配置文件,新的配置文件会在sites-available/目录下修改,并链接到/sites-enabled/目录下。因此可在sites-available/目录下创建配置文件my-flask

1
2
3
4
5
6
7
8
server {
listen 8999;

location / {
include proxy_params;
proxy_pass http://unix:/tmp/my-server/ipc1.sock;
}
}

执行 nginx -t 确保配置文件语法正确,无误之后建立符号链接

1
2
3
4
sudo nginx -t
sudo ln -s /etc/nginx/sites-available/my-flask /etc/nginx/sites-enabled/
sudo ls -l /etc/nginx/sites-enabled/

1
nginx -s reload

以上配置使nginx监听8999端口,代理连接生成的socket文件,gunicorn可以从socket文件读取数据并允许flask web app 产生响应,然后gunicorn将flask web app的响应的数据写到socket文件,提供给nginx读取并返回用户

验证nginx代理请求,http://ip:8999,会得到响应

配置Nginx服务域名

通过server_name给服务添加域名,添加一行配置如下:

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name server.example.com; ## 添加

location / {
include proxy_params;
proxy_pass http://unix:/tmp/my-server/ipc1.sock;
}
}

然后在/etc/hosts添加域名解析,或者搭建配置DNS服务器便于查找域名

1
2
...
192.168.0.188 server.example.com

故障排查

  • sudo journalctl -u SERVICE 检查系统服务相关的错误
  • 查看supervisord日志文件,提供了stderr_logfilestdout_logfile,可以查看相关的错误
  • /var/log/nginx/access.log and /var/log/nginx/error.log可查看nginx相关错误

总结

介绍了采用Gunicorn和Nginx部署flask应用的过程,利用gunicorn和nginx的并发性可以简单的对其配置文件进行扩展。

参考

https://www.golinuxcloud.com/flask-gunicorn-nginx/

https://wizardforcel.gitbooks.io/the-way-to-flask/content/chapter013.html