1. 程式人生 > >一個簡單的Docker+Gunicorn+Flask示例

一個簡單的Docker+Gunicorn+Flask示例

使用Docker部署有諸多好處,flask程式也通常需要搭配一個高效能的wsgi容器,今天就記錄一下在使用docker+gunicorn+flask過程中的一些坑,錯誤之處歡迎指正。

一個簡單的demo(宿主機為ubuntu18.04),先來看目錄結構:

目錄結構

即 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指令碼需要加許可權
  6. 若使用掛載資料卷方式啟動,需要先宣告一個掛載點

再來看一下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 "
[email protected]
"

同樣的,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 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: <HaltServer 'Worker failed to boot.' 3>"

通過檢視我們自定義的錯誤資訊日誌檔案/var/log/supp/error_pring.log,找到是importError,

啟動成功:

驗證,先檢視掛載卷資料夾下新建的日誌檔案:請求日誌記錄在access.log,錯誤日誌在error.log,自定義輸出日誌在output.log

訪問宿主機8888埠(即容器對應的8000埠),helloworld ojbk了:

一個簡單的例項就OK了