Docker容器化部署Python應用

1. 簡介

Docker是目前主流IT公司普遍接受和使用的,用於構建、管理和保護它們應用程序的工具。html

容器,例如Docker容許開發人員在單個操做系統上隔離和運行多個應用程序,而不是爲服務器上的每一個應用程序專用一個虛擬機。使用容器更輕量級,能夠下降成本、更好地使用資源和發揮更高的性能。python

本文將使用Flask開發一個簡單的Python web應用程序,併爲「容器化」作好準備。而後建立一個Docker映像,並將其部署到測試和生產環境中。nginx

注意: 請確保機器上已安裝Docker,若是沒有請參考Docker官方安裝教程web

2. Docker介紹

Docker是一種工具,它使開發人員可以交付他們的應用程序(以及庫或其餘依賴項),確保他們可使用正確的配置運行,而不受部署環境影響。docker

這是經過將應用程序隔離在單獨的容器中來實現的,這些應用程序雖然被容器分隔開,可是卻能夠共享操做系統和其餘資源。shell

Docker包含兩部分:flask

  • Docker Engine — 應用打包工具,用於封裝應用程序。ubuntu

  • Docker Hub — 用於管理雲上容器應用程序的工具。瀏覽器

3.爲什麼選擇容器

瞭解容器的重要性和實用性很是重要,雖然它和直接將應用部署到服務器沒有多大區別,可是當涉及到比較複雜的且至關吃資源的應用,尤爲是多個應用部署在同一臺服務器,或是同一應用要部署到多臺服務器時。容器就變得很是有用。緩存

在容器以前,這是經過 VMWareHypervisor等虛擬機解決的,可是它們在效率、速度和可移植性方面已被證實並非最佳選擇。

Docker容器是虛擬機的輕量級的替代品-與VM不一樣,咱們不須要爲它預先分配RAM、CPU或其餘資源,也不須要爲每一個應用程序啓動一個VM,僅僅只須要一個操做系統便可。

使用容器開發人員就不須要爲不一樣環境制定特殊版本,這樣能夠專一於應用程序的核心業務邏輯。

4.建立Python應用

Flask是Python的一個輕量級Web應用框架,簡單易用,能夠很快速地建立web應用。咱們用它來建立此demo應用。

若是尚未安裝Flask模塊,可使用下面命令安裝:

$ pip install flask

安裝成功後,新建一個應用目錄,命名爲FlaskDemo。並在該目錄下建立應用代碼文件app.py

app.py中,首先引入Flask模塊,而後建立一個web應用:

from flask import Flask

app = Flask(__name__)

而後定義路由/和其對應的請求處理程序:

@app.route("/")
def index():  
  return """
  <h1>Python Flask in Docker!</h1>
  <p>A sample web-app for running Flask inside Docker.</p>
  """

最後,添加運行主程序並啓動該腳本:

if __name__ == "__main__":  
    app.run(debug=True, host='0.0.0.0')
$ python app.py

而後在瀏覽器中訪問http://localhost:5000/,能夠看到Dockerzing Python app using Flask這樣的頁面。

5.Dokcer打包應用

要在Docker上運行應用程序,首先必須構建一個容器,並且必須包含使用的全部依賴項——在咱們的例子中只有Flask。所以,新建一個包含全部依賴包的 requirements.txt 文件,而後建立一個Dockerfile,該文件用來描述構建映像過程。

此外,當啓動容器時還須要放開應用程序的HTTP端口。

準備工做

requirements.txt 文件很是簡單,只須要填入項目的依賴包和其對應版本便可:

Flask==1.0.2

接下來,須要將應用程序運行所需的全部Python文件都放在頂層文件夾中,例如,名爲app的目錄。

同時建議將主入口程序命名爲 app.py ,將腳本中建立的Flask對象命名爲 app 是一種一般的作法,這樣也能夠簡化部署。

FlaskApp  
    ├── requirements.txt
    ├── Dockerfile
    └── app
        └── app.py
        └── <other .py files>

建立Dockerfile

Dockerfile本質上是一個文本文件,其中明肯定義瞭如何爲咱們的項目構建Docker鏡像。

接下來建立一個基於Ubuntu 16.04 和 Python 3.X的Dokcer鏡像:

FROM ubuntu:16.04

MAINTAINER jhao104 "j_hao104@163.com"

RUN apt-get update -y && \  
    apt-get install -y python3-pip python3-dev

COPY ./requirements.txt /requirements.txt

WORKDIR /

RUN pip3 install -r requirements.txt

COPY . /

ENTRYPOINT [ "python3" ]

CMD [ "app/app.py" ]

Dockerfile的基本指令有十三個,上面用到了部分;

  • FROM - 全部Dockerfile的第一個指令都必須是 FROM ,用於指定一個構建鏡像的基礎源鏡像,若是本地沒有就會從公共庫中拉取,沒有指定鏡像的標籤會使用默認的latest標籤,若是須要在一個Dockerfile中構建多個鏡像,可使用屢次。

  • MAINTAINER - 描述鏡像的建立者,名稱和郵箱。

  • RUN - RUN命令是一個經常使用的命令,執行完成以後會成爲一個新的鏡像,一般用於運行安裝任務從而向映像中添加額外的內容。在這裏,咱們需更新包,安裝 python3pip 。在第二個 RUN 命令中使用 pip 來安裝 requirements.txt 文件中的全部包。

  • COPY - 複製本機文件或目錄,添加到指定的容器目錄, 本例中將 requirements.txt 複製到鏡像中。

  • WORKDIR - 爲RUN、CMD、ENTRYPOINT指令配置工做目錄。可使用多個WORKDIR指令,後續參數若是是相對路徑,則會基於以前命令指定的路徑。

  • ENTRYPOINT - 在啓動容器的時候提供一個默認的命令項。

  • RUN - 運行 app 目錄中的 app.py

Docker鏡像構建原理

Docker鏡像是使用 Docker build 命令構建的。在構建鏡像時,Docker建立了所謂的「層(layers)」。每一層都記錄了Dockerfile中的命令所致使的更改,以及運行命令後鏡像的狀態。

Docker在內部緩存這些層,這樣在從新構建鏡像時只須要從新建立已更改的層。例如,這裏使用了 ubuntu:16.04 的基礎鏡像,相同容器的全部後續構建均可以重用它,由於它不會改變。可是,由於項目修改,在下次從新構建過程當中 app 目錄的內容可能會有所不一樣,所以只會從新構建這一層。

須要注意的是,每當從新構建某一層時,Dockerfile 中緊隨其後的全部層也都須要從新構建。例如,咱們首先複製 requirements.txt 文件,而後再複製應用程序的其他部分。這樣以前安裝的依賴項只要沒有新的依賴關係,即便應用程序中的其餘文件發生了更改,也不須要從新構建這一層。這一點在建立 Dockerfiles 時必定要注意。

所以,經過將 pip 安裝與應用程序其他部分的部署分離,能夠優化容器的構建過程。

構建Docker鏡像

如今 Dockerfile 已經準備好了,並且也瞭解了Docker的構建過程,接下來爲咱們的應用程序建立Docker映像:

docker build -t docker-flask:0.1 .

調試模式運行

根據前面講到的容器化的優勢,開發的應用程序經過容器部署,這從一開始就確保了應用程序構建的環境是乾淨的,從而消除了交付過程當中的意外狀況。

可是呢,在開發應用程序的過程當中,更重要的是要快速從新構建和測試,以檢查驗證過程當中的每一箇中間步驟。爲此,web應用程序的開發人員須要依賴於Flask等框架提供的自動重啓功能(Debug模式下,修改代碼自動重啓)。而這一功能也能夠在容器中使用。

爲了啓用自動重啓,在啓動Docker容器時將主機中的開發目錄映射到容器中的app目錄。這樣Flask就能夠監聽主機中的文件變化(經過映射)來發現代碼更改,並在檢測到更改時自動重啓應用程序。

此外,還須要將應用程序的端口從容器轉發到主機。這是爲了可以讓主機上的瀏覽器訪問應用程序。

所以,啓動Dokcer容器時須要使用 volume-mappingport-forwarding 選項:

docker run --name flask_app -v $PWD/app:/app -p 5000:5000 docker-flask:0.1

改命令將會執行如下操做:

  • 基於以前構建的 docker-flask 鏡像啓動一個容器;

  • 這個容器的名稱被設置爲 flask_app 。若是沒有 ——name 選項,Docker將爲容器生成一個名稱。顯式指定名稱能夠幫助咱們定位容器(用來中止等操做);

  • -v 選項將主機的app目錄掛載到容器;

  • -p 選項將容器的端口映射到主機。

如今能夠經過http://localhost:5000 或者 http://0.0.0.0:5000/ 訪問到應用:

若是咱們在容器運行的時候,修改應用程序代碼,Flask會檢測到更改並從新啓動應用程序。

要中止容器的話,可使用 Ctrl + C, 並運行 docker rm flask_app 移除容器。

生產模式運行

雖然直接使用Flask裸跑運行應用程序對於開發來講已經足夠好了,可是咱們須要在生產中使用更健壯的部署方法。

目前主流的部署方案是 nginx + uwsgi,下面咱們將介紹如何爲生產環境部署web應用程序。Nginx是一個開源web服務器,uWSGI是一個快速、自我修復、開發人員和系統管理員友好的服務器。

首先,咱們建立一個入口腳本,用來控制以開發模式仍是生產模式啓動咱們的應用程序,這二者區別是選擇直接運行python仍是nginx模式。

而後再寫一個簡單shell啓動腳本 entry-point.sh:

#!/bin/bash

if [ ! -f /debug0 ]; then  
  touch /debug0

  while getopts 'hd:' flag; do
    case "${flag}" in
      h)
        echo "options:"
        echo "-h        show brief help"
        echo "-d        debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
        exit 0
        ;;
      d)
        touch /debug1
        ;;
      *)
        break
        ;;
    esac
  done
fi

if [ -e /debug1 ]; then  
  echo "Running app in debug mode!"
  python3 app/app.py
else  
  echo "Running app in production mode!"
  nginx && uwsgi --ini /app.ini
fi

而後建立uWSGI配置文件 app.ini:

[uwsgi]
plugins = /usr/lib/uwsgi/plugins/python3  
chdir = /app  
module = app:app  
uid = nginx  
gid = nginx  
socket = /run/uwsgiApp.sock  
pidfile = /run/.pid  
processes = 4  
threads = 2

和nginx配置文件 nginx.conf:

user nginx;
worker_processes  4;
pid /run/nginx.pid;

events {
    worker_connections  20000;
}

http {
    include    mime.types;
    sendfile on;
    keepalive_timeout  65;
    gzip off;

    server {
        listen 80;
        access_log off;
        error_log off;

        location / { try_files $uri @flaskApp; }
        location @flaskApp {
            include uwsgi_params;
            uwsgi_pass unix:/run/uwsgiApp.sock;
        }
    }
}

最後,修改DockerfilenginxuWSGI安裝到鏡像,將配置文件複製到鏡像中,並設置運行nginx所需的用戶權限:

FROM ubuntu:16.04

MAINTAINER jhao104 "j_hao104@163.com"

RUN apt-get update -y && \
    apt-get install -y python3-pip python3-dev && \
    apt-get install -y nginx uwsgi uwsgi-plugin-python3

COPY ./requirements.txt /requirements.txt
COPY ./nginx.conf /etc/nginx/nginx.conf

WORKDIR /

RUN pip3 install -r requirements.txt

COPY . /

RUN adduser --disabled-password --gecos '' nginx\
  && chown -R nginx:nginx /app \
  && chmod 777 /run/ -R \
  && chmod 777 /root/ -R

ENTRYPOINT [ "/bin/bash", "/entry-point.sh"]

而後從新打包鏡像:

docker build -t docker-flask:0.1 .

而後使用nginx啓動應用程序:

docker run -d --name flaskapp --restart=always -p 8091:80 docker-flask:0.1

該鏡像包含python、ngix、uwsgi完整環境,只須要在部署時指定端口映射即可以自動部署應用。要中止並刪除此容器,請運行下面命令:

docker stop flaskapp && docker rm flaskapp

此外,若是咱們仍然須要上面調試功能或修改部分代碼,也能夠像上面同樣以調試模式運行容器:

docker run -it --name flaskapp -p 5000:5000 -v $PWD/app:/app docker-flask:0.1 -d debug

6.管理外部依賴

若是將應用程序做爲容器交付時,須要記住的一個關鍵事項是,開發人員管理依賴項的責任增長了。除了識別和指定正確的依賴項和版本以外,還須要負責在容器環境中安裝和設置這些依賴項。

在Python項目中管理安裝依賴比較容易,可使用requirements.txt指定依賴項和對應版本,而後經過 pip 安裝。

須要重申的是是,不管什麼時候修改 requirements.txt 文件,都須要從新構建Docker鏡像。

啓動時安裝依賴項

可能在某次版本更新時須要安裝額外的依賴項。好比,在開發過程當中使用了一個新的包。若是不但願每次都從新構建Docker鏡像,或者但願在啓動時使用最新的可用版本。能夠經過修改啓動程序在應用程序啓動時運行安裝程序來實現這一點。

一樣,咱們也能夠安裝額外的系統級包依賴項。修改 entry-point.sh:

#!/bin/bash

if [ ! -f debug0 ]; then
  touch debug0
  
  if [ -e requirements_os.txt ]; then
    apt-get install -y $(cat requirements_os.txt)
    
   fi
   if [-e requirements.txt ]; then
    pip3 install -r requirements.txt
   fi

  while getopts 'hd:' flag; do
    case "${flag}" in
      h)
        echo "options:"
        echo "-h        show brief help"
        echo "-d        debug mode, no nginx or uwsgi, direct start with 'python3 app/app.py'"
        exit 0
        ;;
      d)
        touch debug1
        ;;
      *)
        break
        ;;
    esac
  done
fi

if [ -e debug1 ]; then
  echo "Running app in debug mode!"
  python3 app/app.py
else
  echo "Running app in production mode!"
  nginx && uwsgi --ini /app.ini
fi

這樣咱們能夠在 requirements_os.txt 中指定將要安裝的系統軟件包名稱,這些包名以空格分隔放在同一行。他們將和 requirements.txt 中的Python依賴庫同樣在應用程序啓動以前安裝。

儘管這樣對應用的迭代開發期間提供了便利,可是出於幾個緣由,在啓動時安裝依賴項不是一個好的實踐:

  • 它破壞了容器化的目標之一,即修復和測試因爲部署環境的變化而不會改變的依賴關係;

  • 增長了應用程序啓動的額外開銷,這將增長容器的啓動時間;

  • 每次啓動應用程序時須要安裝依賴項,這樣對網絡資源有要求。

相關文章
相關標籤/搜索