前言

GitLab CI/CD 的 job 如何獲取程式碼庫時提供三種策略,分別為:

  1. clone
  2. fetch
  3. none

在比較這三個策略的差異之前,我們需要先了解 Executor 主要的初始作業結束作業是怎麼運作的。

Executor 初始作業

我們先以一個基礎的 GitLab CI/CD 範例來看,該範例將會運行一個 build job:

stages:
  - build

build:
  stage: build
  script:
    - echo "building...."

job:build 輸出畫面


如紅框標示,可以發現每個 job 在開始執行 .gitlab-ci.yml 所定義的工作前會經過一些的初始準備。

步驟說明:

  1. 準備 Executor
    • 若是採用 Docker Executor,則會在此步驟建立 Docker 容器
  2. 環境設定
    • 此步驟會在 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 的名稱)
  3. 從 Git 儲存庫獲取程式碼(預設):
    1. 根據 git depth 抓取變動(預設為 shallow clone,僅會抓取指定深度的變動)
    2. 切換到工作分支
    3. 設定 Git 的子模組
  4. 執行 .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:testjob:deploy 僅需針對編譯後的執行檔或封裝的映像檔進行測試或部署。

後記

對於小型專案而言,三種策略在 CI/CD 的執行時間影響或許並不是那麼顯著。然而,對於大型專案而言,選擇合適的 Git 策略對於 CI/CD 的效率可能會很有幫助!更多 GitLab CI/CD 關於 Git 的設定可參考 GitLab 官方網站。不同的設定搭配不同的 Git 策略,有時行為也會有所不同,比方說當 GIT_CHECKOUTfalse 時,clonefetch 這兩種策略也會有所不同。不妨動手試試看,就能對 GitLab CI/CD 有更深的了解和獲得更多的操控力唷!


1. 選擇預設的 Git 策略
2. GIT_STRATEGY