搭建基于Travis CI和GitHub的自動化測試工作流
在上個月的博客中我們討論了如何 將博客Docker化( 譯文鏈接)。但是那篇文章我沒有講我是怎么使用Docker Hub的 自動構建功能來在包含了本博客源碼的GitHub倉庫有改動的時候自動地構建一個新的鏡像。
自動構建非常有用。因為我只需要簡單地改動倉庫中的代碼或者文章,然后一旦push,這些改動都會觸發Docker Hub使用我們前一個文章中創建的 Dockerfile
來構建一個鏡像。另外一個好處是Docker鏡像也會在Docker Hub中獲取到,這意味任何安裝了Docker的系統都可以通過執行 docker run -d madflojo/blog
這樣簡單的命令來部署最新的博客版本。
唯一的問題在于,假如這些改動具有破壞性怎么辦?假如一次構建讓整體構建都無法完成,或者更糟的,讓靜態網站生成器不再正確的生成頁面。我需要的是一個方法在他們被合并到 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。 盡管安裝好Python只需要使用幾個
apt-get
命令就能搞定,但是因為Python是唯一我們在這個環境中需要的語言,將其在 .travis.yml
文件中用 language: python
參數來將其定義為基礎語言是更好的做法。 language: python python: - 2.7 - 3.5
上面的配置讓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
在上邊這個例子我們僅僅通過將3.2添加到這個列表完成了就添加這個版本支持。
必需的服務
因為我們將要構建一個Docker鏡像我們也需要Docker安裝好,并且需要Doker服務在環境中運行,我們可以通過使用services
參數來讓Travis CI來安裝Docker并且啟動Docker服務。 services: - docker
與
python
參數類似 services
參數是一個需要在我們環境中啟動的服務的列表。這意味著通過向這個列表添加條目就能添加額外的服務。假如我們需要Docker和Redis服務,那只需要指定Docker服務下面那一行再添加一行。 services: - docker - redis-server
在這個例子中我們不需要除Docker之外的任何服務,然而知道Travis CI 支持很多服務是很有用處的。
進行構建
現在我們已經定義好的我們想要的構建環境,我們可以執行構建步驟了。因為我們想驗證一個Docker的構建我們基本上需要執行兩個步驟:構建一個Docker的鏡像,并且啟動一個基于該鏡像的容器。我們可以依靠指定在前一篇文章中使用的
docker
命名來一步步的執行這些步驟。 install: - docker build -t blog . - docker run -d -p 127.0.0.1:80:80 --name blog blog
在上面我們可以看到在
install
參數下面兩個 docker命令。這個
install`參數實際上是Travis CI的一個定義好的構建步驟。 Taravis CI有多個在構建中預定義的步驟,在
.tarvis.yml
文件中可以對這些步驟進行指定。在上面的例子中我們定義了那兩個 docker
命名令是安裝這個應用的兩個必需步驟。 測試構建
Travis CI不僅僅是一個簡單的構建工具,它還是一個持續構建工具,這意味著它的主要功能是進行測試。這也意味著我們需要在構建步驟中添加一個測試;暫時我們可以簡單地驗證是否Docker容器是否能運行,這可以通過執行命令docker ps
命令。 script: - docker ps | grep -q blog
在上面,我們通過使用
script
參數定義了我們基本的測試。這是另外一個我們可調用測試用例的構建步驟。 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
在Travis CI的注冊過程中,你會被詢問到將你的倉庫和Travis CI進行連接。這能讓它監控倉庫發生的任何改動。當改動發生的時候,Travis CI會自動的拉取這些改動并執行在
.travis.yml
文件中定義好的步驟。在這個例子中,意味著它會執行我們的Docker構建然后驗證是否一切工作正常。 就在我們將我們新的改動推到倉庫中的時候,Travis CI應該已經檢測到了這些更改。我們可以到Travis CI中驗證時候這些改動的結果是否能夠構建成功。
Travis CI將會為每一個構建顯示一個構建日志,在這個特定構建日志的末尾,我們可以看到構建成功了。
Removing intermediate container c991de57cced Successfully built 45e8fb68a440 $ docker run -d -p 127.0.0.1:80:80 --name blog blog 45fe9081a7af138da991bb9e52852feec414b8e33ba2007968853da9803b1d96 $ docker ps | grep -q blogThe command "docker ps | grep -q blog" exited with 0.
Done. Your build exited with 0.</pre>
Travis CI的一個很重要的一點是,絕大多的構建步驟需要命令成功執行才能讓構建被標記為成功。
步驟script
和install
就是兩個例子,如果這兩個步驟中的任何命令失敗了,沒有返回0的 退出碼那么整個構建將被標記為失敗。
如果這發生在install
步驟階段,構建將會終止在該步驟發生的那一個點。然而對于script
步驟,構建將不會停止。這背后的想法是,如果一個install
步驟失敗了,構建無論如何也不會成功;然而假如一個單個測試用例失敗了,只有一部分功能會失敗。最終將所有的測試結果顯示給用戶,用戶能自己辨別出哪些沒有正常工作,哪些是符合預期工作正常。添加額外的測試
盡管現在我們Travis CI能驗證是否Docker構建成功與否,仍然有很多其他的可能讓我們不經意地破壞這個博客的運行。比如我們可能做了一個改動,讓網站靜態生成器無法繼續正常生成頁面,這會破壞容器中的網站但不一定會破壞容器自身。要避免類似這樣的情形發生,我們需要添加一些額外的測試。
在我們的倉庫中,有一個目錄叫做tests
,這個目錄包含三個目錄,unit
,integration
和functional
。這些目錄包含了這個環境的多種自動化測試。前兩個測試類型unit
和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
這些測試是用來連接到運行的Docker容器并且驗證靜態網站的內容。
比如,test_broken_links.py
將會爬取由Docker容器服務的網站,然后檢測每一個頁面HTTP的返回狀態碼。如果狀態碼不是200 OK
那么測試就會失敗。test_content.py
也會爬取網站并且驗證返回的內容,看其是是否與一個特定的模式匹配。如果不匹配,那么這些測試還是會失敗。
這些測試有用之處在于,即使靜態網站在Docker容器中運行,我們仍然能夠測試網站的功能性。如果我們能像Travis CI的配置添加這些測試,它們也會在每一次構建的時候運行;可以給我每一次發生的更改更多信心。
用
要通過Travis CI來運行這些測試,我們只需要簡單地將他們添加到before_scriot
安裝測試的必須條件script
部分,正如我們添加docker ps
命令一樣。然而在它們可以被執行之前,這些測試需要安裝好一些Python的庫。要安裝這些庫我們可以將安裝步驟放在before_script
的構建步驟里面:
before_script: - pip install -r requirements.txt - pip install mock - pip install requests - pip install feedparser
before_script
構建步驟會在script
步驟之前,但在install
步驟之后執行。這讓before_script
是一個完美的放置script
的預先條件,但是不屬于整個安裝過程的地方。因為before_script
不像install
步驟一樣會執行安裝操作,它也要求所有的命令都成功執行才會繼續script
構建步驟。如果before_script
構建的一個命令失敗了,那么構建將會失敗。
運行額外的測試
在必須的Python庫安裝好我們可以添加測試添加到script
步驟中:
script: - docker ps | grep -q blog - python tests.py
這些測試可以通過通過執行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
當把更新提交到倉庫之后我們可以坐下來然后靜靜等待Travis CI構建并測試我們的應用了。
#Test Runner: Functional testsrunTest (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</pre>
一旦構建完成我們可以在構建日志中看到如上的消息,顯示Travis CI已經真正地運行了我們的測試。總結
當我們我們構建成功處理完成,然后我們最后看看我們.travis.yml
文件的樣子:
language: python python: - 2.7services: - 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來自動構建Docker鏡像并且對其做功能性測試。
在接下來的博文里,我們將會更進一步,自動地將這些更改使用SaltStack部署到多個服務器。在下一個文章結束的時候,我們就有了一個完整的持續集成和部署工作流,這會讓更改經過測試,并且不需要人為的干預就能部署到生產環境。
原文鏈接:Using Travis CI to test Docker builds(翻譯:鐘最龍)