(或者讓我們繞過virtualenv)
本文中,我將嘗試展示用Docker開發python應用(主要是Web應用)的可行方法。雖然我本人專注於Python的Flask微框架,但本文目的是演示如何通過Docker更好地開發和共享應用程式,(由任何語言和框架開發的應用程式)。Docker通過封裝依賴項,大大減少了開發環境和正式產品的差距。
大多數Python開發人員在開發中使用virtualenv。它提供了一種易用的機制讓應用程式使用自己專用的依賴項,這些依賴項可能與在其它應用程式或作業系統存在衝突(尤其是不同的Pyhton版本,還有不同的庫版本等等)。個人而言,我對virtualenv一直沒有太大興趣,原因如下:
- 我經常忘記啟用它,或者在切換工程時忘記切換它,這會遇到含糊的出錯資訊,另人倍感困惑。
- 它無法提供“純粹的”隔離,只能是Python級別的隔離(系統庫和非python的依賴項仍然會出問題)。
- 我通常不想在正式產品中執行它,這就意味著在開發環境和正式產品的不一致。
- 它讓人感覺有點“黑客”作法:它是依靠修改指令碼和設定新路徑實現的。
( 檢視 pythonrants的這篇文章 瞭解更多為什麼你可能不想用virtualenv )
那麼,怎麼做Docker才能變得更好呢?Docker本質上提供了非常輕量化的VMs(在說法上可以稱為“容器”),我們可以使用其建立一個高標準隔離並能大大減少失配的開發和產品環境。(如果你不熟悉Docker,卻還想學習更多,你可以檢視我在愛丁堡技術座談會上介紹Docker的談話)。
當我們建立一個小型的視覺化Web APP,我自己和Mark Coleman使用這種方法(文件在這)。這(裡面)劃出了一個基本映象安裝Python 2.7,還有一些Flask管理以及PostgreSQL的內容。我會依據這個映象去開發一個hello world的Web應用。我假設你是在Linux上開發,並且你已經有git,還安裝了Docker,MacOS的指令應該非常類似。通過克隆和建立基本映象開始:
1 2 |
$ git clone https://github.com/mrmrcoleman/python_webapp $ docker build -t python_webapp . |
現在,我們需要為容器中新增一些程式碼並詳細寫明。我們打算新建一個僅僅指向Docker映象的專案來完成這項工作,而不是直接修改之前的專案。
建立一個具有下列結構的新專案:
1 2 3 4 5 6 7 |
├── Dockerfile ├── example_app │ ├── app │ │ ├── __init__.py │ │ └── views.py │ └── __init__.py ├── example_app.wsgi |
或者克隆該地址的示例專案: https://github.com/amouat/example_app.git
在example_app/app/_init_.py中寫入:
1 2 3 4 |
from flask import Flask app = Flask(__name__) from app import views |
使另一個_init_.py為空。在views.py中寫入:
1 2 3 4 5 6 |
from app import app @app.route('/') @app.route('/index') def index(): return "Hello, World!" |
以上就是我們的一個hello world應用的最小flask版本。我在 這個教程中也使用過類似的程式碼,所以如果你剛剛接觸Flask或者Python,你可以根據上述提到的教程,使用Docker而不是virtualenv繼續學習。
為了使之執行在Docker容器內部,我們還需要做一些操作。在我們的例項Apache伺服器中,example_app.wsgi檔案包含了連線Python程式碼和web伺服器的指令。該檔案應當包含下列內容:
1 2 3 |
import site site.addsitedir('/opt/example_app/') from app import app as application |
最終,我們需要一個Dockerfile來構建容器並執行容器。在我們的例項中,它看起來是這樣的:
1 2 3 4 5 6 |
FROM python_webapp MAINTAINER amouat ADD example_app.wsgi /var/www/flaskapp/flaskapp.wsgi CMD service apache2 start && tail -F /var/log/apache2/error.log |
ADD那行為啟動WSGI注入了一些程式碼。CMD那行在啟動容器,啟動apache web伺服器時獲取任何可能的錯誤資訊,並將其傳送至stdout。
如果你下列操作:
1 2 |
$ docker build -t example_app . $ docker run -p 5000:5000 -v $(pwd)/example_app:/opt/example_app/ -i -t example_app |
你應當會得到這樣的回饋:通過瀏覽器開啟地址localhost:5000,你會看到你的網站正在執行。如果你實在VM或者vagrant中執行,記得開啟5000埠。
現在我們執行了web伺服器,已經非常接近我們在產品中使用的東西了(我有意的使用Apache來做這點而不是Python預設的web伺服器)。我們通過從主機向容器對映的方式向容器中注入程式碼;也可以在Dockerfile命令列中是用ADD來新增程式碼,但那樣的話當我們隊程式碼進行改動時,每次都需要重新構建容器。
然而,這仍然不是很好 ;開發中我們真的希望使用很大程度上幫助我們除錯的Python web伺服器。該高興的是我們不用對Dockerfile進行任何修改。在example_app檔案從建立一個run.py檔案開始,按照一下內容:
1 2 3 |
!flask/bin/python from app import app app.run(debug = True, host='0.0.0.0') |
這將啟動Python的帶除錯的web伺服器並監聽所有連線,我們也能從容器外訪問。現在用下列命令重啟容器:
1 |
$ docker run -p 5000:5000 -v $(pwd)/example_app:/opt/example_app/ -i -t example_app python /opt/example_app/run.py |
你能看到網頁又執行了。這次我們顯式地提供執行的命令(”python /opt/example_app/ryn.py”),它覆蓋了Dockerfile中的CMD行的設定。現在如果編輯在主機上的源程式,就能馬上看到網頁上的改變。
讓我們花點時間看看我們的收穫:
- 一個執行在隔離容器中的web應用,容器完全封裝了應用的Python依賴項和系統依賴項。
- 能夠使用現有編輯器或IDE開發程式碼並直接檢視變化,就像在本地編輯一樣。
- 比以前更接近正式產品的執行環境。
- 沒有使用virtualenv。
如果你想知道如何以這種方式建立程式釋出的途徑,可以看看Mark Coleman寫的關於前面提到的視覺化Web應用的文章。
不幸的是,這一切還不完美。還有下列幾個問題:
- 你可能仍會遇到需要使用virtualenv或其等價解決方案的情況,例如庫的作業系統版本與你的程式所需版本間的衝突。
- 我們還沒完全解決資料託管的問題,仍需做某些測試。
- 我假設的“產品”是一個Docker容器,但實際情況常常並非如此而且Docker託管本身也剛剛起步。
儘管如此,我仍然認為這向軟體開發的更好未來邁了一大步,大大減輕了部署軟體和管理依賴項的痛苦。