Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

使用gunicorn+gevent作为web服务器启动flask应用

首先需要明白一个概念, FlaskDjango等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 runpython 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

Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

# 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'

注:

  1. 后台守护模式可以在手动使用gunicorn启动命令的情况下开启, 如果是使用docker启动的话, 开启之后会直接退出容器, 所以这种情况下不要开启后台守护模式

gunicorn记录flask的日志

上面的配置文件中介绍了日志的配置

  1. 日志信息设置存放在了当路径的logs目录下

  2. 此时只会记录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日志文件中.

  3. 之前我们在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"]

注:

  1. FROM python:3.7是基础镜像, 我们的项目是在python3.7下开发的, 所以基于该镜像

  2. MAINTAINER是只作者或者维护者的信息

  3. 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
    

    Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

  4. COPY requirements.txt ./是指将当前目录下的requirements.txt复制到docker容器的WORKDIR

  5. RUN pip install -r requirements.txt是指docker在启动后, 会自动运行pip命令把所需的环境安装好

  6. EXPOSE 5000是只docker容器向外暴露的5000端口号

  7. CMD ["gunicorn", "manager:app", "-c", "./gunicorn.conf.py"]是指docker启动后, 自动运行gunicorn的启动命令, 即启动了容器就会启动其中的flask项目

  8. Dockerfile的其他编写关键字可以查看官网

build构建镜像

Dockerfile的路径中(项目根目录)运行命令

sudo docker build -t 'flaskihome' .

注:

  1. 该命令会默认寻找当前目录的名为Dockerfile的文件, 也可以使用-f指定具体的Dockerfile

  2. -t是指--tag list , 给镜像命名name:tag, 未指定:后面的tag则默认为latest

  3. 最后的.不要忘记了

  4. 这一步构建会花一点时间, 因为要下载python基础镜像和pip包等, 构建完成之后截图如下

    Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

查看构造的镜像

(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:[镜像版本号]

Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

在另一台机器中拉取并运行镜像

首先同样需要安装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

Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

使用 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"

注:

  1. docker-compose的配置文件编写语法可以查看官网: https://docs.docker.com/compose/compose-file/

  2. version参数需要根据docker的版本填写, 对应关系可以查看上面的官网, 这里的docker engine版本为19.03.12, 所以选择3.8

  3. 这里定义了两个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_webflaskihome_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"

注:

  1. 和前面的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端口, 发现可以正常访问项目网站

Flask-爱家租房项目ihome-11-Docker-compose部署flask+celery

上一篇:在具有Nginx和Gunicorn的生产环境中,使用unix套接字或tcp绑定它们是否更好?


下一篇:docker容器化python服务部署(supervisor-gunicorn-flask)