一个简单的Docker+Gunicorn+Flask示例

使用Docker部署有诸多好处,flask程序也通常需要搭配一个高性能的wsgi容器,今天就记录一下在使用docker+gunicorn+flask过程中的一些坑,错误之处欢迎指正。

一个简单的demo(宿主机为ubuntu18.04),先来看目录结构:

一个简单的Docker+Gunicorn+Flask示例_第1张图片 目录结构

即 myweb/src/为flask工程路径,与src目录同级的Dockerfile、run.sh文件分别用于构建docker镜像和docker-entrypoint脚本,requirement.txt为flask所需依赖

再来看一下Dockerfile:

FROM centos
WORKDIR /var/jenkins_home/workspace

# 添加文件
ADD src /var/jenkins_home/workspace/src
ADD run.sh /var/jenkins_home/workspace/
ADD Dockerfile /var/jenkins_home/workspace/
ADD requirements.txt /var/jenkins_home/workspace/

# 时区设置
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone

# 安装pip及flask依赖
RUN yum -y install epel-release
RUN yum -y install python-pip
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

# 安装gunicorn
RUN pip install gunicorn -i https://pypi.tuna.tsinghua.edu.cn/simple
# 建日志目录
RUN mkdir -p /var/jenkins_home/workspace/log/

RUN chmod +x /var/jenkins_home/workspace/run.sh
# 声明数据卷挂载点
VOLUME /var/jenkins_home/workspace
# 启动命令
ENTRYPOINT ["./run.sh"]
# 容器暴露端口
EXPOSE 8000

Dockerfile基本命令的解释文档有很多,可自行搜索,本人参考的文章:Dockerfile基本命令解释,上面的Dockerfile大意是把当前路径下的文件及文件夹添加到容器的"/var/jenkins_home/workspace/"下,安装依赖,声明挂载点:

  1. Dockerfile中的ADD命令,宿主机地址可以是相对路径,但映射到容器内最好是绝对路径。
  2. ADD命令添加单个文件时,如可以使用 ADD demo.py /var/mypath 或者 ADD demo.py /var/mypath/ 即可把文件添加到 mypath路径下,但是添加文件夹时,需要在容器内指定同名的文件夹,如添加 src文件夹要是用: ADD src /var/mypath/src
  3. docker中时间默认是UTC时间,比北京时间慢8小时,如上面所示设置时区
  4. 若基础镜像包里面有pip命令,即可不用再次安装,直接删除Dockerfile中对应的两行,另外pip安装依赖时,可以用 -i指定国内的镜像源,速度可能会快一些,我用的清华镜像源: -i https://pypi.tuna.tsinghua.edu.cn/simple
  5. docker-enterpoint脚本需要加权限

再来看一下requirements.txt

Flask==0.12.1

接着看一下 容器运行时的自动运行的脚本 run.sh

#!/bin/bash
set -e
pwd
# 日志文件
touch /var/jenkins_home/workspace/log/access_print.log
touch /var/jenkins_home/workspace/log/error_print.log
touch /var/jenkins_home/workspace/log/output_print.log
pwd
ls -l
echo makedir ok 
chmod 777 src
	
# gunicorn启动命令
exec gunicorn src.manage:app \
        --bind 0.0.0.0:8000 \
        --workers 4 \
        --log-level debug \
        --access-logfile=/var/jenkins_home/workspace/log/access_print.log \
        --error-logfile=/var/jenkins_home/workspace/log/error_print.log
exec "$@"

同样的,gunicorn命令解释文档也有很多,不一一说了,我参考的是:gunicorn配置文件解释,有两个需要注意的地方:

一个是:当run.sh和flask启动文件manage.py不在同一级目录时,使用 gunicorn src.manage:app ,

而非:gunicorn /src/manage:app,或者指定gunicorn的pathonpath参数,--pythonpath /var/jenkins_home/workspace/src

另一个注意点:若启动容器时报 "docker standard_init_linux.go:195: exec user process caused  no such file or directory",

有可能是run.sh文本格式问题,可以如下解决:

sudo apt install dos2unix
dos2unix Dockerfile
dos2unix run.sh 

再看一下flask的主文件manage.py:

# coding:utf-8
from flask import Flask
import logging
import sys

app = Flask(__name__)

# 日志输出到终端
app.debug = True
handler = logging.StreamHandler(sys.stdout)
logging_format = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(lineno)s - %(message)s')
handler.setFormatter(logging_format)
# 日志输出到文本
file_handler = logging.handlers.RotatingFileHandler('/var/jenkins_home/workspace/log/output_print.log', maxBytes=10*1024*1024, backupCount=9)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging_format)
app.logger.addHandler(file_handler)
app.logger.addHandler(handler)
app.logger.debug('app is ready')


@app.route('/')
def hello_world():
    app.logger.debug('debug level log')
    app.logger.info('info level log')
    return "hello world"

    
if __name__ == '__main__':
    from werkzeug.contrib.fixers import ProxyFix
    app.wsgi_app = ProxyFix(app.wsgi_app)
    app.run(host='0.0.0.0', port=8000)

日志输出到文本便于使用docker挂载查看日志,不然容器停止运行就不方便查看日志了

构造镜像:

docker build -t myflask .

查看构建好的镜像: docker images

一个简单的Docker+Gunicorn+Flask示例_第2张图片

指定镜像启动容器,并指定挂载路径:

docker run -p 8888:8000 -v /var/log/supp:/var/jenkins_home/workspace/log --name ademo 5551f90f20a4

上面启动容器参数含义:  -p 8888:8000  指定宿主机端口到容器端口映射

-v /var/log/supp:/var/jenkins_home/workspace/log  指定宿主机"/var/log/supp"文件映射容器的"/var/jenkins_home/workspace/log"文件夹,容器停止运行后日志文件依然可以被查看(需要先在宿主机自定义建文件夹/var/log/supp)。

--name ademo  容器命名为 ademo

这里还有一个坑,第一次写helloworld的时候,这个flask工程中我没有新建 __init__.py文件,导致"包"无法被导入,找不到manane.py文件,启动容器时gunirocn一直报错:

"no model named manage" 和 "gunicorn.errors.HaltServer: "

一个简单的Docker+Gunicorn+Flask示例_第3张图片

通过查看我们自定义的错误信息日志文件/var/log/supp/error_pring.log,找到是importError,

启动成功:

一个简单的Docker+Gunicorn+Flask示例_第4张图片

验证,先查看挂载卷文件夹下新建的日志文件:请求日志记录在access.log,错误日志在error.log,自定义输出日志在output.log

访问宿主机8888端口(即容器对应的8000端口),helloworld ojbk了:

一个简单的实例就OK了

你可能感兴趣的:(个人笔记)