前言
GitLab CI/CD 的 job 如何獲取程式碼庫時提供三種策略,分別為:
- clone
- fetch
- none
在比較這三個策略的差異之前,我們需要先了解 Executor 主要的初始作業與結束作業是怎麼運作的。
Executor 初始作業
我們先以一個基礎的 GitLab CI/CD 範例來看,該範例將會運行一個 build job:
stages:
- build
build:
stage: build
script:
- echo "building...."
job:build 輸出畫面
如紅框標示,可以發現每個 job 在開始執行 .gitlab-ci.yml
所定義的工作前會經過一些的初始準備。
步驟說明:
- 準備 Executor
- 若是採用 Docker Executor,則會在此步驟建立 Docker 容器
- 環境設定
- 此步驟會在 Executor 進行相關的環境設定,例如建立工作目錄等。而每個 job 會在對應的工作目錄下進行工作。
- Shell Executor:工作目錄保存於主機,路徑為
/home/gitlab-runner/builds/<runner-token>/<concurrent 號碼牌>/<GitLab 專案名稱>
。若 Runner 允許並行處裡(concurrent),當同一專案同時間有數個 CI job 在執行時,由同一 Runner 指派的 Executor 會以號碼牌(0, 1, 2, …)區隔工作目錄 - Docker Executor:工作目錄保存於 Docker Volume,掛載路徑為
/home/gitlab-runner/builds/<runner-token>/<GitLab 專案名稱>
(號碼牌則標示於 Docker Volume 的名稱)
- Shell Executor:工作目錄保存於主機,路徑為
- 此步驟會在 Executor 進行相關的環境設定,例如建立工作目錄等。而每個 job 會在對應的工作目錄下進行工作。
- 從 Git 儲存庫獲取程式碼(預設):
- 根據 git depth 抓取變動(預設為 shallow clone,僅會抓取指定深度的變動)
- 切換到工作分支
- 設定 Git 的子模組
- 執行
.gitlab-ci.yml
中定義的腳本
Executor 結束作業
在 Executor 結束工作任務後,該工作目錄並不會隨著工作結束而刪除或是清理。以 Shell Exeuctor 而言,其工作目錄仍會存在。同樣地,Docker Executor 會將工作目錄保存於 Docker Volume,該 Docker Volume 一樣會留存。
如何設定 Git 策略?
要設定策略的方式共有兩種方法:
👉方法一:從專案的 Settings 選單進行設定(專案預設) 1
GitLab 專案預設採用 fetch
策略。若要調整預設策略,使用者可以在此處進行設定:
👉方法二:透過 CI/CD 環境變數 GIT_STRATEGY
進行設定 2
此變數可設定在 .gitlab-ci.yml
的全域或是局域變數,該變數設定後將會覆蓋掉 Settings 選單的預設選項。
若全部的 job 採取同一策略則可設定在全域;
variables:
GIT_STRATEGY: clone # <---
build:
stage: build
script:
- echo "building"
...
若要使特定的 job 使用特定的策略,則可以在 job 區塊進行配置。例如:job:build
維持預設的 Git 策略,而 job:test
改為採用 none
策略:
build:
stage: build
script:
- echo "building"
test:
stage: test
variables:
GIT_STRATEGY: none # <---
script:
- echo "testing"
三種 Git 策略的差異
為明顯表現差異,以下說明將以 Shell Exeuctor 執行以下的 .gitlab-ci.yml
並觀察三種 Git 策略的差異:
stages:
- build
- test
- deploy
build:
stage: build
variables:
GIT_STRATEGY: clone # build 採用 clone 策略
script:
- echo "building...."
- ls -al
- echo "Modify README by build job" > README.txt # 修改 Git 版本控制的檔案
- echo "Add new file by build job" > buildfile.txt # 新增檔案(不由 Git 版本控制)
- sleep 60
test:
stage: test
variables:
GIT_STRATEGY: fetch # test 採用 fetch 策略
script:
- echo "testing...."
- ls -al
- cat README.md # 查看檔案內容為 Git 原始版本,或是前一階段所修改的版本
- echo "Modify README by test job" > README.txt # 修改 Git 版本控制的檔案
- echo "Add new file by test job" > testfile.txt # 新增檔案(不由 Git 版本控制)
- sleep 60
deploy:
stage: deploy
variables:
GIT_STRATEGY: none # deploy 採用 none 策略
script:
- echo "deploying...."
- ls -al
- echo README.txt # 查看檔案內容為 Git 原始版本,或是前一階段所修改的版本
(1) clone 策略
當前 job 的工作目錄會進行初始化,工作目錄將以乾淨的狀態獲取程式碼庫。
job:build 輸出畫面
備註:在運行此範例前,該工作目錄已存在且留有先前操作的 Git 檔案
可以從上面的截圖看到該工作目錄下的檔案為同一時間獲取的資料。
clone
策略是三種策略中最慢的方式,若大型專案採用 clone
策略,每個 job 在初始階段會耗費非常多的時間再獲取程式碼庫,並且容易因為網路不佳的狀況而導致 job 在此 Git 作業階段發生失敗,但此策略的好處是它能確保所獲取的程式碼庫為最原始乾淨的狀態。
(2) fetch 策略
此策略會重複使用工作目錄。工作目錄下會有三種類型的檔案,在 Git 作業階段,其所採取的處理動作分別為:
檔案是否被版本控制 | 檔案是否有變動 | fetch 策略處理方式 |
---|---|---|
是 | 是 | 重新獲取最新版本的內容 |
是 | 否 | 重複使用該工作目錄下的檔案 |
否 | - | 透過 git clean 刪除 |
job:test 輸出畫面
從上面的截圖看到該工作目錄刪除了未被 Git 控管的檔案(buildfile.txt); 由 Git 版本控制卻發生變動的檔案(README.md)重新獲取最新狀態,而同樣由 Git 控管的其他檔案則因未變動所以並沒有任何的更新。
* 若想要保留先前的變動,可以透過 artifacts 或是 cache 進行管理
* 若當前 job 所對應的工作目錄並不存在,則會改由 clone 的方式獲取程式碼庫
由於僅重新獲取變動檔案,因此對於變動好發於少數或小型檔案的大型專案而言,CI/CD 流水線採用 fetch
策略會來得比 clone
更為有效率。
(3) none 策略
此策略亦會重複使用先前在工作目錄所留存的檔案,但與 fetch
策略不同的是,none
策略並不會執行任何 Git 的相關操作,因此無法確保當前工作目錄下的檔案是否為最新版本。
job:deploy 輸出畫面
從上面的截圖看到該 job 略過了所有的 Git 操作,使用的工作目錄仍為前一個 job 執行結束後的狀態。
補充:若是當前 job 所對應的工作目錄不存在,則僅會建立工作目錄,並不會執行任何的 Git 配置:
通常會採用 none
策略發生在該 job 並不需要使用到程式碼庫的資料,其主要操作物件為先前 job 所建立的產物(artifacts)、快取(cache)或是其餘外部物件。
例如:在
job:build
已完成程式編譯或是已封裝成容器映像檔,而job:test
與job:deploy
僅需針對編譯後的執行檔或封裝的映像檔進行測試或部署。
後記
對於小型專案而言,三種策略在 CI/CD 的執行時間影響或許並不是那麼顯著。然而,對於大型專案而言,選擇合適的 Git 策略對於 CI/CD 的效率可能會很有幫助!更多 GitLab CI/CD 關於 Git 的設定可參考 GitLab 官方網站。不同的設定搭配不同的 Git 策略,有時行為也會有所不同,比方說當 GIT_CHECKOUT
為 false
時,clone
與 fetch
這兩種策略也會有所不同。不妨動手試試看,就能對 GitLab CI/CD 有更深的了解和獲得更多的操控力唷!