在生产环境中使用 Docker 部署 Flask 服务

用 Anaconda 开启一个新的环境

创建一个新的 conda 环境,并取名为 flask

1
conda create --name flask python=3

激活 flask 环境

1
conda activate flask

开启一个简单的 Flask 服务

用 pip 安装 Flask

1
pip install flask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# main.py
from flask import Flask, request, jsonify

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

@app.route("/hello", methods=["GET"])
def hello_world():
name = request.args.get("name", "")
return jsonify({
"err_code": 0,
"ret": f"Hello, {name}"
})

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

运行程序,得到如下输出结果:

1
2
3
4
5
 * Serving Flask app 'main'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit

然后浏览器打开:

1
http://127.0.0.1:5000/hello?name=AI探险家

就会从浏览器中得到如下结果:

1
{"err_code":0,"ret":"Hello, AI探险家"}

从前面开启服务的输出结果可以看出,我们的 Flask 应用目前是一个开发服务。 要正式部署的话,建议使用 WSGI。那我们就接受建议,使用 uWSGI 搭配 nginx 来部署服务搭配。

安装 uWSGI

首先安装 uWSGI 相关的依赖环境。下面是 Ubuntu 系统下的安装命令

1
apt-get install build-essential python-dev

其他操作系统可以参考官方安装教程:Installing uWSGI

接下来是安装 uwsgi。理论上可以直接用 pip 安装。

1
pip install uwsgi

奇怪的是,我这边在 Anaconda 环境中用 pip 安装 uwsgi 会报错。用 conda 命令来安装则是正常的:

1
conda install -c conda-forge uwsgi

配置 uWSGI

main.py 同个目录创建一个 uwsgi.ini 文件:

1
2
3
4
5
6
7
8
9
10
11
[uwsgi]
module = main:app

uid = www-data
gid = www-data
master = true
processes = 5

socket = /tmp/uwsgi.socket
chmod-sock = 664
vacuum = true

在这份配置文件中,调用的模块是 main.pyapp。然后使用 www-data(WEB 服务的标准用户) 作为 uwsgi 进程的 uid/gid。 通过 processes 指定 5 个进程。另外,我们给 uwsgi 创建了一个 socket 文件 /tmp/uwsgi.socket,并赋予 664 的执行权限。 作为对比,也可以直接给 socket 设置端口号,例如:、

1
2
3
4
[uwsgi]
...
socket = :3032
...

另外,当我们退出进程时,希望 /tmp/uwsgi.socket 文件能够被自动删除,因此可以设置 vacuum = true 来实现这个功能。

运行 uwsgi

1
uwsgi uwsgi.ini

得到如下输出结果:

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
[uWSGI] getting INI configuration from uwsgi.ini
*** Starting uWSGI 2.0.21 (64bit) on [Tue Apr 18 21:13:37 2023] ***
compiled with version: 11.2.0 on 28 March 2023 07:32:14
os: Linux-5.15.90.1-microsoft-standard-WSL2 #1 SMP Fri Jan 27 02:56:13 UTC 2023
nodename: Airme
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 20
current working directory: /home/aizpy/Workspace/FlaskTest
detected binary path: /home/aizpy/miniconda3/envs/flask/bin/uwsgi
your processes number limit is 63631
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uwsgi socket 0 bound to UNIX address /tmp/uwsgi.socket fd 3
Python version: 3.11.2 (main, Mar 27 2023, 23:42:44) [GCC 11.2.0]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0xa01a98
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 437520 bytes (427 KB) for 5 cores
*** Operational MODE: preforking ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0xa01a98 pid: 13221 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 13221)
spawned uWSGI worker 1 (pid: 13222, cores: 1)
spawned uWSGI worker 2 (pid: 13223, cores: 1)
spawned uWSGI worker 3 (pid: 13224, cores: 1)
spawned uWSGI worker 4 (pid: 13225, cores: 1)
spawned uWSGI worker 5 (pid: 13226, cores: 1)

确实创建了 5 个进程。当输入Ctrl-C 来退出进程时,可以看到如下输出:

1
2
3
4
5
6
7
8
^CSIGINT/SIGTERM received...killing workers...
worker 1 buried after 1 seconds
worker 2 buried after 1 seconds
worker 3 buried after 1 seconds
worker 4 buried after 1 seconds
worker 5 buried after 1 seconds
goodbye to uWSGI.
VACUUM: unix socket /tmp/uwsgi.socket removed.

此时,5 个进程全部退出了,并且 /tmp/uwsgi.socket 文件也被删除了。

生成依赖环境

1
pip freeze > requirements.txt

需要注意的是,如果你是通过 conda 安装的 uwsgi,那么执行这个命令后,uWSGI 这一条会变成类似这样的结果:

1
uWSGI @ file:///croot/uwsgi_1679988297904/work

这是一个绝对路径,无法部署到其他机器上。此时可以根据之前 conda 的安装结果,来指定 uwsgi 的版本号。例如:

1
uWSGI==2.0.21

最终的 requirements.txt 长这样:

1
2
Flask==2.2.3
uWSGI==2.0.21

这里删掉了中间的依赖项,因为安装这两个库就会自动安装其他的。

配置 nginx

创建一份 nginx.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
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
worker_connections 1024;
use epoll;
multi_accept on;
}

http {
access_log /dev/stdout;
error_log /dev/stdout;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

index index.html index.htm;

server {
listen 80 default_server;
listen [::]:80 default_server;
server_name localhost;
root /var/www/html;

location / {
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.socket;
}
}
}

创建执行脚本

创建一个启动脚本 start.sh

1
2
3
#!/usr/bin/env bash
service nginx start
uwsgi --ini uwsgi.ini

编写 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM ubuntu:22.04
MAINTAINER aizpy "aizpy@outlook.com"

EXPOSE 80
COPY . /srv/app
WORKDIR /srv/app

RUN apt-get clean && apt-get -y update
RUN apt-get install -y nginx python3-dev build-essential pip

RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

COPY nginx.conf /etc/nginx
RUN chmod +x ./start.sh
CMD ["./start.sh"]

通过 Dockerfile 创建镜像

1
docker build . -t my_flask_app

生成容器并运行

1
docker run --name flask_app -p 80:80 my_flask_app

参考


在生产环境中使用 Docker 部署 Flask 服务
https://aizpy.com/2023/04/18/flask-uwsgi-docker/
作者
aizpy
发布于
2023年4月18日
许可协议