Stages 設計(lint / test / build / deploy)
Stage 定義了 Pipeline 的執行順序:前一個 stage 的所有 job 全部成功後,才進入下一個 stage。良好的 stage 設計能讓問題在最早的階段被攔截,避免浪費後續的運算資源。每個 stage 代表一個職責明確的質量關卡,從程式碼風格到最終部署,層層把守確保只有通過所有檢查的版本才能進入生產環境。標準四層結構如下:
# 推薦的四層 Stage 設計(GitLab CI 範例)
stages:
- lint # 最快(秒級):語法檢查、格式驗證、型別檢查
- test # 中速(分鐘):單元測試、整合測試、安全掃描
- build # 較慢(分鐘):編譯、打包、建置 Docker 映像
- deploy # 需控制(分鐘):部署至測試/正式環境
# 全域預設設定(所有 jobs 繼承,減少重複設定)
default:
image: node:20-alpine
before_script:
- npm ci --cache .npm --prefer-offline
cache:
key:
files:
- package-lock.json # lock 檔案變動時自動失效,強制重新安裝
paths:
- .npm/
timeout: 10 minutes # 全域 job 超時時間,防止 job 卡住耗盡資源
# lint stage:程式碼品質把關(同 stage 內的 job 平行執行)
lint-eslint:
stage: lint
script:
- npm run lint
allow_failure: false # lint 失敗則阻止後續所有 stage 執行
typecheck:
stage: lint # 與 lint-eslint 同時平行執行
script:
- npm run typecheck
# test stage:測試覆蓋率與報告
unit-test:
stage: test
script:
- npm run test -- --coverage
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/' # 從 log 解析覆蓋率,顯示於 MR 頁面
artifacts:
reports:
junit: test-results.xml # GitLab 原生支援,在 MR 頁面顯示測試摘要
paths:
- coverage/
expire_in: 3 days
# build stage:產出可部署的產物
build-app:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/ # 傳遞給 deploy stage 使用
expire_in: 1 hour # 短效期節省儲存空間Trigger 條件設定(push / PR / tag / schedule)
不同事件應觸發不同範圍的 Pipeline。過度觸發會浪費資源,觸發不足則可能讓問題流入主線。核心原則是:功能分支執行輕量快速的檢查,主線分支執行完整的 CI,發佈版本觸發完整的建置與部署,排程任務處理定期維護工作。以下示範四種常見觸發場景的設定:
# GitLab CI — 使用 rules 精確控制觸發條件
# (GitLab 14.0+ 推薦 rules 取代舊有的 only/except)
# 1. push 到功能分支時,只執行 lint 與 test(節省建置資源)
feature-check:
stage: test
script:
- npm test
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "main"
when: on_success
# 2. Merge Request 時執行完整 CI(lint + test + build),確保合併品質
mr-pipeline:
stage: build
script:
- npm run build
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 3. 推送符合語意化版本格式的 tag 時,觸發正式發佈流程
release-build:
stage: build
script:
- npm run build:production
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/ # 符合 semver(如 v1.2.3)
# 4. 排程觸發:每日定時執行安全掃描(不影響正常開發流程)
security-scan:
stage: test
script:
- npm audit --audit-level=high
- trivy image $CI_REGISTRY_IMAGE:latest
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"$CI_PIPELINE_SOURCE 變數可辨識觸發來源:push(一般推送)、merge_request_event(MR)、web(手動觸發)、schedule(排程)、trigger(跨專案觸發)。善用此變數搭配 rules 可精確控制每個 job 的執行時機,避免不必要的資源消耗。平行執行 vs 循序執行
同一個 stage 內的所有 job 預設平行執行,不同 stage 間則循序執行。可透過 needs: 關鍵字建立 job 間的直接依賴關係,打破 stage 邊界,實現更靈活的 DAG(有向無環圖)Pipeline。DAG 模式讓準備好的 job 可以立即執行,不必等待整個 stage 完成,顯著縮短 Pipeline 總執行時間:
# 預設行為:同 stage 內的 job 全部平行執行
test:
stage: test
script: npm test # 與 lint job 同時執行,互不等待
lint:
stage: test
script: npm run lint # 與 test job 同時執行
# 使用 needs: 實現 DAG Pipeline(不受 stage 邊界限制)
# build-app 完成後,deploy-staging 可立即執行,無需等待 security-scan
deploy-staging:
stage: deploy
needs:
- job: build-app # 只等待 build-app 完成,不等待整個 build stage
artifacts: true # 繼承 build-app 上傳的 artifacts(dist/ 目錄)
script:
- ./deploy.sh staging
security-scan:
stage: deploy # 同 stage,但與 deploy-staging 獨立平行執行
needs:
- job: build-app
artifacts: false # 只需等待 build-app 完成,不需要其 artifacts
script:
- trivy image $CI_REGISTRY_IMAGE:latest
# 矩陣平行:同時在多個環境組合中執行相同的測試
test-matrix:
stage: test
parallel:
matrix:
- NODE_VERSION: ["18", "20", "22"]
OS: ["alpine", "bullseye"] # 共 6 個 job 同時執行
image: node:${NODE_VERSION}-${OS}
script:
- npm testArtifact 與 Cache 策略差異
Artifact 與 Cache 是兩種不同用途的檔案儲存機制,在 CI/CD 初學者中常被混用,但選擇錯誤會導致 Pipeline 緩慢甚至不穩定。理解兩者的本質差異是設計高效 Pipeline 的關鍵:
# ── Artifact:stage 間傳遞建置產物 ──────────────────────────────────────
# 特性:上傳至 GitLab/GitHub 中央伺服器,可在 UI 下載,跨 job 自動傳遞
# 適合:編譯產物(dist/)、測試報告(coverage/)、Docker 映像 tarball
build:
stage: build
script:
- npm run build
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" # 人類可讀的命名
paths:
- dist/ # 建置產物,deploy stage 自動下載並使用
- coverage/ # 測試覆蓋率報告,可在 GitLab UI 瀏覽
reports:
junit: test-results.xml # 特殊類型:在 GitLab MR 頁面顯示測試摘要
expire_in: 7 days # 7 天後自動清除(節省儲存空間與費用)
when: always # 即使 job 失敗也保存 artifact(便於 debug)
# ── Cache:跨 Pipeline 重用相依套件 ────────────────────────────────────
# 特性:儲存在 Runner 本機磁碟,不保證每次都命中,不應存放建置產物
# 適合:node_modules、pip packages、Maven .m2 等安裝耗時的相依套件
default:
cache:
key:
files:
- package-lock.json # 依 lock 檔案內容雜湊值決定 key
# lock 檔一旦變動,cache 自動失效重建
paths:
- node_modules/ # 快取 node_modules
policy: pull-push # pull-push(預設):先讀取舊 cache,完成後更新
# pull:唯讀模式,不更新 cache(加速唯讀 job)條件執行(when / rules)
when 關鍵字控制 job 在什麼情況下執行,共有五種值,各有適用場景。搭配 rules 可組合出精細的執行邏輯,讓 Pipeline 根據分支名稱、觸發來源或自訂變數動態決定每個 job 的行為:
# when 的五種值與對應使用情境
deploy-production:
script: ./deploy.sh production
when: manual # 需要人工在 GitLab UI 點擊才執行(適合正式部署審核)
notify-failure:
script: ./notify-slack.sh "Pipeline 失敗,請立即處理"
when: on_failure # 只在前一個 job 失敗時執行(發送告警通知)
cleanup:
script: ./cleanup.sh
when: always # 無論成功或失敗都執行(適合清理臨時資源)
# on_success(預設值):只在前一個 job 成功時執行
# delayed:延遲指定時間後執行(如 start_in: 30 minutes)
# rules 搭配 when 實現複雜的多條件邏輯
deploy:
stage: deploy
script: ./deploy.sh
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success # main branch push 後自動部署(CD)
- if: $CI_COMMIT_BRANCH =~ /^release\//
when: manual # release 分支需手動點擊才部署
allow_failure: false # 標記為必要步驟,不可略過
- when: never # 其他所有情況完全不執行此 job
# 條件變數賦值:根據分支動態決定建置模式
build:
stage: build
variables:
BUILD_MODE: $([[ "$CI_COMMIT_BRANCH" == "main" ]] && echo "production" || echo "development")
script:
- npm run build -- --mode $BUILD_MODEReusable Workflows 與 Templates
當多個專案或多條 Pipeline 需要共用相同的 CI 邏輯時(如相同的測試設定、Docker 建置流程),應將共用部分抽取為可重用的模板,集中維護,避免在多處重複相同設定。一旦需要更新(例如升級 Node.js 版本),只需修改模板,所有引用的 Pipeline 自動受益:
# GitLab CI — 使用 include 引入共用模板(三種來源)
# 方式 1:引入同 repo 的本地模板檔案(小型專案推薦)
include:
- local: '.gitlab/ci-templates/node.yml'
# 方式 2:引入遠端 GitLab 專案的模板(跨專案共用,組織級管理)
include:
- project: 'my-org/ci-templates'
ref: main
file: '/templates/docker-build.yml'
# 方式 3:引入 GitLab 官方維護的安全掃描模板
include:
- template: Security/SAST.gitlab-ci.yml
- template: Code-Quality.gitlab-ci.yml
---
# .gitlab/ci-templates/node.yml — 共用基礎模板定義
.node-base: # 以 . 開頭的 job 不會被直接執行(稱為隱藏 job)
image: node:20-alpine
before_script:
- npm ci --cache .npm --prefer-offline
cache:
key:
files: [package-lock.json]
paths: [.npm/]
# 在主要設定檔中用 extends 繼承模板(只需定義差異部分)
test:
extends: .node-base # 繼承 image、before_script、cache 等所有設定
stage: test
script:
- npm test # 只需定義此 job 特有的腳本
build:
extends: .node-base # 同樣繼承模板,共用相同基礎設定
stage: build
script:
- npm run buildworkflow_call 觸發器)與 Composite Actions,可將多個 step 封裝為可呼叫的單元,在不同 workflow 中重複使用。Reusable Workflows 適合封裝完整的工作流程(如完整的 CI 流程),Composite Actions 則適合封裝一組相關的 step(如安裝與設定特定工具)。Pipeline 優化技巧(fail-fast / timeout / skip)
隨著專案規模增長,Pipeline 執行時間可能從幾分鐘延長至數十分鐘,嚴重影響開發效率。優化 Pipeline 應從「減少不必要的執行」出發,而非一味追求執行速度。以下是五種最有效的優化策略:
# 技巧 1:fail-fast — 矩陣測試中,一個失敗立即取消其他(GitHub Actions)
jobs:
test:
strategy:
fail-fast: true # 預設 true;設為 false 可讓所有矩陣跑完後再彙整報告
matrix:
node-version: [18, 20, 22]
# 技巧 2:timeout — 防止 job 卡住長時間消耗 Runner 資源
test:
stage: test
timeout: 5 minutes # job 超時自動終止(預設全域超時通常是 1 小時)
script:
- npm test
# 技巧 3:interruptible — 新 Pipeline 觸發時自動取消舊 Pipeline
build:
stage: build
interruptible: true # 同一分支有新 push 時,自動取消正在執行的舊 build
script:
- npm run build
# 技巧 4:resource_group — 限制同一資源同時只有一個 job 執行
deploy-production:
stage: deploy
resource_group: production # 確保不會有兩個部署 job 同時執行(避免競爭條件)
script:
- ./deploy.sh production
# 技巧 5:rules changes — 只在相關檔案有變更時才執行對應 job
test-backend:
stage: test
script:
- npm run test:backend
rules:
- changes:
- src/api/**/* # 只有 api 目錄有變更時才執行後端測試
- package-lock.json # 相依套件變動也需要重新測試
# 技巧 6:skip CI — 在 commit message 加入關鍵字跳過 Pipeline
# 在 commit message 末尾加上 [skip ci] 或 [ci skip] 即可略過整個 Pipeline
# 適用於純文件修改(如 README 更新)不需要執行 CI 的情況rules: changes 只在相關檔案變更時觸發對應 job,搭配 interruptible: true 自動取消過時的 Pipeline,可大幅降低 CI 資源消耗,同時縮短開發者等待反饋的時間,讓 CI/CD 真正服務於開發流程而非成為阻礙。