初探 Gitlab CI/CD

最近因為工作需求而開始接觸 gitlab cicd,要在既有的架構下疊加新的 job 上去,在毫無基礎的情況下,打開 gitlab ci yml 簡直就像在看無字天書一樣 😂,在跌跌撞撞、各種試錯之後,便決定寫一篇筆記整理 cicd 的知識點

cicd 是什麼?

持續整合和持續部署

持續整合

在多人協作的情況下,ci 的任務將是不斷地整合大家的程式碼,並且確保沒有人會對專案產生破壞性的影響,舉例來說,當每次有人發 pull request 要請 reviewer 做 code review 的時候,會先確認 project 是否可以 build 成功 ,如果 build fail 的話就沒有 review 的必要了,先請原作者做修正,當然這些作業都可以手動沒問題,但就會變得非常繁瑣,讓 ci 幫我們完成這些例行公事是再方便不過了

持續部署

把持續整合 build 出來的成品自動部署到 server 上,可以根據不同的環境自動部署(dev/staging/production),遙想很久以前我都是用土法煉鋼的方式,先手動 build ,然後打開 ftp 丟檔案,再下 ssh 指令部署,一恍神就可能出錯,自動化部署絕對會比純手動來得可靠多了

cicd 的三個關鍵要素:pipeline、stage、job

gitlab 的 pipeline 是由多個 stage 組成,然後每個 stage 裡面又可以設定多個 job,聽起來好像有點抽象,用個生活化的方式舉例,pipeline 可以理解為工廠的產線,每個 stage 就是部門,ex.生產部、品管部,然後 job 就好比是每個部門的員工,有各自的任務,生產部(build stage)產出成品再由品管部(testing stage)來檢驗品質,透過跨部門合作來完成產出

動手實作

新增  .gitlab-ci.yml 到專案根目錄,首先要先熟悉 YAML 如何撰寫,YAML 的縮排是採用 space*2,對於習慣按 tab 縮排的開發者來說,這點需要習慣一下

一開始要先設定 stage,定義每個 stage 要做的 job,先來定義一個簡單的 build job

1
2
3
4
5
6
7
8
9
stages:
- build

build:
stage: build
script:
- echo "hello"
- npm install
- npm run build
  • script: 要執行的指令
  • echo: 就像 javaScript 的 console log 可以印出想要關注的值

這時候如果直接把這個 yml push 到 gitlab 上面,會自動觸發 pipeline 的執行,但結果是 fail 的,因為 gitlab 上面並沒有 node 的環境可以執行 npm 的指令,這時候需要 docker image 來幫我們建置環境

docker 是一種虛擬容器的技術,透過 image 可以生成 container 建立執行環境,因此我們可以使用 docker 在虛擬機器上建立需要的開發環境,

假設A專案用的是 node 12、B專案用的是 node 20,使用對應的 node image 的開發環境就能輕鬆地建立出來

1
2
3
4
5
build
image: node:latest
script:
- npm install
- npm build

因為是練習的緣故所以直接使用網路上現有的 docker image,如果是公司專案通常會使用公司內部的 docker image,因為使用別人提供的免費 image 的會有使用次數限制,超過上限的話 job 就不能跑了

考量到之後的每個 job 都有可能會用到這個 image,於是新增 default job,讓每個 job 都會繼承這邊的設定

1
2
3
4
5
6
7
8
9
10
11
stages:
- build

default:
image: node:latest

build:
stage: build
script:
- npm install
- npm run build

當專案 build 成功之後,我們想要將產出物存放到 gitlab 的空間,以便之後給其他 stage 使用,就可以設定 artifact,將指定路徑的資料夾或檔案存起來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
stages:
- build

default:
image: node:latest

build:
stage: build
script:
- npm install
- npm run build
artifacts:
paths:
- ./dist

到這裡先告一段落,將這個 yml push 到 gitlab 上面,當 gitlab 偵測到  .gitlab-ci.yml 的存在時,就會自動 run pipeline 如下圖

點那個像是上弦月的藍色 icon 就可以看到 pipeline 的細節,在這裡面可以看到每個階段的執行 job

點進去每個 job 可以看到指令執行的細節,在右邊的側邊欄裡的 job artifacts 可以找到 build 成功的產出物,要下載或是預覽都可以

接下來讓我們優化這個 job,大家都知道 npm install 其實很花時間,要下載所有的依賴項目,如果有 cache 機制的話可以保留先前下載過的 depandency,這樣其他的 job 就可以重複使用了,能夠節省大量的時間,設定 cache 的時候會以 CI_COMMIT_REF_SLUG 來做為 cache key,讓不同的分支擁有各自的 cache

1
2
3
4
5
6
build:
stage: build
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/

考量到每個 job 都有 cache 的需求,把 cache 的設定提升到全域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
stages:
- build
- test

default:
image: node:latest

cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/

build:
stage: build

script:
- echo $CI_COMMIT_SHORT_SHA
- npm install
- npm run build
artifacts:
paths:
- ./dist

初次執行的時候,會出現抓不到 cache 的提示字樣,這時候會下載需要的依賴項目並且存到 cache 裏

執行第二次的時候,就可以直接拿 cache 來用了!

如果想清掉 cache 的話可以按這裡,clear runner caches

接下來新增 unit test 的 job,在執行 script 之前先使用 apt-get 安裝 jq 這個工具(這個 jq 不是 jQuery,而是一種解析 json 的工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
testing:
stage: test
before_script:
- apt-get update -y && apt-get install -y jq
script:
- npm install
- npm run report
- data=$(cat test-report.json)
- success=$(echo "$data" | jq -r '.success')
- if [ "$success" = "true" ]; then
echo "test success";
else
echo "test failed";
exit 1;
fi
  • 執行 test 指令
  • 生成 test report.json
  • 讀取 report 裡面 success 的 key
  • 如果是 true 代表測試通過,false 代表測試失敗就執行 exit 1 讓 job fail

如何重新執行 pipeline?

當 pipeline 失敗的時候,想要重跑該怎麼辦?到 pipeline 列表,找到想要重跑的 pipeline,按下右側的 🔁 按鈕來重跑 ci

pipeline 有時會因為不明原因 fail,重跑就好了,不過如果是因為 code 寫爛了導致 fail,那重跑幾次都還是會 fail,因此 pipeline fail 的時候要先去找是哪個 job fail 了,分析失敗的原因,再做後續的處理

或是點右上角的 run pipeline,指定 branch 來重跑,這裡面也可以傳入變數,可根據自身需求來調整

接下來介紹幾個 gitlab-ci.yml 常用的語法

限定該 job 只在特定 branch 才執行,或是僅在 merge_requests 的時候才執行

1
2
3
4
5
build:
stage: build
only:
- main
- merge_requests

allow_failure

容許讓特定的 job fail,即便該 job 失敗,也不會讓 pipeline 失敗

1
2
3
4
5
6
7
8
9
build:
stage: build
script:
- npm install
- npm run build
artifacts:
paths:
- ./dist
allow_failure: true

舉例來說,下圖的這兩個 pipeline 的第二個 job 的執行結果都是 failed,但一個狀態是 warning 另一個則是 failed,差別在於上面那個 status warning 的是將 job allow_failure 設定為 true,允許特定 job 失敗

舉例來說,下圖的這兩個 pipeline 的第二個 job 的執行結果都是 failed,但一個狀態是 warning 另一個則是 failed,差別在於上面那個 status warning 的是將 job allow_failure 設定為 true,允許特定 job 失敗

extend

可以繼承特定的 job,減少重複作業

1
2
3
4
5
6
7
8
.common:
script:
- echo "common job"

test:
extends: .common
script:
- echo "test job"

.job

假設在 job 前面多加一個.,則該 job 並不會執行,看到這裡大家應該會冒出一個疑問,為什麼要設定一個不會執行的 job ? 通常.job 的用途是提供給其他 job extend 使用,所以這個 job 不執行很合理

1
2
3
.common:
script:
- echo "common job"

variables

宣告變數,讓 script 可以使用

1
2
3
4
5
test:
script:
- echo "version is ${VERSION}"
variables:
VERSION: "1.1.0"

關於 gitlab 的介紹到這邊告一段落,其實上面介紹的內容不過是冰山一角罷了,還有很多內容沒有提到,個人認為學習 gitlab 的最佳的方式就是先確認使用情境,釐清想要透過 gitlab cicd 解決什麼樣的問題,舉例來說常見的使用情境有以下幾種

  • 跑完 test 之後要 deploy 到特定的機器,需要執行特定腳本
  • 依據 commit 的 msg 的 prefix,判斷這是 breaking change 或只是 bug fix,自動加上(大、中、小)版號
  • 生成 change log,讓其他人知道這次的版本改動了哪些東西
  • 上線發現有重大 bug,需要執行 rollback 回到上一個版本

確認好使用情境之後就可以開始動手做了,但不得不說 cicd 真的很讓人挫折,不像是一般的程式那麼好 debug,真的需要強大的通靈能力 😂

在撰寫 gitlab-ci.yml 的時候,要歷經多次失敗,才能夠讓 pipeline 能夠 success

我個人推薦這本書 GitLab CI/CD 從入門到實戰,先說這不是業配,之前在天攏書局看到這本書,覺得蠻不錯的,推薦給大家