在專案持續成長的過程中,.gitlab-ci.yml
的維護往往會逐漸變得複雜。若缺乏一套模組化的設計與引用規範,團隊很容易陷入 重複定義、錯誤難以追蹤,甚至 耦合過深 的困境。
本文將說明如何透過 模板拆分、include 引用與 anchor/extend,讓 GitLab CI 的維護更加清晰有序。內容分為三個部分:
在實作之前,先釐清幾個重要的規則:
Job 不要寫太複雜的 Shell 邏輯
複雜腳本建議放到 scripts/
目錄,CI 僅需簡單呼叫。
避免在 include 的 YAML 中再次 include
巢狀引用會使維護困難,引用鏈條過長也增加理解成本。
定義全域型模板
建議維護幾個全域檔案,例如:
global.gitlab-ci.yml
:全域變數與共用設定rules.gitlab-ci.yml
:共用的 rules 定義versions.gitlab-ci.yml
:版本號或環境參數參數控制下放到專案
模板提供結構,具體參數交由專案本身的 .gitlab-ci.yml
控制。
include 的 YAML 需盡量完整
避免過度依賴巢狀引用,保持單一檔案即可清楚讀懂。
複雜 Shell 放到 scripts/
不要將長篇邏輯包進 template,以免影響可讀性與可維護性。
其中,「Job 不要寫太複雜的 Shell 邏輯」 與 「避免在 include 的 YAML 中再次 include」 是最常被忽略的問題,以下提供範例說明。
❌ 錯誤做法(邏輯塞在 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"
❌ 錯誤做法(層層 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 (&
, <<:
)
!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
.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
# 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
# .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 造成的維護困境。
簡而言之:
這樣設計出來的 CI/CD pipeline 結構將更具 可讀性、可維護性與可擴充性。
今天我們聚焦在 GitLab CI 的模組化設計與引用規範,從日常最容易踩到的坑談起:
scripts/
,讓 CI 檔案更精簡。global.gitlab-ci.yml
,減少重複。透過對比 錯誤範例與正確範例,我們理解到:
我們也整理了 引用方式的層級差異:
&
, <<:
):同檔內部共用。!reference
:片段重用,保持靈活。在這裡,我想補充一些自己的思考:
雖然封裝、重用、模組化在程式設計中備受推崇,但在 DevOps 的世界裡,我們更應該重視 使用者介面的服務設計。好的 CI/CD pipeline 不只是自動化工具,它應該 簡化、易於理解,避免使用者陷入像迷宮般的複雜設定。
這也是為什麼本系列以 抽象與模板為起點:透過清晰的抽象,使服務更易使用、減少心智負擔,並提升操作效率與維護便利性——這正是我撰寫這系列文章的初衷。
最後:
隨著 Day 13 的結束,我們完整地走過了 GitLab CI/CD 從架構到模組化設計的實務旅程。接下來,我們將回到資料平台的核心:事件流。透過解析 Kafka 的架構與效能祕訣,探討 Log 與資料庫的關係,並說明為什麼資料流成為現代資料平台的關鍵。期待與各位在下一篇文章中,一起揭開這些底層運作的奧秘。
感謝各位的閱讀,我們明天見!