[Python] Flask 的 Gunicorn 的 Dcoker 包装方法


一步一步的实现将 Flask 包装成 Gunicorn 的 Docker Container

Posted by Yufan Zheng on August 15, 2018

在 Python Web 的开发过程中,经常的我们会因为图方便,使用 flask 这种非常简单并且容易上手的 web server,但 flask 有一个很致命的缺点就是是阻塞,即是说,如果有两个人同时调用 server 的 request 的话,server 就会卡住了,返回不了任何东西,为了防止阻塞,一般的解决办法要么是使用 gevent 在 flask server 外面包一层,或者是使用 gunicorn。

Gevent 的 server 实际上仍然还是一个单一 server,但是不再是同时处理就会卡掉了,而是有一个队列的处理,所以处理会变慢,但不会卡。Gunicorn 则会将原先的 server 复制几个,做到真正的并行处理。

创建一个简单的 flask app

首先我们需要安装 python 的外部依赖,在 requirements.txt 中中加入

flask
eventlet
gunicorn

执行pip install -r requirements.txt 来进行安装。

创建一个简单的 app.py 文件,注意,这里的 host 一定要设定成 ‘0.0.0.0‘,否则在部署的时候可能会有问题。

from flask import Flask

app = Flask(__name__)


@app.route('/index')
def index():
    return 'Hello, world'


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

这个简单的 app.py 实现了一个显示 hello word 的功能。当然,之后你也可以修改加入不同的 API 功能。

文件结构

在正式的写各种脚本之前,我们首先来看一下项目的文件结构

| - bin
  | - create_image.sh
  | - run_container.sh
| - conf
   | - gunicorn.conf
   | - supervisor.conf
| - scripts
   | - flask.sh
   | - gunicorn.sh
   | - kill.sh
   | - restart.sh
| - Dockerfile
| - app.py
| - requirements.txt  

接下来,我们会一个一个的对这些文件还有脚本进行分析和解释。

启动 flask 的脚本 flask.sh

#!/usr/bin/env bash

cd ../
export FLASK_APP=app.py
# export FLASK_DEBUG=FALSE
flask run --port=5000 --host=0.0.0.0

这个脚本会将 app.py 文件中的 app 导出出来,用 flask command 来启动 server。它可以作为一个测试脚本,当你的 server 部署上去的时候,如果碰到问题,debug 想知道是自己的 server 本身代码有问题,还是 gunicorn 的部署问题,就需要一个单机测试的脚本在这儿,启动 flash.sh,看是否 OK。

启动 gunicorn 的脚本 gunicorn.sh

#!/usr/bin/env bash

# Kill previous process
bash kill.sh
# Start Process
cd ../
# gunicorn -c conf/gunicorn.conf app:wise_influencer_app
gunicorn -c conf/gunicorn.conf app:app

当 flask 单进程 OK 的时候,就可以考虑 gunicorn 的多进程了,首先我们将之前启动的 app 都杀死掉,然后利用 gunicorn 来启动多个核的 server。这个里面有提到一个 kill 的 脚本,它的内容如下:

#!/usr/bin/env bash

# Process Name
PROCESS=app
# Kill previous process
for pid in $(ps aux | grep $PROCESS | awk '{print $2}')
do
  if [ "$pid" != "" ]
  then
    echo "killing previous process: $pid"
    kill -9 $pid
  fi
done

kill 会杀死之前的进程,然后再开始创建。

重启 gunicorn 的脚本 restart.sh

restart.sh 当中会首先检查和安装 dependency,然后启动 gunicorn。

#!/usr/bin/env bash

echo "Checking dependencies with pip..."
pip install -r ../requirements.txt
bash gunicorn.sh

这一些脚本在创建 container 的时候会用到,同时,当 server 已经部署到 container 当中的时候,你想要通过 interactive 的方式进入到里面去重启 server 或者 debug 的时候,你会发现他们是非常的有用的。

创建 Dockerfile

Dockerfile 是用来 build 这个 container 会使用的 image 的。我们使用 python 3.6 的 image 作为base。最后利用 supervisor 来进行启动 server。利用 supervisor 的好处是,首先是可以 monitor 当前的状态,然后是在 start container 的时候,就会自动的启动这项 server 的服务了。

FROM python:3.6.5-slim
MAINTAINER Yufan Zheng 
LABEL Description="Gunicorn Docker Example"

# Install vim, supervisord and git
RUN apt-get -y update
RUN apt-get -y install vim
RUN apt-get -y install supervisor
RUN apt-get -y install git

# Make Directory for app
RUN mkdir /home/gunicorn_docker
WORKDIR /home/gunicorn_docker
# Copy code inside
COPY . /home/gunicorn_docker

# Install requirements
RUN pip install -r /home/gunicorn_docker/requirements.txt

# Supervisor config (auto start)
ADD conf/supervisor.conf  /etc/supervisor/conf.d/

# Unblock port 5000 for the Flask app to run on
EXPOSE 5000

# execute the gunicorn app
CMD ["supervisord", "-n"]

可以看到,这里的 Docker 当中利用 supervisor 来启动 gunicorn。两个启动的配置文件分别是:

  • conf/gunicorn.conf
workers = 2
worker_class = 'eventlet'
bind = '0.0.0.0:5000'
timeout = 1200
pidfile = 'gunicorn.pid'

这里的 workers 可能是你需要改的地方,看你想复制几个 flask 的 core。越多当然就越吃资源啦。

  • conf/supervisor.conf
[program:app]
directory=/home/gunicorn_docker/scripts
command=/bin/bash restart.sh
autostart=true
autorestart=false
startsecs=0
startretries=3
user=root

这里 supervisor 的文件基本就是进入到 scripts directory 然后执行我们的 restart.sh 命令。

有关这种东西该如何配置,可以自行去 gunicornsupervisor 的官网上去看哈。

创建 image 和启动 container

最后,可以创建两个 scripts 来生成 image 和启动 container了。

  • create_image.sh
#!/usr/bin/env bash
cd ../
docker build -t gunicorn_docker .

这里-t是为了能给 container 一个名字。方便启动和查看

  • create_image.sh
#!/usr/bin/env bash

docker rm -f gunicorn_docker
docker run -d -p 5000:5000 --name gunicorn_docker gunicorn_docker

在 background mode 在启动 gunicorn docker。到此,所有的过程已经完成。

Reference

源码传送门和使用说明。