Preface
舉例來說,假設我的 Github
專案會希望每次Release
時會有下列的行為
- 進行 單元測試/整合測試 等各種確保程式碼正常運作的測試
- 建置
Docker
相關映像檔並且更新到相關的容器倉庫 - 將最新的程式碼部屬到相關的環境上
接下來會跟大家分享一下,在 TravisCI
的設定中,我們有什麼辦法可以滿足上述的需求
本文最後用到的 travisCI
設定檔可以在 TravisCI Example 找到
Build Steps
所謂的「工欲善其事,必先利其器」,在使用 TravisCI
來解決我們的問題之前,我們必須要先瞭解 TravisCI
的基本概念,然後思考如何用這些基本概念來完成我們的需求。
install/script
整個 TravisCI
的生命週期是由兩個主要步驟來構成
- install: 用來安裝任何相依性套件的階段
- script: 真正用來進行
CI
相關測試的階段
除了上述這兩個步驟外,也有所謂類似 Environment
這種非必要
選項可以讓整個 TrasivCI
設定更簡潔
譬如可以讓 TravisCI
幫你準備相關的環境程式語言環境與特定版本,譬如說 golang 1.8, 1.9
對於 install/script
這兩個執行步驟來說,本身為了讓運作邏輯更加細膩,所以又衍生出了前後步驟的概念
- before_install: 該步驟會在
install
前運行,主要是用來準備任何install
步驟所需要的資源,譬如透過apt-get update
等更新套件倉庫。 - before_script: 該步驟會在
script
前運行,如同before_install
一樣,為了script
進行資源準備來滿足真正測試所需,譬如資料庫的建置 - after_script: 當
script
運行完畢後會執行的步驟,實際上還有所謂的after_success
以及after_failure
更細部的針對測試的結果來區分的步驟。
根據相關人員在 Github Issue
的回答, before_install
以及 before_script
的使用時機如下
before_install runs before the install step, which is meant to install any required packages or dependencies. You can prepare things before you run this step, or you can e.g. run sudo apt-get update to refresh the apt indexes.
before_script runs before the actual test/build script runs. It's commonly used to run any preparation steps required to get the build running, for instance copy database configurations, set up any additional environment configuration, and so on.
Example
以下示範一個非常簡單的 .travis.yml
設定檔案,在此環境中,我們要求 TravisCI
準備一個 golang 1.8
版本的環境,同時對於 install
以及 script
這兩個階段我們都執行非常簡單的指令。
language: go
go:
- "1.8"
before_install:
- echo "before_install"
install:
- echo "install"
-
before_script:
- echo "before_script"
script:
- echo "script"
上述的運行結果如下圖,該圖示我們可以觀察到
- golang 版本 1.8
- 四個步驟的結果依序輸出
Solution
有了關於 TravisCI
建置週期的概念後,回過頭來探討一些下最初的需求
- 進行 單元測試/整合測試 等各種確保程式碼正常運作的測試
- 建置
Docker
相關映像檔並且更新到相關的容器倉庫 - 將最新的程式碼部屬到相關的環境上
首先,這三個要求是有依賴性的,前面的失敗,後面的就不需要運行。 這邊沒有一個標準答案,有非常多的實現方式,譬如說
- 將所有的步驟都放在
script
這個步驟去依序執行 - 使用
script
,after_script
甚至是其他deploy
等不同的步驟來依序完成這些事情
譬如下面範例 (單純舉例)
language: go
go:
- "1.8"
before_install:
- echo "before_install"
install:
- echo "install"
before_script:
- echo "before_script"
script:
- go test -v ./...
after_script:
- sudo docker build -t ....
- sudo docker push ....
deploy:
- ./deploy.sh
可以到官方網頁這邊學到更多不同的建置步驟以及彼此之間的先後關係 https://docs.travis-ci.com/user/customizing-the-build/
用下列這張圖來幫這個章節做一個總結
Job
Definition
瞭解了基本的用法後,我們要來看看一些關於 TravisCI
的進階用法,看看透過這些進階用法我們能夠完成什麼樣更豐富的 CI
流程。
首先,我們先定義什麼叫做 Job
, Job
就是一個歷經 TravisCI
生命週期所有步驟的基本單位。
所以一個Job
簡單來說會經歷過 Environment
, before_install
, install
, before_script
, script
以及 after_script
所有步驟
這邊列舉的步驟並不是TravisCI
的所有步驟,只是舉出幾個常見的步驟
Multiple Job
有了 Job
的基本概念後,我們就可以往下思考一些更進階的用法。
假設專案本身是透過 golang
程式語言撰寫而成的,我們現在希望測試該專案在不同 golang
版本下是否都能夠正常運行。
舉例來說,我希望使用 golang 1.8
以及 golang 1.9
這兩個版本來進行專案的測試。
問題來了,這種情況下,我們要如何透過TravisCI
來完成呢?
最直覺的就是我們什麼都硬幹,自己在 TravisCI
內去安裝各式各樣的環境,然後撰寫腳本去分開各式各樣的測試,將所有的需求都在一個Job 內完成。
當然這種情況下整個環境準備/測試等相關的邏輯就會複雜且不好維護
為了讓整個測試的架構乾淨與明瞭,我們可以透過 Travis
平行的運行多個Job
來滿足我們的需求。
在此架構下, Travis
會併行的去運行這些 Job
, 且每個 Job
都有自己的建置週期,所有的 Job
都要都要成功該次測試才算成功。
使用下列圖示再次說明 Multiple Job
的概念
上方描述的是一個簡單的 Jobs
概念,涵蓋了本文提及的基本建置週期。
下面則是為了滿足特別需求,希望多個golang
版本同時測試,此時我們就可以一次運行多個 Job
, 其中只有 Environemnt
的部份是完全獨立,其餘則是都會採用相同的設定。
在 TravisCI
的設定檔案 .travis.yml 裡面,我們可以用下列的方式完成這個需求
language: go
go:
- "1.8"
- "1.9"
before_install:
- echo "before_install"
install:
- echo "install"
before_script:
- echo "before_script"
script:
- echo "script"
最後運行的結果如下, 可以看到該次的測試同時運行了兩個 Job
, 這兩個 Job
分別是不同的 Golang
版本。
Custom Job
上述我們利用個 go
這個由 TravisCI
所定義的語法來完成產生 MultipleJob
的功能。
這時候腦筋一轉,install
,script
這些建置步驟是否也都可以有類似的概念呢?
舉例來說,我希望對我的專案進行不同的測試,譬如 Unit Test
, Integration test
。
而這些測試除了測試的方式不同之外,環境的準備也不同
此外,同時運行這些測試也能夠減少測試的時間,並且將測試結果更清楚的標示出是哪種測試出問題。
將上述的需求轉換成 TravisCI
的概念的話
就是需要同時運行多個 Job
, 每個 Job
裡面對於每個建置步驟都有自己客製化的需求。
這個需求我們透過下圖視覺化的方式來重新檢視一次
我們的需求很簡單,希望同時運行多個 Job
且這些 Job
針對不同的運行階段能夠選擇是否要使用預設的規則或是客製化自身的需求。
TravisCI
就有提供了這樣的功能供各位去使用,在其 .travis.yml
這個 yml
的檔案中,我們要透過 jobs:include
的概念去撰寫
language: go
go:
- "1.8"
before_install:
- echo "before_install"
install:
- echo "install"
before_script:
- echo "before_script"
script:
- echo "script"
jobs:
include:
- stage: Custom Testing
name: Unit-Testing
go: "1.8"
script: echo "unit script"
- name: Integration-Testing
before_install: "Integration-Testing_before_install"
go: "1.9"
script: "Integration-Testing_script"
其運行結果如下
我們可以觀察到我們的確運行了兩個 Job
而這兩個 Job
都有明確的名稱,這邊就沒有點進去看各自 Job
的運行結果,有興趣的人可以自行嘗試看看。
這邊先不討論語法,等到所有的概念都講述完畢後,再來討論語法的撰寫。
Build Stage
有了上述的 Multiple Job
的概念後,我們重新審視一下最初的需求
- 進行 單元測試/整合測試 等各種確保程式碼正常運作的測試
- 建置
Docker
相關映像檔並且更新到相關的容器倉庫 - 將最新的程式碼部屬到相關的環境上
首先,不同的測試本身可以透過同時運行多個 Job
來滿足,這邊好處理。
那建置/更新 Docker Image
這件事情,我們要讓誰來處理?
- 上述的測試選一個
Job
, 客製化其某些建置步驟來處理 - 額外產生一個
Job
來專門處理這類的需求
只是採用第一種方式可能會有一個問題
假設我們需要 所有
測試都通過才能進行 Docker
相關的處理,那我們就沒有辦法在任意一個測試 Job
內去處理這個邏輯。
為了解決這個問題,除了將所有的工作重新集中回一個Job
處理外,就只能在開啟第三個 Job
來處理。
但是這個 Job
本身有相依性的問題,它必須要確認前述相關測試的所有 Job
都完成才能夠繼續往下運行。
為了解決這個問題,我們要在這邊介紹 Stage
這個概念。
Stage
的特色以及概念如下
- 由一群
Job
組成 - 只要有一個
Job
失敗,該Stage
就會被視為失敗 - 只有當該前 Stage 是成功的狀態,才會執行下一個 Stage
有了 Stage
的概念,我們可以把上述的需求重新整理歸納成三個 Stage
- Testing Stage
- Unit Testing Job
- Integration Testing Job
- Docker Build Stage
- Deploy Stage
將這個概念用下圖再次檢視一次
Example
每個 Stage
之間彼此有依賴性,只要當其中一個 Stage
失敗,就不會往下執行
下圖是每個 Stage
都順利執行的成果
下圖則是當第一個 Testing Stage
有任何失敗的結果
language: go
go:
- "1.8"
before_install:
- echo "before_install"
install:
- echo "install"
before_script:
- echo "before_script"
script:
- echo "script"
jobs:
include:
- stage: Custom Testing
name: Unit-Testing
script: echo "unit script"
- name: Integration-Testing
before_install: echo "Integration-Testing_before_install"
- stage: Build Docker Image
script: echo "docker build"
- stage: Deploy
script: echo "release"
首先,我們要先定義 Stage
, 在 Stage
裡面可以定義多個 Job
, 而每個 Job
內又可以自定義每個建置階段,若沒有特別設定的,都會採用最上層的全域設定
定義 Stage
則採用 stage
這個關鍵字來幫建立,並且命名,針對每個 job
可以透過 name
的方式把對應的名稱替換掉讓整個測試報告更有閱讀性,然後接下來就可以去描述每個建置步驟,如 script
, install
等各式各樣的建置週期步驟。
更詳細的設定可以直接參考官網的說明
Summary
本文跟大家分享了關於 TravisCI
的使用心得,從基本的使用方法到進階的 Multiple Job
以及 Stage
透過這些概念的組合,我們能夠將 CI/CD
的流程拆的更細緻,讓整個架構與流程更加清楚,同時透過平行運行的方式加快整體流程的速度 (這部份不一定,完全是看每個專案的流程).