編者按:借助Docker,我們可以更容易地進行web應用部署,而同時不必頭疼于項目依賴、環境變量以及各種配置問題,Docker可以快捷、高效地處理好這一切。而這也是本教程所要實現的主要目的。以下是作者原文:
首先我們來學習使用Docker容器運行一個Python Flask應用,然后逐步介紹一套更酷的開發流程,其中涵蓋了應用的持續集成與發布。
流程
在本地功能分支上完成應用代碼。
在Github上發起一個到master分支的Pull Request。
在Docker容器上運行自動測試。
如果測試通過,手動將這個PR merge進master分支。
一旦merge成功,再次運行自動測試。
如果第二次測試也通過,就在Docker Hub上對應用進行構建。
一旦構建完成,自動化地部署到生產環境。
本教程基于Mac OS X,在開始前需要保證以下工具已正確安裝配置:Python v2.7.9, Flask v0.10.1, Docker v1.4.1, Docker Compose, v1.0.0, boot2docker 1.4.1, Redis v2.8.19
好了,讓我們開始吧。首先來介紹一些Docker中的基本概念:
Dockerfile中包括了一系列語句,用于對鏡像的行為進行描述。
鏡像是一個模板,用來保存環境狀態并創建容器。
容器可以理解為實例化的鏡像,并會在其中運行一系列進程。
如果對Dockerfile、鏡像和容器的具體細節感興趣,那么可以從Docker的官方文檔獲取更多詳細信息。
為什么是Docker?
使用Docker意味著你能在開發機上完美地模擬生產環境,而不用再為任何由兩者環境、配置差異所造成的問題而擔心,除此之外Docker帶給我們的還有:
良好的版本控制。
隨時便捷地發布/重建整個開發環境。
一次構建,隨處運行,就是這么神奇!
配置Docker
由于Darwin(OS X內核)缺少運行Docker容器的一些Linux內核功能,所以我們需要借助boot2docker,一個用于運行Docker的輕量級Linux發行版(啟動一個專門為運行Docker定制過的小型虛擬機)。
首先為我們的Flask項目創建一個名為“fitter-happier-docker”的目錄。
接下來遵照官方文檔的步驟來完成Docker和boot2docker的安裝。
我們可以通過以下命令來驗證安裝是否正確:
$ boot2docker version
Boot2Docker-cli version: v1.4.1
Git commit: 43241cb
Compose Up!
Docker Compose是官方提供的容器業務流程框架(譯注:曾經的項目名稱是Fig,甚至在本譯文的初稿時依然是,進化速度之快可見一斑),只需通過簡單的.yml配置文件,就能完成多個容器服務的構建和運行。
使用pip來安裝Docker Compose,并通過如下命令來確認安裝正確:
$ pip install docker-compose
$ docker-compose --version
docker-compose 1.1.0現在來啟動我們的Flask+Redis應用(你可以從這個repo來獲取項目的全部源代碼),首先在項目根目錄下新建docker-compose.yml文件:
web:
build: web
volumes:
- web:/code
ports:
- "80:5000"
links:
- redis
command: python app.py
redis:
image: redis:2.8.19
ports:
- "6379:6379"
可以看到我們對項目所含兩個服務進行的操作:
web:我們將在web目錄下進行容器的構建,并且將其作為Volume掛載到容器的/code目錄中,然后通過python app.py來啟動Flask應用。最后將容器的5000端口暴露出來,并將其映射到主機的80端口上。
redis:我們直接使用Docker Hub上的官方鏡像來提供所需的Redis服務支持,將6379端口暴露并映射到主機上。
你一定注意到了位于web目錄下的Dockerfile文件,它用于指導Docker如何構建我們的應用鏡像(基于Ubuntu),并且保證了完備的依賴支持。
構建并運行
接下來只需要一行簡單命令,就能輕松搞定一切(鏡像的構建及容器的啟動運行):
Ubuntu
$ docker-compose up
這會根據Dockerfile來構建Flask應用的鏡像,從官方倉庫拉取Redis鏡像,然后將一切運行起來。
現在你可以去喝一杯咖啡,呃,也許是兩杯:首次運行會花費相對較長的時間,事實上Docker會在構建過程中,將Dockerfile中的每一步操作(更正式的說法應該是layer)緩存下來,以后的構建過程會因此提速很多,因為只有發生改變的步驟才會被重新執行。
Docker Compose會并行地啟動全部容器,每個容器都會被分配各自的名字,并且會為日志設置可讀性更高的配色方案。
好了,那么準備好來測試了嗎?
打開你的瀏覽器,輸入主機DOCKER_HOST環境變量所對應的IP地址,例如在我這里是 http://192.168.59.103/(運行boot2docker ip命令可以查詢到IP地址)。
接下來你應該會在瀏覽器中看到以下文本“Hello! This page has been seen 1 times.”:
刷新頁面,如果一切正常的話,計數器變量應該會進行累加。
通過Ctrl-C來終止我們的應用進程,然后通過以下命令讓其改為在后臺運行:
$ docker-compose up -d想查看應用進程的運行狀態?只需要輸入以下命令就可以了:
$ docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------------------------------
fitterhappierdocker_redis_1 /entrypoint.sh redis-server Up 0.0.0.0:6379->6379/tcp
fitterhappierdocker_web_1 python app.py Up 0.0.0.0:80->5000/tcp, 80/tcp
可以看到我們的兩個進程運行在不同的容器中,而Docker Compose將它們組織在一起!
更進一步
在確定一切正常無誤后,使用docker-compose stop命令來終止我們的應用,然后通過boot2docker down來安全地關閉虛擬機。接下來就可以向Git提交本地修改,并推送到Github了。
那么,我們剛才都完成了什么呢?
我們建立了本地環境,通過Dockerfile詳盡描述了如何構建鏡像,并基于該鏡像啟動了相應容器。我們使用Docker Compose來將這一切整合起來,包括構建和容器之間的關聯、通信(在Flask和Redis進程之間)。
接下來,我們來看一個更酷的工作流程,即通過引入CircleCI來實現項目的持續集成。
同樣的,你可以從此處獲取源代碼。
Docker Hub
到目前為止我們已經接觸過Dockerfile、鏡像以及容器(當然,借助了Docker Compose的幫助)。
如果你很熟悉Git的工作流程,那么可以把Docker鏡像理解為Git的repo,而容器類似于該repo的clone,如果將這個比喻繼續類推下去,那么Docker Hub也就相當于Github的地位了。
為了使用Docker Hub,你可以用Github賬號來完成注冊。
添加一個新的自動構建,將剛才完成的項目repo加入進來,一切按照默認選項即可,除了將“Dockerfile Location”改為“/web”。
一旦添加完畢,Docker Hub會進行一次初始化構建,請確保一切正常。
Docker Hub和CI
Docker Hub自身通過 配置就可以充當持續集成服務,從而在每次推送Git提交后自動進行構建。
這意味著你不能直接將鏡像推送到(通過docker push)Docker Hub上。Docker Hub會自己從repo進行拉取并構建鏡像,從而保證整個過程中沒有錯誤。在你的工作流程中請銘記這一點,因為在Docker文檔中目前并沒有對此進行詳細說明。
讓我們來試一下,加入以下測試用例:
self.assertNotEqual(four, 5)
提交并推送到Github,然后就可以看到Docker Hub如何開始一次新構建了。
由于這是項目部署上線前的最后一道防線,我們當然希望Docker Hub在構建完成之前,能夠捕獲所有的錯誤和異常。另外,你肯定也希望能將自己的單元測試和集成測試加入到持續集成流程中, 而這正是CircleCI的用武之地。
CircleCI
CircleCI是一個持續集成/發布平臺,支持對Docker容器進行測試。你只需提供一個Dockerfile,CircleCI會據此構建鏡像,并啟動一個新容器,然后在其中運行你的測試。還記得我們期望的工作流程嗎?鏈接現在來看看如何完成它。
安裝
CIrcleCI官方提供了很好的入門指導。
使用Github賬號完成注冊,然后將你的Github repo添加為一個新項目(成功后會收到郵件通知)。這會為該repo增加一個hook,每當你向其推送新的提交時,都會觸發一次新的構建。
接下來需要向我們的repo添加一個配置文件,用以指導CircleCI完成構建。
circle.yml的文件內容如下:
machine:
services:
- docker
dependencies:
override:
- pip install -r requirements.txt
test:
override:
- docker-compose run -d --no-deps web
- python web/tests.py
實際上,我們構建了一個新的鏡像,并啟動了一個新容器,然后進行測試:首先檢查web應用是否正常啟動運行,然后逐一進行單元測試。
你應該已經注意到我們在這里使用了命令docker-compose run -d --no-deps web而不是docker-compose up來啟動應用,這是因為CircleCI已經集成了可用的Redis運行時環境,所以我們只需啟動web應用進程就可以了。
當circle.yml文件修改完成,就可以推送到Github來啟動一次新構建了。記住,這將同時在Docker Hub上啟動一次構建。
一切正常?
在繼續下去之前,需要對工作流程進行一些調整,因為我們通常不希望將提交直接推送到master分支上。
功能分支工作流程
如果不太熟悉這一工作流程,那么可以從此處獲得準確生動的解釋。
讓我們來快速瀏覽一個示例:
創建功能分支$ git checkout -b circle-test master
Switched to a new branch 'circle-test'更新應用 并在texts.py里增加一個新的斷言:
self.assertNotEqual(four, 6)發起一個Pull Request
$ git add web/tests.py
$ git commit -m "circle-test"
$ git push origin circle-test
甚至在你真正發起PR之前,CircleCI就已經啟動了構建。在PR創建完畢后,只需等待CircleCI通過所有測試,我們就可以點擊Merge按鈕來合并入master分支了。一旦merge成功,Docker Hub就會觸發相應的構建過程。
重構工作流程
如果回到本文開頭的工作流程處,你會發現我們實際上希望Docker Hub在master分支上再次進行測試后才啟動構建,所以讓我們來對現有流程進行以一些快速的調整:
打開你的Docker Hub倉庫,在Settings下選擇Automated Build。
取消對“When active we will build when new pushes occur”的選中狀態。
保存。
選擇位于Settings下的Build Triggers。
將status改為on。
復制以下curl命令:$ curl --data "build=true" -X POST
將以下代碼加入到circle.yml文件末尾:
deployment:
hub:
branch: master
commands:
- $DEPLOY
現在我們會在merge到master分支并通過測試之后,執行$DEPLOY環境變量所代表的命令,我們需要將這個變量的值加入到CircleCi的環境變量中:
打開 Project Settings,選擇 Environment variables。
添加一個名為“Deploy”的新變量,并且將剛才復制的curl命令粘貼進去作為該變量的值。
現在來檢驗一下成果:
$ git add circle.yml
$ git commit -m "circle-test"
$ git push origin circle-test
發起一個新的PR,一旦其通過CirecleCI測試,將其merge到master分支,這會觸發另一次構建。一旦再次通過測試,之前設置的curl命令就會觸發Docker Hub去啟動一次新構建,一切都很完美。
結論
我們已經跑通了這個基于CircleCI的持續集成工作流程(步驟1-6):
在本地功能分支上完成應用代碼。
在Github上發起一個到master分支的Pull Request。
在Docker容器上運行自動測試。
如果測試通過,手動將這個PR merge進master分支。
一旦merge成功,再次運行自動測試。
如果第二次測試也通過,就在Docker Hub上對應用進行構建。
一旦構建完成,自動化地部署到生產環境。
關于整個流程的最后一塊拼圖呢:及自動化地將應用發布到盛傳環境(第7步),你可以在我的另一篇博客中得到答案。(翻譯:李明/審校:劉亞瓊)
原文鏈接:https://realpython.com/blog/python/docker-in-action-fitter-happier-more-productive/%E3%80%91