iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
自我挑戰組

雲端與資料平台實戰:從抽象概念到落地技術系列 第 13

Day13 GitLab CI 模組化與引用設計:最佳實踐與範例

  • 分享至 

  • xImage
  •  

在專案持續成長的過程中,.gitlab-ci.yml 的維護往往會逐漸變得複雜。若缺乏一套模組化的設計與引用規範,團隊很容易陷入 重複定義錯誤難以追蹤,甚至 耦合過深 的困境。

本文將說明如何透過 模板拆分、include 引用與 anchor/extend,讓 GitLab CI 的維護更加清晰有序。內容分為三個部分:

  1. 模組化設計的基本原則
  2. 原則對應的正確與錯誤範例
  3. 引用方式的層級差異

一、基本原則

在實作之前,先釐清幾個重要的規則:

  1. Job 不要寫太複雜的 Shell 邏輯
    複雜腳本建議放到 scripts/ 目錄,CI 僅需簡單呼叫。

  2. 避免在 include 的 YAML 中再次 include
    巢狀引用會使維護困難,引用鏈條過長也增加理解成本。

  3. 定義全域型模板
    建議維護幾個全域檔案,例如:

    • global.gitlab-ci.yml:全域變數與共用設定
    • rules.gitlab-ci.yml:共用的 rules 定義
    • versions.gitlab-ci.yml:版本號或環境參數
  4. 參數控制下放到專案
    模板提供結構,具體參數交由專案本身的 .gitlab-ci.yml 控制。

  5. include 的 YAML 需盡量完整
    避免過度依賴巢狀引用,保持單一檔案即可清楚讀懂。

  6. 複雜 Shell 放到 scripts/
    不要將長篇邏輯包進 template,以免影響可讀性與可維護性。

其中,「Job 不要寫太複雜的 Shell 邏輯」「避免在 include 的 YAML 中再次 include」 是最常被忽略的問題,以下提供範例說明。


二、原則範例說明

1. Job 不要寫太複雜的 Shell 邏輯

錯誤做法(邏輯塞在 CI 裡,難以維護):

當邏輯變得龐大複雜時,不僅會讓 YAML 的可讀性降低,也會讓 Runner 的執行環境更難追蹤與除錯。

build-job:
  stage: build
  script:
    - echo "Start build"
    - mkdir -p build
    - for f in $(ls src); do gcc src/$f -o build/${f%.c}; done
    - tar -czf build.tar.gz build/
    - echo "Build done"

錯誤做法(將龐大邏輯直接打包成模板):

當 template 的數量與內容持續擴大時,如果彼此之間還互相 include,會讓引用關係變成「迷宮」,最後幾乎無法追蹤實際 pipeline 的組成來源。

.file-build:
  script:
    - for f in $(ls src); do gcc src/$f -o build/${f%.c}; done
    - tar -czf build.tar.gz build/

正確做法(抽出腳本,簡化 CI 定義):

build-job:
  stage: build
  script:
    - ./scripts/build.sh
# scripts/build.sh
#!/bin/bash
set -e
echo "Start build"
mkdir -p build
for f in $(ls src); do
  gcc src/$f -o build/${f%.c}
done
tar -czf build.tar.gz build/
echo "Build done"

2. 避免在 include 的 YAML 中再次 include

錯誤做法(層層 include,難以追蹤):

# ci-templates/build.gitlab-ci.yml
include:
  - local: 'ci-templates/compile.gitlab-ci.yml'
# ci-templates/compile.gitlab-ci.yml
compile-job:
  stage: build
  script: echo "Compile"

正確做法(統一在 .gitlab-ci.yml 引入):

# .gitlab-ci.yml
include:
  - local: 'ci-templates/build.gitlab-ci.yml'
  - local: 'ci-templates/compile.gitlab-ci.yml'

👉 規範是:一律由 .gitlab-ci.yml 進行 include,template YAML 僅透過 extends 引用全域模板。


三、引用方式的層級差異

為了更清楚劃分「模板來源」,我們在不同層級採取不同的引用方式:

  • .gitlab-ci.yml → anchor/alias (&, <<:)
  • Template 檔內 → !reference
  • 全域模板 → .gitlab-ci.yml include + Template 檔 extends

以下提供完整範例。


專案目錄結構

project-root/
├── .gitlab-ci.yml
├── ci-templates/
│   ├── global.gitlab-ci.yml    # 全域共用模板
│   ├── rules.gitlab-ci.yml     # 共用 rules
│   ├── versions.gitlab-ci.yml  # 版本與參數定義
│   └── deploy.gitlab-ci.yml    # 部署相關 job
└── scripts/
    ├── build.sh
    └── deploy.sh

1. 在 .gitlab-ci.yml 中(單檔內部引用)

.default-job: &default-job
  image: alpine:3.18
  before_script:
    - echo "Preparing common environment..."

build:
  <<: *default-job
  stage: build
  script:
    - ./scripts/build.sh

2. 在 Template YAML 中(本地模板)

# deploy.gitlab-ci.yml
.deploy-base:
  stage: deploy
  script:
    - ./scripts/deploy.sh

deploy-job:
  extends: 
    - .global-base       # 來自 global.gitlab-ci.yml
    - .deploy-base       # 本檔案內定義
  stage: deploy

3. 全域型模板(跨檔共用)

# .gitlab-ci.yml
include:
  - local: 'ci-templates/global.gitlab-ci.yml'
  - local: 'ci-templates/deploy.gitlab-ci.yml'

stages:
  - build
  - deploy
# global.gitlab-ci.yml
.global-base:
  image: alpine:3.18
  before_script:
    - echo "Setup global environment"

透過以上規範,我們能清楚劃分 模板來源引用方式,並避免因重複邏輯或過度巢狀 include 造成的維護困境。

簡而言之:

  • 複雜邏輯 → 放到 scripts/
  • 全域共用 → 抽到 global template
  • 引用規範 → 分層清楚(anchor / reference / extends)

這樣設計出來的 CI/CD pipeline 結構將更具 可讀性、可維護性與可擴充性


小結

今天我們聚焦在 GitLab CI 的模組化設計與引用規範,從日常最容易踩到的坑談起:

  • Job 不要寫太複雜的 Shell 邏輯 → 抽出到 scripts/,讓 CI 檔案更精簡。
  • 避免在 include 的 YAML 中再次 include → 保持引用單一來源,讓依賴關係不會變成迷宮。
  • 定義全域型模板 → 將共用設定抽離成 global.gitlab-ci.yml,減少重複。
  • 參數控制下放到專案 → 模板只給結構,環境差異交給專案決定。
  • include 的 YAML 需盡量完整 → 單檔即可讀懂,不需要再拼湊。
  • 複雜 Shell 放到 scripts/ → 模板只放骨架,細節交給腳本。

透過對比 錯誤範例與正確範例,我們理解到:

  • 把邏輯塞進 job 或打包進模板,會讓 YAML 失去可讀性;
  • 巢狀 include 雖然方便,但隨著專案擴張,維護成本會急速上升。

我們也整理了 引用方式的層級差異

  • .gitlab-ci.yml → anchor/alias (&, <<:):同檔內部共用。
  • Template YAML → !reference:片段重用,保持靈活。
  • 全域模板 → include + extends:跨檔抽象,支撐大專案。

在這裡,我想補充一些自己的思考:

雖然封裝、重用、模組化在程式設計中備受推崇,但在 DevOps 的世界裡,我們更應該重視 使用者介面的服務設計。好的 CI/CD pipeline 不只是自動化工具,它應該 簡化、易於理解,避免使用者陷入像迷宮般的複雜設定。
這也是為什麼本系列以 抽象與模板為起點:透過清晰的抽象,使服務更易使用、減少心智負擔,並提升操作效率與維護便利性——這正是我撰寫這系列文章的初衷。


最後:

隨著 Day 13 的結束,我們完整地走過了 GitLab CI/CD 從架構到模組化設計的實務旅程。接下來,我們將回到資料平台的核心:事件流。透過解析 Kafka 的架構與效能祕訣,探討 Log 與資料庫的關係,並說明為什麼資料流成為現代資料平台的關鍵。期待與各位在下一篇文章中,一起揭開這些底層運作的奧秘。

感謝各位的閱讀,我們明天見!


上一篇
Day12 GitLab CI:參數解耦與多環境管理實務分享
下一篇
Day14 Log 與資料庫的關係:從批處理到事件流
系列文
雲端與資料平台實戰:從抽象概念到落地技術18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言