初探 Gitlab CI/CD

Joe Chang
Coding Hot Pot
Published in
11 min readAug 17, 2024
photo by @theblowup

最近因為工作需求而開始接觸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就不能跑了

too many request

考量到之後的每個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從入門到實戰,先說這不是業配,之前在天攏書局看到這本書,覺得蠻不錯的,推薦給大家

--

--

Joe Chang
Coding Hot Pot

前端工程師,唯有非常努力,才能看起來毫不費力