CI/CD Pipeline 之 CI Service 掛掉時該怎麼辦?

本系列文是從 iT 邦幫忙鐵人賽系列文章搬至 gitlab-book.tw,鐵人賽撰文時 GitLab 仍為 12.x 版本,因此本系列文內容已有部分過期,本次搬移至此後,會視狀況新增一些補註說明。

本系列文是從 iT 邦幫忙鐵人賽系列文章搬至 gitlab-book.tw,鐵人賽撰文時 GitLab 仍為 12.x 版本,因此本系列文內容已有部分過期,本次搬移至此後,會視狀況新增一些補註說明。 鐵人賽原文網址:https://ithelp.ithome.com.tw/articles/10224332

今天我們要繼續改善 CI/CD Pipeline。

首先,我們要回歸初心,詢問幾個問題。

第一個問題——為什麼我們需要做 CI/CD ?

第二個問題——如果工程師修改了一行程式碼,需要多久才能部署上線?

最後的問題——如何做到讓任何人可以任意的取得任何版本的程式?
(當然前提是這個人本來就是有取得程式的權限,也需要取得這個特定版本的程式。)

這三個都是團隊在建構 CI/CD 時非常重要的問題,務必要記在心中常常捫心自問,才不會在 CI/CD 的路上走偏了。(謎之音:是可以走到哪裡去~)

當 CI Service 掛掉時該怎麼辦?

如果你比較早開始加入 gitlab.com,不知道你有沒有一種感覺,就是有一陣子在台灣普遍的下班時間 18:00 之後,gitlab.com 上的 GitLab CI 就很容易罷工遲遲都不執行 CI Job,甚至根本我明明幾分鐘前才 git push 了一個更新版的 .gitlab-ci.yml 到 gitlab.com,但 CI Pipeline 卻遲遲都不更新。那段日子我都戲稱,只要員工下班了,就連 CI Service 也一起下班了。

如果只是下班時間 CI service 掛掉,可能影響還不大,但你難保不會遇到「 CI Service 臨時掛掉,但偏偏這時又急著要部署某個版本的程式給重要人士觀看。」的狀況。對此,我們不禁要問「如果 CI Service 臨時掛掉時到底該怎麼辦?」。

答案:「那就手動啊,結案。」(揍飛)

其實這個答案一點問題都沒有,因為這本來就是現實狀況,當「自動化」故障時,當然你就只能手動處理了,只不過有沒有辦法可以讓「手動」這件事變得輕鬆一些呢?

這就和 CI/CD Pipeline,以及 Pipeline 裡面每一個 CI Job 是怎麼設計與規劃有關了,我們先看一個 CI Job 的範例。

build:
  stage: build
  tags:
    - "build"
  script:
    - cd $deploy_dest
    - touch $git_commit_sha
    - composer install
    - npm install
    - npm run production
    - tar -zc $deploy_dest -f /artifacts/"$project_name"-"$git_commit_sha".tar.gz

上面這個 Job 有六行 script,如果要改成人工手動執行,最慢的方式就是工程師看著這六行 script,然後在備用主機上一行又一行的輸入,在輸入的同時他還必須想辦法弄清楚那些變數 $deploy_dest$git_commit_sha$deploy_dest 應該要填哪些值才對。

聰明一點的工程師,可能會先把變數釐清,接著臨時建立一個 shell script,把六行 script 貼進去,最後以一行 Command bash not-auto-build.sh 來完成它。但即便如此,還是免不了工程師需要人工進行腳本轉換的動作。

但假如是下面這種 CI Job 呢?

build:
  stage: build
  tags:
    - "build"
  script:
    - ansible-playbook build-artifacts.yml -e env=$branch_name

同樣功能的 CI Job,但這次應該執行的 script 已經被事先撰寫成 Ansible 的 Playbook,變數的取得也同樣寫在 Playbook 內,只要透過 -e env= 輸入正確的 branch name 即可。如此一來工程師只要有辦法取得 build-artifacts.yml,就可以直接在備用主機上執行它來完成原本 CI Service 代勞的工作。倘若我們將同樣的概念套用在所有的 CI Job 上,是否就能讓「手動」這件事變得輕鬆一些呢?

此外這種做法還有其他的好處,假如今天公司忽然決定不繼續採用 GitLab CI,要改換其他 CI Service 時,CI Job 需要執行的 script 並不需要改寫,因為 script 本來就是可以獨立執行的,因此要抽換到其他 CI Service 也不會太難。

以及,這種做法更容易做到 script 的重複利用與管理,例如以 deploy 為例。

deploy:
  stage: deploy
  tags:
    - "ansible"
  script:
    - ansible-playbook deploy.yml -e env=$branch_name

只要正確的替換 $branch_name,同一個 script 即可用來部署程式到不同的環境。因此不管是 CI Service 代勞或工程師自己手動執行,都能用同一個 script 搞定部署工作。並且這種方式也能善用 CI 來為 script 除錯,如果 script 的動作是一致的,只是變數不同,那麼假如 script 有問題,那 CI 在 dev 或 stg 環境執行時就會先出錯,而將問題揭露,而不會等到某天急著要執行 prod-deploy 時才浮現水面。

小結

透過今天的說明,你有覺得將 CI/CD Pipeline 改成今天介紹的方式是比較好的嗎?這種做法會不會有哪些缺點呢?歡迎一起來思考與討論!

今天的內容其實跟 GitLab 沒太多直接關聯,反而是提供大家對於「工具」的一個反思,GitLab CI 提供了一條龍的服務,固然有方便之處,但也有被它綁住而受限於它的危機。

另外,今天的內容也試圖在點出 CI/CD Pipeline 的另一項關鍵——「相依性管理」。這所指的不僅是程式碼本身的相依性而已,也包含對於環境、CI Service、變數、Artifacts、Script⋯⋯等。

最後,再次邀請大家不妨重新調整一下思維,思考本文一開始的三個提問,努力做好 CI/CD 的各種「相依性管理」,嘗試達成「讓任何人可以任意的取得任何版本的程式」這項目標吧!