使用gunicorn+gevent作为web服务器启动flask应用
首先需要明白一个概念, Flask
和Django
等web框架严格意义上来说只是应用程序的框架, 主要是用来处理业务逻辑的.
而前端浏览器与后端web框架
之间是通过请求来沟通的, 这个专门解析和发送请求的中间桥梁叫做web服务器
, 在python中也叫作WSGI(Web Server Getway Interface) server
.
WSGI
是一种在python中规定的web服务器
与web应用框架
之间通信的协议, 能够支持这种协议的web服务器就叫做WSGI server
, Flask和Django都自带了WSGI(Web Server Getway Interface) server, 其自带的启动命令flask run
和python manager.py runserver
就是使用了WSGI server来启动项目的.
但是这种web 框架自带的WSGI server只能用来开发测试, 因为性能等以下问题, 不适合在最终的生产环境中使用, 所以一般我们会使用gunicorn
或者uWSGI
作为线上的WSGI server. 两者都可以使用, 具体区别可以自行百度, 这里我们使用gunicorn
, 当然前面也可以再加一台nginx
, 或者再加上supervisor
对后台程序进行监控, 这里就不再扩展了.
下载gunicorn和gevent
pip install gunicorn gevent
在虚拟环境中下载gunicorn, 由于gunicorn默认使用的是同步阻塞的网络模型, 因此为了提高并发能力, 可以将其设置为gevent模式. 通过协程提高并发量, 因此也需要下载gevent库
配置gunicorn
可以直接在gunicorn的运行命令中添加参数实现设置, 但是一般建议通过一个单独的配置文件来配置.
我们在ihome项目的根目录下创建配置文件gunicorn.conf.py
# gunicorn.conf.py
# 定义同时开启的处理请求的进程数量,根据网站流量适当调整
workers = 5
# 采用gevent库,支持异步处理请求,提高吞吐量
worker_class = "gevent"
# 绑定IP和端口
bind = "0.0.0.0:5000"
# 使用后台守护进程的模式启动,若开启则docker启动时容器会直接退出
# daemon = "true"
# 日志设置
# 设置gunicorn访问日志格式,错误日志无法设置
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
# 访问日志的存储文件, 当前路径的logs目录下
accesslog = "logs/gunicorn_access.log"
# 错误日志的存储文件, 当前路径的logs目录下
errorlog = "logs/gunicorn_error.log"
# 日志记录等级
loglevel = 'info'
注:
- 后台守护模式可以在手动使用gunicorn启动命令的情况下开启, 如果是使用docker启动的话, 开启之后会直接退出容器, 所以这种情况下不要开启后台守护模式
gunicorn记录flask的日志
上面的配置文件中介绍了日志的配置
-
日志信息设置存放在了当路径的logs目录下
-
此时只会记录gunicorn的日志, 而flask程序中手动写的记录日志
current_app.logger.error(e)
并不会存入到gunicorn设置的日志文件中, 所以需要将flask程序中的日志也记录到gunicorn日志文件中. 在flask的启动文件manager.py
中, 添加gunicorn的日志记录器from flask_migrate import Migrate from ihome import create_app from ihome import db import logging app = create_app('dev') # 配置日志信息 if __name__ != '__main__': # 使用gunicorn启动时, 将flask应用中的日志绑定到gunicorn的日志配置中 gunicorn_logger = logging.getLogger('gunicorn.error') app.logger.handlers = gunicorn_logger.handlers app.logger.setLevel(gunicorn_logger.level) # 创建迁移对象 migrate = Migrate(app, db)
当使用gunicorn启动时,
manager.py
会被当做一个模块被导入, 所以__name__ != '__main__'
是成立的. 创建一个gunicorn的日志器, 将flask app的日志器与gunicorn绑定, 这样flask程序中的日志也会记录到gunicorn日志文件中. -
之前我们在
ihom/__init__.py
中设置的flask日志管理, 也同样会生效, 即也会把日志记录在了logs/log.log
文件中, 所以两个日志关系器都会有效, 如果不需要的话可以把ihom/__init__.py
中的日志关系器注释掉
启动gunicorn
(flask) alex@alex:~/python/FlaskIhome$ gunicorn manager:app -c gunicorn.conf.py
在项目根目录运行启动命令, manager为项目入口启动文件, app为启动文件中创建的app名字, -c
为设置配置文件的参数名. 如果运行命令后没有任何报错, 则说明运行成功了, linux中没有消息就是最好的消息
查看gunicorn进程运行情况
运行ps -ef | grep gunicorn
命令查看进程:
(flask) alex@alex:~/python/FlaskIhome$ ps -ef | grep gunicorn
alex 42217 1 0 21:41 ? 00:00:00 /home/alex/.virtualenvs/flask/bin/python /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
alex 42220 42217 0 21:41 ? 00:00:00 /home/alex/.virtualenvs/flask/bin/python /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
alex 42221 42217 0 21:41 ? 00:00:00 /home/alex/.virtualenvs/flask/bin/python /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
alex 42222 42217 0 21:41 ? 00:00:00 /home/alex/.virtualenvs/flask/bin/python /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
alex 42223 42217 0 21:41 ? 00:00:00 /home/alex/.virtualenvs/flask/bin/python /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
alex 42224 42217 0 21:41 ? 00:00:00 /home/alex/.virtualenvs/flask/bin/python /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
或者pstree -ap | grep gunicorn
命令查看进程树, 这样进程间的父子关系展示的更直观
(flask) alex@alex:~/python/FlaskIhome$ pstree -ap | grep gunicorn
|-gunicorn,42217 /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
| |-gunicorn,42220 /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
| | `-{gunicorn},42225
| |-gunicorn,42221 /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
| | `-{gunicorn},42226
| |-gunicorn,42222 /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
| | `-{gunicorn},42227
| |-gunicorn,42223 /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
| | `-{gunicorn},42229
| `-gunicorn,42224 /home/alex/.virtualenvs/flask/bin/gunicorn manager:app -c gunicorn.conf.py
| `-{gunicorn},42228
| | |-grep,48392 gunicorn
可以看到进程号42217
是gunicorn的主进程, 下面的5个子进程就是根据gunicorn的配置文件中的worker
参数配置的. gunicorn的工作模式就是主进程主要管理worker
子进程, 子进程才是真正解析和处理请求的worker
停止gunicorn
直接运行kill 主进程号
就可以停止gunicorn了, 子进程会跟着自动停止. 也可以通过额外配置supervisor
来启停gunicorn. 这里就不介绍了.
使用 Docker 封装 Flask 应用
首先需要下载安装docker, 这里就不赘述了, 可以查看官网: https://docs.docker.com/engine/install/
生成requirements.txt
在项目的根目录生成该项目需要安装的python包和环境信息文件.
(flask) alex@alex:~/python/FlaskIhome$ pip freeze >> requirements.txt
这一步在python项目迁移时必须要做, 通常需要在新的机器上执行如下命令将该项目需要安装的环境信息都安装好
pip install -r requirements.txt
但是这里我们由于是封装在docker中的, 所以不需要手动执行pip
安装命令
创建Dockerfile
在项目的根目录创建文件Dockerfile
, 一般命名规定就为这个, 因为在docker build
时其会自动去寻找运行build
命令的当前路径下的Dockerfile
文件
FROM python:3.7
MAINTAINER alex<g1242556827@163.com>
WORKDIR /Project/FlaskIhome
COPY requirements.txt ./
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
EXPOSE 5000
COPY . .
CMD ["gunicorn", "manager:app", "-c", "./gunicorn.conf.py"]
注:
-
FROM python:3.7
是基础镜像, 我们的项目是在python3.7下开发的, 所以基于该镜像 -
MAINTAINER
是只作者或者维护者的信息 -
WORKDIR
是指进入容器后的工作目录, 如果容器中没有该目录会自动创建. 配合后面的COPY . .
意思是把当前Dockerfile所在的目录下的所有文件复制到WORKDIR
的路径下, 也就是把WORKDIR
作为了容器中的项目目录. 下面的例子就是后续docker运行起来后, 进入docker终端的示例, 默认就是进入了WORKDIR
路径(root@Aliyun-Alex:/home/alex/python/docker_ihome)# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f2f48daf07ea registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:3.0 "gunicorn manager:ap…" 13 hours ago Up 13 hours 0.0.0.0:5000->5000/tcp docker_ihome_web_1 d1a43dbca2c4 registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:3.0 "celery -A ihome.cel…" 13 hours ago Up 13 hours 5000/tcp docker_ihome_worker_1 (root@Aliyun-Alex:/home/alex/python/docker_ihome)# docker exec -it f2f48daf07ea /bin/bash root@f2f48daf07ea:/Project/FlaskIhome# ls Dockerfile README.md __pycache__ config.py docker-compose.yml gunicorn.conf.py ihome logs manager.py migrations requeirements.py requirements.txt single_manager.py root@f2f48daf07ea:/Project/FlaskIhome# pwd /Project/FlaskIhome
-
COPY requirements.txt ./
是指将当前目录下的requirements.txt
复制到docker容器的WORKDIR
下 -
RUN pip install -r requirements.txt
是指docker在启动后, 会自动运行pip命令把所需的环境安装好 -
EXPOSE 5000
是只docker容器向外暴露的5000端口号 -
CMD ["gunicorn", "manager:app", "-c", "./gunicorn.conf.py"]
是指docker启动后, 自动运行gunicorn的启动命令, 即启动了容器就会启动其中的flask项目 -
Dockerfile的其他编写关键字可以查看官网
build构建镜像
在Dockerfile
的路径中(项目根目录)运行命令
sudo docker build -t 'flaskihome' .
注:
-
该命令会默认寻找当前目录的名为
Dockerfile
的文件, 也可以使用-f
指定具体的Dockerfile -
-t
是指--tag list
, 给镜像命名name:tag
, 未指定:
后面的tag
则默认为latest
-
最后的
.
不要忘记了 -
这一步构建会花一点时间, 因为要下载python基础镜像和pip包等, 构建完成之后截图如下
查看构造的镜像
(flask) alex@alex:~/python/FlaskIhome$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flaskihome latest ee00fc32da96 6 minutes ago 1.06GB
上传镜像
可以将镜像上传至Docker Hub
官网, 由于官网上传下载比较慢, 所以这里选择上传到阿里云的容器镜像服务
中. 具体步骤可以查看我的另一篇博客总结 Docker-基础005-发布自己的镜像, 简单来说就是进入容器镜像服务->创建命名空间->创建镜像仓库
, 创建好镜像仓库后, 可以在基本信息
页签中查看操作指南
, 告诉你如何上传拉取镜像
1. 登录阿里云Docker Registry
$ sudo docker login --username=alex马上读初一 registry.cn-shanghai.aliyuncs.com
用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。
2. 从Registry中拉取镜像
$ sudo docker pull registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:[镜像版本号]
3. 将镜像推送到Registry
$ sudo docker login --username=alex马上读初一 registry.cn-shanghai.aliyuncs.com
$ sudo docker tag [ImageId] registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:[镜像版本号]
$ sudo docker push registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:[镜像版本号]
在另一台机器中拉取并运行镜像
首先同样需要安装docker, 根据上面的指示拉取镜像, 然后运行命令即可启动容器同时启动flask项目, -d
为后台守护进程运行
# 启动容器
sudo docker run -d -p 5000:5000 镜像ID
# 停止容器
sudo docker stop 容器ID
在容器中启动celery
由于容器启动后, 只启动了flask应用, 本项目中还需要启动celery的worker, 因此需要进入容器终端启动celery
# 运行命令进入容器终端
sudo docker exec -it 2bc65dc10f2 /bin/bash
# 启动celery命令
celery -A ihome.celery_tasks.main worker -l=info
使用 Docker-compose 封装 flask + celery
编写容器的docker-compose file
由于上面还需要手动进入容器启动celery, 我们可以使用docker-compose
来编排docker, 启动多个命令
回到之前编写的flask项目目录, 创建docker-compose
的配置文件docker-compose.yml
version: '3.8'
services:
web:
build: .
ports:
- "5000:5000"
worker:
build: .
command: "celery -A ihome.celery_tasks.main worker -l=info"
注:
-
docker-compose的配置文件编写语法可以查看官网: https://docs.docker.com/compose/compose-file/
-
version
参数需要根据docker的版本填写, 对应关系可以查看上面的官网, 这里的docker engine版本为19.03.12
, 所以选择3.8
-
这里定义了两个
services
,web
为flask应用,worker
为celery的worker, 需要添加celery的运行命令celery -A ihome.celery_tasks.main worker -l=info
build构建镜像
在docker-compose.yml
的路径中(项目根目录)运行命令, 注意后面没有.
sudo docker-compose build
构建完成后, 查看镜像, 发现其构建了两个镜像flaskihome_web
和flaskihome_worker
, 但是这两个的镜像ID是一样的, 说明用的是同一份代码
(flask) alex@alex:~/python/FlaskIhome$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flaskihome_web latest 6fa5f85e885a About a minute ago 1.06GB
flaskihome_worker latest 6fa5f85e885a About a minute ago 1.06GB
上传镜像
根据上面的阿里云容器镜像服务
的指南, 上传镜像
# 打tag
sudo docker tag 6fa5f85e885a registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:1.0
# 上传
sudo docker push registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:1.0
在另一台机器中编写docker-compose file启动容器
在合适的位置创建自定义项目目录docker_ihome
, 并在目录下创建配置文件docker-compose.yml
version: '3.8'
services:
web:
image: registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:1.0
ports:
- "5000:5000"
worker:
image: registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:1.0
command: "celery -A ihome.celery_tasks.main worker -l=info"
注:
- 和前面的
docker-compose file
内容类似, 只是把build
参数改成了image
参数
启动docker-compose
执行命令, 运行docker-compose
, -d
是后台守护进程运行
$ sudo docker-compose up -d
Starting docker_ihome_web_1 ... done
Starting docker_ihome_worker_1 ... done
可以发现两个服务已经启起来了, 使用sudo docker ps
可以查看正在启动的docker
(root@Aliyun-Alex:/home/alex/python/docker_ihome)# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
66af64b043ee registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:1.0 "celery -A ihome.cel…" 6 minutes ago Up About a minute 5000/tcp docker_ihome_worker_1
5d8773442c3d registry.cn-shanghai.aliyuncs.com/alexgong/flaskihome:1.0 "gunicorn manager:ap…" 6 minutes ago Up About a minute 0.0.0.0:5000->5000/tcp docker_ihome_web_1
浏览flask网站
访问该机器的5000端口, 发现可以正常访问项目网站