Docker 容器化部署 Python 應(yīng)用

圖片來源于網(wǎng)絡(luò)

作者:jerry,爬蟲/數(shù)據(jù)分析/Web。

Blog: http://www.spiderpy.cn

Github: https://github.com/jhao104

1. 簡介

Docker是目前主流IT公司廣泛接受和使用的隘竭,用于構(gòu)建、管理和保護(hù)它們應(yīng)用程序的工具绘梦。

容器嘀掸,例如Docker允許開發(fā)人員在單個操作系統(tǒng)上隔離和運(yùn)行多個應(yīng)用程序,而不是為服務(wù)器上的每個應(yīng)用程序?qū)S靡粋€虛擬機(jī)实幕。使用容器更輕量級吝镣,可以降低成本、更好地使用資源和發(fā)揮更高的性能昆庇。

本文將使用Flask開發(fā)一個簡單的Python web應(yīng)用程序末贾,并為“容器化”做好準(zhǔn)備。然后創(chuàng)建一個Docker映像整吆,并將其部署到測試和生產(chǎn)環(huán)境中拱撵。

注意:請確保機(jī)器上已安裝Docker,如果沒有請參考Docker官方安裝教程(https://docs.docker.com/install/)表蝙。

2. Docker介紹

Docker是一種工具拴测,它使開發(fā)人員能夠交付他們的應(yīng)用程序(以及庫或其他依賴項(xiàng)),確保他們可以使用正確的配置運(yùn)行府蛇,而不受部署環(huán)境影響集索。

這是通過將應(yīng)用程序隔離在單獨(dú)的容器中來實(shí)現(xiàn)的,這些應(yīng)用程序雖然被容器分隔開汇跨,但是卻可以共享操作系統(tǒng)和其他資源务荆。

Docker包含兩部分:

  • Docker Engine — 應(yīng)用打包工具,用于封裝應(yīng)用程序穷遂。

  • Docker Hub — 用于管理云上容器應(yīng)用程序的工具函匕。

3.為何選擇容器

了解容器的重要性和實(shí)用性非常重要,雖然它和直接將應(yīng)用部署到服務(wù)器沒有多大區(qū)別蚪黑,但是當(dāng)涉及到比較復(fù)雜的且相當(dāng)吃資源的應(yīng)用盅惜,尤其是多個應(yīng)用部署在同一臺服務(wù)器,或是同一應(yīng)用要部署到多臺服務(wù)器時祠锣。容器就變得非常有用酷窥。

在容器之前,這是通過 VMWare 和 Hypervisor等虛擬機(jī)解決的伴网,但是它們在效率蓬推、速度和可移植性方面已被證明并不是最佳選擇。

Docker容器是虛擬機(jī)的輕量級的替代品-與VM不同澡腾,我們不需要為它預(yù)先分配RAM沸伏、CPU或其他資源糕珊,也不需要為每個應(yīng)用程序啟動一個VM,僅僅只需要一個操作系統(tǒng)即可毅糟。

使用容器開發(fā)人員就不需要為不同環(huán)境制定特殊版本红选,這樣可以專注于應(yīng)用程序的核心業(yè)務(wù)邏輯。

4.創(chuàng)建Python應(yīng)用

Flask是Python的一個輕量級Web應(yīng)用框架姆另,簡單易用喇肋,可以很快速地創(chuàng)建web應(yīng)用。我們用它來創(chuàng)建此demo應(yīng)用迹辐。

如果還沒有安裝Flask模塊蝶防,可以使用下面命令安裝:

$ pip install flask

安裝成功后,新建一個應(yīng)用目錄明吩,命名為FlaskDemo间学。并在該目錄下創(chuàng)建應(yīng)用代碼文件app.py

app.py中印荔,首先引入Flask模塊低葫,然后創(chuàng)建一個web應(yīng)用:

from flask import Flask

app = Flask(__name__)

然后定義路由/和其對應(yīng)的請求處理程序:

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

最后,添加運(yùn)行主程序并啟動該腳本:

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

然后在瀏覽器中訪問http://localhost:5000/,可以看到Dockerzing Python app using Flask這樣的頁面仍律。

image

5.Dokcer打包應(yīng)用

要在Docker上運(yùn)行應(yīng)用程序嘿悬,首先必須構(gòu)建一個容器,而且必須包含使用的所有依賴項(xiàng)——在我們的例子中只有Flask染苛。因此鹊漠,新建一個包含所有依賴包的 requirements.txt 文件,然后創(chuàng)建一個Dockerfile茶行,該文件用來描述構(gòu)建映像過程躯概。

此外,當(dāng)啟動容器時還需要放開應(yīng)用程序的HTTP端口畔师。

準(zhǔn)備工作

requirements.txt 文件非常簡單娶靡,只需要填入項(xiàng)目的依賴包和其對應(yīng)版本即可:

Flask==1.0.2
...等等其他工具包

接下來,需要將應(yīng)用程序運(yùn)行所需的所有Python文件都放在頂層文件夾中看锉,例如姿锭,名為app的目錄。

同時建議將主入口程序命名為 app.py 伯铣,將腳本中創(chuàng)建的Flask對象命名為 app 是一種通常的做法呻此,這樣也可以簡化部署。

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

創(chuàng)建Dockerfile

Dockerfile本質(zhì)上是一個文本文件腔寡,其中明確定義了如何為我們的項(xiàng)目構(gòu)建Docker鏡像焚鲜。

接下來創(chuàng)建一個基于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 ,用于指定一個構(gòu)建鏡像的基礎(chǔ)源鏡像忿磅,如果本地沒有就會從公共庫中拉取糯彬,沒有指定鏡像的標(biāo)簽會使用默認(rèn)的latest標(biāo)簽,如果需要在一個Dockerfile中構(gòu)建多個鏡像葱她,可以使用多次撩扒。

  • MAINTAINER - 描述鏡像的創(chuàng)建者,名稱和郵箱吨些。

  • RUN - RUN命令是一個常用的命令搓谆,執(zhí)行完成之后會成為一個新的鏡像,通常用于運(yùn)行安裝任務(wù)從而向映像中添加額外的內(nèi)容锤灿。在這里挽拔,我們需更新包,安裝 python3 和 pip 但校。在第二個 RUN 命令中使用 pip 來安裝 requirements.txt 文件中的所有包。

  • COPY - 復(fù)制本機(jī)文件或目錄啡氢,添加到指定的容器目錄, 本例中將 requirements.txt 復(fù)制到鏡像中状囱。

  • WORKDIR - 為RUN、CMD倘是、ENTRYPOINT指令配置工作目錄亭枷。可以使用多個WORKDIR指令搀崭,后續(xù)參數(shù)如果是相對路徑叨粘,則會基于之前命令指定的路徑。

  • ENTRYPOINT - 在啟動容器的時候提供一個默認(rèn)的命令項(xiàng)瘤睹。

  • RUN - 運(yùn)行 app 目錄中的 app.py 升敲。

Docker鏡像構(gòu)建原理

Docker鏡像是使用 Docker build 命令構(gòu)建的。在構(gòu)建鏡像時轰传,Docker創(chuàng)建了所謂的“層(layers)”驴党。每一層都記錄了Dockerfile中的命令所導(dǎo)致的更改,以及運(yùn)行命令后鏡像的狀態(tài)获茬。

Docker在內(nèi)部緩存這些層港庄,這樣在重新構(gòu)建鏡像時只需要重新創(chuàng)建已更改的層。例如恕曲,這里使用了 ubuntu:16.04 的基礎(chǔ)鏡像鹏氧,相同容器的所有后續(xù)構(gòu)建都可以重用它,因?yàn)樗粫淖兣逡ァ5前鸦梗驗(yàn)轫?xiàng)目修改,在下次重新構(gòu)建過程中 app 目錄的內(nèi)容可能會有所不同,因此只會重新構(gòu)建這一層笨篷。

需要注意的是瞳秽,每當(dāng)重新構(gòu)建某一層時,Dockerfile 中緊隨其后的所有層也都需要重新構(gòu)建率翅。例如练俐,我們首先復(fù)制 requirements.txt 文件,然后再復(fù)制應(yīng)用程序的其余部分冕臭。這樣之前安裝的依賴項(xiàng)只要沒有新的依賴關(guān)系腺晾,即使應(yīng)用程序中的其他文件發(fā)生了更改,也不需要重新構(gòu)建這一層辜贵。這一點(diǎn)在創(chuàng)建 Dockerfiles 時一定要注意悯蝉。

因此,通過將 pip 安裝與應(yīng)用程序其余部分的部署分離托慨,可以優(yōu)化容器的構(gòu)建過程鼻由。

構(gòu)建Docker鏡像

現(xiàn)在 Dockerfile 已經(jīng)準(zhǔn)備好了,而且也了解了Docker的構(gòu)建過程厚棵,接下來為我們的應(yīng)用程序創(chuàng)建Docker映像:

docker build -t docker-flask:0.1 .

調(diào)試模式運(yùn)行

根據(jù)前面講到的容器化的優(yōu)點(diǎn)蕉世,開發(fā)的應(yīng)用程序通過容器部署,這從一開始就確保了應(yīng)用程序構(gòu)建的環(huán)境是干凈的婆硬,從而消除了交付過程中的意外情況狠轻。

但是呢,在開發(fā)應(yīng)用程序的過程中彬犯,更重要的是要快速重新構(gòu)建和測試向楼,以檢查驗(yàn)證過程中的每個中間步驟。為此谐区,web應(yīng)用程序的開發(fā)人員需要依賴于Flask等框架提供的自動重啟功能(Debug模式下湖蜕,修改代碼自動重啟)。而這一功能也可以在容器中使用卢佣。

為了啟用自動重啟重荠,在啟動Docker容器時將主機(jī)中的開發(fā)目錄映射到容器中的app目錄。這樣Flask就可以監(jiān)聽主機(jī)中的文件變化(通過映射)來發(fā)現(xiàn)代碼更改虚茶,并在檢測到更改時自動重啟應(yīng)用程序戈鲁。

此外,還需要將應(yīng)用程序的端口從容器轉(zhuǎn)發(fā)到主機(jī)嘹叫。這是為了能夠讓主機(jī)上的瀏覽器訪問應(yīng)用程序婆殿。

因此,啟動Dokcer容器時需要使用 volume-mappingport-forwarding 選項(xiàng):

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

該命令將會執(zhí)行以下操作:

  • 基于之前構(gòu)建的 docker-flask 鏡像啟動一個容器罩扇;

  • 這個容器的名稱被設(shè)置為 flask_app 婆芦。如果沒有 ——name 選項(xiàng)怕磨,Docker將為容器生成一個名稱。顯式指定名稱可以幫助我們定位容器(用來停止等操作)消约;

  • -v 選項(xiàng)將主機(jī)的app目錄掛載到容器肠鲫;

  • -p 選項(xiàng)將容器的端口映射到主機(jī)。

現(xiàn)在可以通過http://localhost:5000或者 http://0.0.0.0:5000/訪問到應(yīng)用:

image

如果我們在容器運(yùn)行的時候或粮,修改應(yīng)用程序代碼导饲,F(xiàn)lask會檢測到更改并重新啟動應(yīng)用程序。

image

要停止容器的話氯材,可以使用 Ctrl + C, 并運(yùn)行 docker rm flask_app移除容器渣锦。

生產(chǎn)模式運(yùn)行

雖然直接使用Flask裸跑運(yùn)行應(yīng)用程序?qū)τ陂_發(fā)來說已經(jīng)足夠好了,但是我們需要在生產(chǎn)中使用更健壯的部署方法氢哮。

目前主流的部署方案是 nginx + uwsgi袋毙,下面我們將介紹如何為生產(chǎn)環(huán)境部署web應(yīng)用程序。Nginx是一個開源web服務(wù)器冗尤,uWSGI是一個快速听盖、自我修復(fù)喉镰、開發(fā)人員和系統(tǒng)管理員友好的服務(wù)器衫生。

首先,我們創(chuàng)建一個入口腳本雪隧,用來控制以開發(fā)模式還是生產(chǎn)模式啟動我們的應(yīng)用程序,這兩者區(qū)別是選擇直接運(yùn)行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

然后創(chuàng)建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安裝到鏡像,將配置文件復(fù)制到鏡像中,并設(shè)置運(yùn)行nginx所需的用戶權(quán)限:

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啟動應(yīng)用程序:

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

該鏡像包含python录语、ngix、uwsgi完整環(huán)境澎埠,只需要在部署時指定端口映射便可以自動部署應(yīng)用虽缕。要停止并刪除此容器蒲稳,請運(yùn)行下面命令:

docker stop flaskapp && docker rm flaskapp

此外,如果我們?nèi)匀恍枰厦嬲{(diào)試功能或修改部分代碼江耀,也可以像上面一樣以調(diào)試模式運(yùn)行容器:

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

6.管理外部依賴

如果將應(yīng)用程序作為容器交付時剩胁,需要記住的一個關(guān)鍵事項(xiàng)是祥国,開發(fā)人員管理依賴項(xiàng)的責(zé)任增加了昵观。除了識別和指定正確的依賴項(xiàng)和版本之外晾腔,還需要負(fù)責(zé)在容器環(huán)境中安裝和設(shè)置這些依賴項(xiàng)。

在Python項(xiàng)目中管理安裝依賴比較容易啊犬,可以使用requirements.txt指定依賴項(xiàng)和對應(yīng)版本灼擂,然后通過 pip 安裝。

需要重申的是是觉至,無論何時修改 requirements.txt 文件剔应,都需要重新構(gòu)建Docker鏡像。

啟動時安裝依賴項(xiàng)

可能在某次版本更新時需要安裝額外的依賴項(xiàng)康谆。比如领斥,在開發(fā)過程中使用了一個新的包。如果不希望每次都重新構(gòu)建Docker鏡像沃暗,或者希望在啟動時使用最新的可用版本月洛。可以通過修改啟動程序在應(yīng)用程序啟動時運(yùn)行安裝程序來實(shí)現(xiàn)這一點(diǎn)孽锥。

同樣嚼黔,我們也可以安裝額外的系統(tǒng)級包依賴項(xiàng)。修改 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 中指定將要安裝的系統(tǒng)軟件包名稱惜辑,這些包名以空格分隔放在同一行唬涧。他們將和 requirements.txt 中的Python依賴庫一樣在應(yīng)用程序啟動之前安裝。

盡管這樣對應(yīng)用的迭代開發(fā)期間提供了便利盛撑,但是出于幾個原因碎节,在啟動時安裝依賴項(xiàng)不是一個好的實(shí)踐:

  • 它破壞了容器化的目標(biāo)之一,即修復(fù)和測試由于部署環(huán)境的變化而不會改變的依賴關(guān)系抵卫;

  • 增加了應(yīng)用程序啟動的額外開銷狮荔,這將增加容器的啟動時間;

  • 每次啟動應(yīng)用程序時需要安裝依賴項(xiàng)介粘,這樣對網(wǎng)絡(luò)資源有要求殖氏。

demo代碼

https://github.com/jhao104/docker-flask-demo
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市姻采,隨后出現(xiàn)的幾起案子雅采,更是在濱河造成了極大的恐慌,老刑警劉巖慨亲,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婚瓜,死亡現(xiàn)場離奇詭異,居然都是意外死亡巡雨,警方通過查閱死者的電腦和手機(jī)闰渔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铐望,“玉大人冈涧,你說我怎么就攤上這事茂附。” “怎么了督弓?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵营曼,是天一觀的道長。 經(jīng)常有香客問我愚隧,道長蒂阱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任狂塘,我火速辦了婚禮录煤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荞胡。我一直安慰自己妈踊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布泪漂。 她就那樣靜靜地躺著廊营,像睡著了一般。 火紅的嫁衣襯著肌膚如雪萝勤。 梳的紋絲不亂的頭發(fā)上露筒,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音敌卓,去河邊找鬼慎式。 笑死,一個胖子當(dāng)著我的面吹牛瞬捕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劣砍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刑枝?” 一聲冷哼從身側(cè)響起香嗓,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤装畅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掠兄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锌雀,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腋逆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惩歉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撑蚌,死狀恐怖搏屑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情睬棚,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布抑党,位于F島的核電站,受9級特大地震影響底靠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暑中,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鳄逾。 院中可真熱鬧,春花似錦雕凹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽李丰。三九已至,卻和暖如春逼泣,著一層夾襖步出監(jiān)牢的瞬間舟舒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工魏蔗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痹筛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓帚稠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親滋早。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容