財報狗用 CircleCI 有一段時間了,一開始用的時候還是 1.0,2.0 還在 beta 階段。我們的專案在 1.0 上面一直運作良好,所以也沒有想說要換,直到 CircleCI 發佈 2018/8/31 之後將不再支援 1.0,我們才開始考慮要遷移專案 XD,順便趁着這次的遷移導入的 2.0 的新功能: workflow。
Workflow 簡介
CircleCI 在 2.0 中推出了 workflow 這個新功能,官網的介紹如下:
A workflow is a set of rules for defining a collection of jobs and their run order. Workflows support complex job orchestration using a simple set of configuration keys to help you resolve failures sooner.
簡單來說,workflow 提供了一種做法,讓你可以把 CI 的每個步驟拆分成一個個獨立的 job,job 之間遵照一定的執行順序或是並行,萬一發生失敗, 只要針對失敗的 job 去做處理就好了,大大提升反應速度。
Workflow 解決了什麼問題?
- CircleCI 1.0 中,CI 的每一步流程都是循序的,像是必須要執行完
bundle install
才可以繼續yarn install
,但是這兩個 job 彼此間並沒有相依性,因此在 workflow 中,可以設計讓沒有相依的 job 並行節省時間;開的 container 越多,可以並行的 job 也就越多。 - 用循序的方式執行 job,只要其中一個 job 失敗,就需要整個 CI 重來;而 workflow 中的每一個 job 彼此是獨立的,可以只針對失敗的 job 重跑即可。
Workflow 設定
在使用 Workflow 之前,需要先想好專案的 CI 流程可以獨立成哪幾個步驟;以財報狗專案來 說,可以分成以下幾個:
- checkout_code: 將程式碼從遠端 pull 下來,在 CI 環境中做好路徑設定
- bundle_dependencies: 安裝 rails application 相依性套件
- yarn_dependencies: 安裝前端相依性套件
- run_unit_test: 執行單元測試
- run_integration_test: 執行整合測試
- deploy: 將程式佈署到 production
以循序的方式去跑上面這些步驟,大約需要花 16 分鐘
每次都循序跑實在有點沒效率,以上面的流程來說, bundle_dependencies
、yarn_dependencies
在 checkout_code
結束之後就可以同時執行; run_unit_test
以及 run_integration_test
也一樣,只要相依性套件裝完,單元測試跟整合測試是可以一起跑的,這也就是 workflow 想解決的問題之一;在 CricleCI 中只要將 job 定義好 ,就可以很輕易的設定 workflow:
workflows:
version: 2
build_and_test: # Workflow 的名稱
jobs:
- checkout_code
- bundle_dependencies:
requires: # 相依於哪些 jobs
- checkout_code
- yarn_dependencies:
requires:
- checkout_code
- run_unit_tests:
requires:
- bundle_dependencies
- run_integration_tests:
requires:
- yarn_dependencies
- bundle_dependencies
- deploy:
requires:
- run_unit_tests
- run_integration_tests
設定好之後,CircleCI 就會自動以 workflow 的方式執行:
紅色框框圈起來的是並行執行的部分,可以看到比較花時間的 run_unit_test
以及 run_integration_test
分別需要跑 4:58 以及 6:19,而因爲並行跑的關係,只要 6:19 就跑完了,整個 CI 的時間也足足從 16 分鐘縮短到 8 分多鐘,速度變快超有感的阿!
重跑 workflow 失敗的 jobs
剛剛提到 workflow 的好處除了並行縮短執行時間外,還有一點就是可以只重跑失敗的 job,而不需要重跑整個 CI。
爲什麼這很重要呢?在跑 CI 的時候,偶爾會遇到一些 random fails,像是網路突然不穩,或是套件安裝因爲不明原因失敗等等,像下面這樣:
仔細檢查會發現是 deploy 階段在 ssh 連到 server 的過程中 timeout 了,這可能是 server 那邊設定出了點問題,通常這種情況只要進到 server 改好設定,重跑一次 CI 就可以解決,不過如果只是 deploy 階段出錯,重跑還需要從設定環境、裝套件、跑完測試後才可以 deploy,實在太沒效率了,所以在 workflow 中,很貼心的讓你可以選擇只跑失敗的 jobs,進一步的節省時間。
善用 cache
在 workflow 中,每個 job 都是運作在一個獨立的 container 當中,爲了確保每個 job 可以獨立運行,所以可能會需要前一次 CI 或是前一個 job 產生的一些資料,這時候 cache 就派上用場了。
舉例來說,像是在 bundle_dependencies
階段會安裝所需要的套件,但是我們並不想要每次跑 CI 的時候都重裝全部的套件,只想要裝之前沒裝過的;又或者是在 run_unit_test
階段時,會需要用到在 bundle_dependencies
階段裝好的整包套件資料,那這時候我們就可以在 bundle_dependencies
階段,把 bundle install
所產生的資料儲存成 cache,等到下一個 job 或是下一次 CI要使用的時候,再用 cache key 把存的資料取出來用。
以 bundle install
爲例,裝好的 bundle 可以用 save_cache
儲存起來:
- save_cache:
key: v1-gem-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
如果之後的 job 或是 build 需要用到這包裝好的 bundle ,只需要 restore_cache
即可:
restore_cache:
keys:
- v1-gem-{{ checksum "Gemfile.lock" }}
- v1-gem-
這邊 CircleCI 給了一個彈性,可以設定多組 key, 萬一使用第一個 key (v1-gem-{{checksum “Gemfile.lock"}}
) 找不到符合的結果,就會使用下一個順位,也就是 v1-gem-
這個 key,去比對最近產生,且前綴符合 v1-gem-
的快取資料。
不要設計太小的 job
由於每個 job 在運作之前仍然需要準備好所需要的環境與資料才能夠運行,即使有做好 cache,但是在取得 cache 的過程中,只要檔案稍大,仍然需要花費 10~20 秒的時間,如果是一個 30 秒內就可以執行完的 command,不太建議拉出來成爲一個獨立的 job,反而會拖慢整個 CI 的速度。
最後…
還是對 workflow 的設定有些不瞭解的話,可以到我的 Gist 看詳細的設定 ,有問題或是有什麼 CI 設定上的建議,也歡迎留言討論 😃。
友情支持拍 1 下手👏;如果文章對你有幫助,可以拍更多(最多 50 下)👏👏👏👏👏👏👏👏👏👏。這可以讓我知道未來要先寫哪一方面的文章。