在 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 命令。
有关这种东西该如何配置,可以自行去 gunicorn 和 supervisor 的官网上去看哈。
创建 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。到此,所有的过程已经完成。