初探 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
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的開發環境就能輕鬆地建立出來
build
image: node:latest
script:
- npm install
- npm build
因為是練習的緣故所以直接使用網路上現有的docker image,如果是公司專案通常會使用公司內部的docker image,因為使用別人提供的免費image的會有使用次數限制,超過上限的話job就不能跑了
考量到之後的每個job都有可能會用到這個image,於是新增default job,讓每個job都會繼承這邊的設定
stages:
- build
default:
image: node:latest
build:
stage: build
script:
- npm install
- npm run build
當專案build成功之後,我們想要將產出物存放到gitlab的空間,以便之後給其他stage使用,就可以設定artifact,將指定路徑的資料夾或檔案存起來
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
build:
stage: build
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
考量到每個job都有cache的需求,把cache的設定提升到全域
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的工具
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的時候才執行
build:
stage: build
only:
- main
- merge_requests
allow_failure
容許讓特定的job fail,即便該job失敗,也不會讓pipeline失敗
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,減少重複作業
.common:
script:
- echo "common job"
test:
extends: .common
script:
- echo "test job"
.job
假設在job前面多加一個.,則該job並不會執行,看到這裡大家應該會冒出一個疑問,為什麼要設定一個不會執行的job ? 通常.job的用途是提供給其他job extend使用,所以這個job不執行很合理
.common:
script:
- echo "common job"
variables
宣告變數,讓script可以使用
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從入門到實戰,先說這不是業配,之前在天攏書局看到這本書,覺得蠻不錯的,推薦給大家