使用Travis CI來測試Docker構建
在過去幾個月的博客中我們討論了如何 Docker化這個博客 。但是那邊文章我們有提到的是我怎么使用Docker Hub的 自動構建 功能來自動在包含了本博客源碼的Github倉庫有改動的時候創建一個新的鏡像。
自動構建非常有用因為我可以簡單地在倉庫中的代碼或者文章然后一旦push,這些改動都會觸發Docker Hub使用我們前一個文章中創建的 Dockerfile 來構建一個鏡像。另外一個好處是Docker鏡像也會在Docker Hub中獲取到,這意味這人和安裝了Docker的系統可以靠簡單的執行 docker run -d madflojo/blog 來部署最新的版本。
唯一的問題在于,假如這些改動會破壞部署怎么辦?假如一個構建讓構建不在有用,或者更糟的情況讓靜態網站生成器不在正確的生成頁面。我需要的是一個方法在他們被merge到 master 分支并部署到生產環境之前,提前知道是否一個改動將會造成問題。
要達到這一點,我們可以利用 持續集成 的原則和工具。
什么是持續集成
持續集成(Continuous Integration)或者CI,是一個已經在軟件開發中已經出現好一陣子的東西了,但是最近逐漸在運維界中變得火了起來。CI提出來是為了解決多個開發者在同一個代碼庫造成集成的問題。基本上,兩個開發者在同一樣的代碼上進行開發會制造沖突,并且只有在之后很久才會發現這些沖突。
基本的原則是,越往后發現代碼中的問題,要修復這些問題就代價越昂貴(不管是時間還是金錢)。要解決這個問題讓開發者更加頻繁的將代碼提交到版本控制中,一天平均提交多次。隨著代碼頻繁的提交并推到代碼庫,這能減少代碼集成的問題,并且即使在真正發生問題的時候,也能更加容易的被解決。
然后一天多次的提交代碼自身無法解決集成問題。還需要一個方法來保證被提交的代碼質量過關并且能運行良好。這就引出了CI的另外一個概念,每當代碼提交的時候,代碼就會進行構建并且自動的被測試。
在本博客的這個場景中,這個構建將會包含構建一個Docker鏡像,并且測試將會包含多個我編寫的測試來保證支撐本博客的代碼是能正常工作的。要執行這些自動化的構建和測試,我們需要一個工具能檢測更改發生,并且執行必要的操作;我們需要一個類似 Travis CI 的工具。
Travis CI
Travis CI是一個持續構建工具,與Github集成并且能執行自動化的構建和測試操作。它對于公共的Github倉庫免費,比如本博客。
在這個文章中,我將會過一遍如何配置Travis CI來自動構建和測試為這個博客生成的Docker鏡像,這會教會你如何使用Travis CI來測試你自己的Docker構建的基礎知識。
使用Travis CI來自動化Docker構建
這個博客將會假設你已經注冊了Travis CI并且將其連接到我們我們的公共倉庫。這個過程十分的簡單,這是一個Travis CI上手的一部分。如果你需要更加詳細的教程,Travis CI有一個 新手上路 指南。
因為我們將會測試我們的構建,不希望影響主要的 master 分支,第一件事情要做的就是創建一個新的 git 分支然后在這個分支中做實驗。
$ git checkout -b building-docker-with-travis
當我們想這個分支做改動的時候,我們可以將內容提交到Github中統一名稱分支下,然后驗證Travis CI的構建結果,而不需要將更改影響到 master 分支。
配置Travis CI
首先在我們新創建分支中,創建一個 .travis.yml 文件,這個文件主要包含Travis CI需要用到的配置和指令。在這個文件中我們能告訴Travis CI構建的環境需要使用什么語言,需要運行什么服務,以及進行構建需要需要執行什么指令。
定義構建環境
在開始任何的構建之前我們需要定義好構建環境是什么樣子的。比如,因為 hamerkop 應用和相關的測試腳本是用Python編寫的,我們需要在測試環境中安裝Python。
盡管我們可以使用幾個 apt-get 命令來安裝好Python,但是因為Python是唯一我們在這個環境中需要的語言,將其在 .travis.yml 文件中用 language: python 參數來將其定義為基礎語言是更好的做法。
language: pythonpython:
2.7
3.5</pre>
上面額配置讓Travis CI來將構建環境設置為一個Python的環境;分別需要Python的2.7和3.5的版本安裝好并且支持。
上面使用的語法是用YAML文件格式指定的,這是一種十分流行的配置格式。在上面我們主要將 language 參數定義為python并且將 python 參數設置為一個包含值2.7和3.5的列表。如果我們需要添加額外的版本,那就簡單地在這個列表添加該版本,如下邊這個例子:
language: python
python:
2.7
3.2
3.5</pre>
在上邊這個例子我們僅僅通過將3.2添加到這個列表就添加這個版本支持。
必須的服務
因為我們將要構建一個Docker鏡像我們也需要Docker安裝好并且需要Doker服務在環境中運行,我們可以通過使用 services 參數來告訴Travis CI來安裝Travis CI來安裝Docker并且啟動該服務。
services:
docker</pre>
與 python 參數類似 services 參數是一個需要在我們環境中啟動的服務的列表。這意味著靠添加向這個列表就能包含額外的服務。加入我們需要Docker和Redis,我們僅僅需要在指定Docker服務下面那一行添加一行。
services:
docker
redis-server</pre>
在這個例子中我們不需要除Docker之外的任何服務,然后知道Travis CI 支持很多服務 是很有用處的。
進行構建
現在我們已經定義好的我們想要額構建環境,我們可以執行構建步驟了。因為我們想驗證一個Docker的構建我們基本上需要執行兩個步驟,構建一個Docker的鏡像,并且啟動一個基于該鏡像的容器。
我們可以依靠指定在前一篇文章中中使用的同樣的 docker 命名來一步步的執行這些步驟。
install:
docker build -t blog .
docker run -d -p 127.0.0.1:80:80 --name blog blog</pre>
在上面我們可以看到在 install 參數下面兩個`docker命令。這個參數實際上是Travis CI的一定義好的構建步驟。
Taravis CI有多個在構建中預定義的步驟,在 .tarvis.yml 文件中可以指定這些步驟。在上面的例子中我們定義了那兩個 docker 命名令安裝這個英語哦你個必要的步驟。
測試構建
Travis CI不僅僅是一個簡單的構建工具,它還是一個持續構建工具,這意味著它的主要功能是進行測試。這也意味著我們需要在構建步驟中添加一個測試;暫時我們可以簡單地驗證是否Docker容器是否能運行,這可以通過執行命令 docker ps 命令。
script:
docker ps | grep -q blog</pre>
在上面,我們通過使用 script 參數定義了我們基本的測。這是另外一個我們可uiuyonglai調用測試用例的構建步驟。 script 步驟是一個必須的步驟,如果省略了那構建就會失敗。
Push到Github
在上面定義的步驟中,我們有了一個能發送給Travis CI最小化的構建;要達到這個目標我們可以簡單的將我們的改動push到Github。
$ git add .travis.yml
$ git commit -m "Adding docker build steps to Travis"
[building-docker-with-travis 2ad7a43] Adding docker build steps to Travis
1 file changed, 10 insertions(+), 32 deletions(-)
rewrite .travis.yml (72%)
$ git push origin building-docker-with-travis</pre>
在Travis CI的注冊過程中,你會被問到將你的倉庫和Travis CI進行連接。這能讓它監控倉庫發生的任何改動。當改動發生的時候,Travis CI會自動的拉取這些改動并且在 .travis.yml 文件中定義好的步驟。在這個例子中,意味這執行我們的Docker構建并且驗證是否一切工作正常。
就在我們將我們新的改動推到倉庫中的時候,Travis CI應該已經檢測到了這些更改。我們可以到Travis CI中驗證時候這些改動的結果是否能帶來一個成功的構建與否。
Travis CI,將會為每一個構建顯示一個構建日志,在這個特定構建日志的末尾,我們可以看到構建成功了。
Removing intermediate container c991de57ccedSuccessfully built 45e8fb68a440
$ docker run -d -p 127.0.0.1:80:80 --name blog blog
45fe9081a7af138da991bb9e52852feec414b8e33ba2007968853da9803b1d96
$ docker ps | grep -q blog
The command "docker ps | grep -q blog" exited with 0.
Done. Your build exited with 0.</pre>
要知道Travis CI的一個重要點是,絕大多的構建步驟需要命令按順序地成功執行才能讓構建被標記為成功。
script 和 install 步驟是這兩個例子,如果任何命令失敗了,沒有返回0的 退出碼 那么整個構建將被標記為失敗。
如果這個發生在 install 步驟階段,構建將會停止在該步驟發生的那一個點。然而對于 script 步驟,構建將不會停止。這個背后的想法是如果一個安裝步驟失敗了,構建無論如何也不會成功然后如果一個單個測試用例失敗了,只有一部分功能失敗。將所有的測試結構顯示給用戶,用戶能自己辨別出什么是壞了,什么是如預期一樣的工作正常。
添加額外的測試
盡管現在我們Travis CI能驗證是否Docker構建成功與否,仍然后很多其他的途徑我們可以不經意的破這個博客。比如我們做一個改動讓網站靜態生成無法正常生成頁面,這會破壞容器中的網站但不一定會損壞容器自身。要防止像這樣的情形發生,我們添加一些額外的測試。
在我們的倉庫中,有一個目錄叫做 tests ,這個目錄包含三個其他的目錄, unit , integration 和 functional 。這些目錄包含多種這個環境的自動化測試。頭兩個測試類型 uint 和 intergration 是被設計來測試在 hamerkop.py 應用中的代碼的。盡管其很有用但是這些測試對于測試Docker容器卻幫不上忙。然后最后一個目錄, functional ,包含了可以用來測試運行中的Docker容器的自動化測試。
$ ls -la tests/functional/total 24
drwxr-xr-x 1 vagrant vagrant 272 Jan 1 03:22 .
drwxr-xr-x 1 vagrant vagrant 170 Dec 31 22:11 ..
-rw-r--r-- 1 vagrant vagrant 2236 Jan 1 03:02 test_broken_links.py
-rw-r--r-- 1 vagrant vagrant 2155 Jan 1 03:22 test_content.py
-rw-r--r-- 1 vagrant vagrant 1072 Jan 1 03:13 test_rss.py</pre>
這些測試是設計來連接到運行的Docker容器并且驗證靜態網站的內容。
比如, test_broken_links.py 將會爬取由Docker容器服務的網站然后檢測每一個頁面HTTP的返回狀態碼。如果狀態碼不是 200 OK 那么測試就會失敗。 test_content.py 也會爬取網站并且驗證返回的內容,看其是是否與一個特定的模式匹配。如果不匹配,那么這些測試還是會失敗。
這些測試有用之處在于,即使靜態網站在Docker容器中運行,我們仍然能夠測試網站的功能性。如果我們能像Travis CI的配置添加這些測試,它們也會在每一次構建的時候運行;可以給我每一次發生的更改更多信心。
用 before_scriot 安裝測試的必須條件
要通過Travis CI來運行這些測試,我們只需要簡單地將他們添加到 script 部分正如我們添加 docker ps 命令一樣。然而在它們可以被執行之前,這些測試需要安裝好一些Python的庫。要安裝這些庫我們可以將安裝步驟放在 before_script 的構建步驟里面:
before_script:
pip install -r requirements.txt
pip install mock
pip install requests
pip install feedparser</pre>
before_script 構建步驟會在 script 步驟之前但是在 install 步驟之后執行。這讓 before_script 是一個完美的放置 script 的預先條件,但是不屬于整個安裝過程的地方。因為 before_script 不像 install 步驟一樣會執行安裝操作,它也要求所有的命令都成功執行才會繼續 script 構建步驟。如果 before_script 構建的一個命令失敗了,那么構建將會失敗。
運行額外的測試
在必須的Python庫安裝好我們可以添加測試添加到 script 步驟中:
script:
docker ps | grep -q blog
python tests.py</pre>
這些測試可以通過通過執行 tests.py 來啟動,然后執行所有的是哪個自動化測試: unit , intergration 和 functional 。
再次進行測試
在測試添加好我們可以再一次將我們的更改提交到Github。
$ git add .travis.yml
$ git commit -m "Adding tests.py execution"
[building-docker-with-travis 99c4587] Adding tests.py execution
1 file changed, 14 insertions(+)
$ git push origin building-docker-with-travis</pre>
當把更新提交到倉庫之后我們可以坐下來然后靜靜等待Travis CI構建并測試我們的應用了。
#
Test Runner: Functional tests
#
runTest (test_rss.VerifyRSS)
Execute recursive request ... ok
runTest (test_broken_links.CrawlSite)
Execute recursive request ... ok
runTest (test_content.CrawlSite)
Execute recursive request ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.768s
OK
一旦構建完成我們可以在構建日志中看到如上的消息,顯示Travis CI已經真正地運行了我們的測試。
總結
當我們我們構建成功處理完成,然后我們最后看看我們 .travis.yml 文件的樣子:
language: pythonpython:
- 2.7
services:
- docker
install:
docker build -t blog .
docker run -d -p 127.0.0.1:80:80 --name blog blog
before_script:
pip install -r requirements.txt
pip install mock
pip install requests
pip install feedparser
script:
docker ps | grep -q blog
python tests.py</pre>
在上面我們可以看到Travis CI配置包含三個構建步驟: install , before_script 和 script 。 install 步驟是用來構建并且啟動我們的Docker容器。 before_script 步驟只是用來安裝測試腳本所必需庫,然后 script 用來執行我們的測試腳本。
整體上,這個設置是非常簡單的,即使不用Travis CI我們也可以手動的就行測試。然而,使用Travis C的好處在于所有這些步驟會在每一次改動的時候被執行,不管這些改動多么細微。
并且,因為我們使用Github將會將構建的狀態通知附加到每一個拉取請求中, 比如這個 。有了這些提醒,在合并這種類型的拉取請求的時候我就有信心它們不會對生產環境造成破壞。
構建一個持續集成和部署的流水線
過去幾個的博文里,我們探索了使用Docker來打包并發布運行本博客的應用。在這個文章中,我們討論了利用Travis CI來自動ing構建Docker鏡像并且對其做功能性測試。
在接下來幾個月的的博文里,我們將會這個步驟改進,自動地將這些更改使用SaltSTack部署到多個服務器。在下一個文章結束的時候我們就有了一個完整的持續集成和部署工作流,這會讓更改進過測試并且不需要人為的干預就能部署到身生產環境。