Matrix Build:多版本與多平台並行測試
Matrix Build 允許用單一 job 定義,同時跑多個版本或平台組合,是確保跨環境相容性最有效率的手段。傳統做法是為每個版本複製貼上 job 定義,不僅維護成本高,也容易造成各版本測試設定不一致的問題。
# GitLab CI 的 parallel:matrix 語法
test-matrix:
stage: test
image: node:${NODE_VERSION}
parallel:
matrix:
- NODE_VERSION: ["18", "20", "22"]
OS: ["ubuntu", "alpine"]
script:
- node --version
- npm ci
- npm test
artifacts:
reports:
junit: test-results-${NODE_VERSION}-${OS}.xml跨平台 Docker 映像建置(同時產出 linux/amd64 和 linux/arm64)也可透過 matrix 搭配 BuildKit 的 --platform 旗標實現,讓 Apple Silicon 用戶和 ARM 伺服器都能使用原生映像,避免模擬層帶來的效能損失:
build-multiarch:
stage: build
parallel:
matrix:
- PLATFORM: ["linux/amd64", "linux/arm64"]
script:
- docker buildx build
--platform $PLATFORM
-t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-${PLATFORM//\//-}
--push .docker pull 自動選擇正確架構),需要在 matrix job 完成後執行 docker buildx imagetools create 將各平台的映像合併。這個步驟通常放在獨立的 merge-manifest job 中,依賴所有 matrix job 完成後才觸發。動態 Pipeline 生成(generate-matrix)
當測試矩陣需要根據運行時條件(例如哪些服務有變更、哪些環境需要部署)動態決定時,靜態 YAML 定義不夠靈活。GitLab 支援透過腳本輸出 JSON 動態產生後續 job 的矩陣,讓 Pipeline 設定自適應於倉庫當前狀態:
# 第一步:生成矩陣定義
generate-matrix:
stage: .pre
script:
- |
# 根據變更的服務動態產生測試矩陣
python3 scripts/detect-changed-services.py > matrix.json
cat matrix.json # 輸出類似 {"SERVICE":["api","worker","scheduler"]}
artifacts:
paths:
- matrix.json
# 第二步:使用動態矩陣執行測試
test-services:
stage: test
parallel:
matrix:
- SERVICE: !reference [generate-matrix, artifacts]
script:
- cd services/$SERVICE
- npm testdetect-changed-services.py 通常透過 git diff --name-only origin/main 分析變更路徑,再對應到服務目錄清單。這個模式讓 CI 設定本身也具備「可擴展性」。Monorepo CI 策略:Path Filtering 與 Affected Detection
在單一倉庫(Monorepo)管理多個服務或套件時,每次 commit 都觸發全量建置會造成嚴重的資源浪費,也讓 Pipeline 回饋時間拉長,降低開發效率。根據 Monorepo 的規模與複雜度,有兩種主流的選擇性觸發策略:
策略一:Path Filtering(GitLab rules:changes)
# 只有 backend/ 下有變更時才觸發 backend 的建置
build-backend:
stage: build
rules:
- changes:
- backend/**/*
- shared/libs/**/* # 共享依賴也要觸發
script:
- cd backend && docker build -t backend:$CI_COMMIT_SHA .
build-frontend:
stage: build
rules:
- changes:
- frontend/**/*
- shared/libs/**/*
script:
- cd frontend && npm run build策略二:Affected Detection(nx / turborepo)
# 使用 Nx 的 affected 命令,自動分析依賴圖
test-affected:
stage: test
script:
- npx nx affected:test --base=origin/main --head=HEAD
- npx nx affected:build --base=origin/main --head=HEAD
# Nx 會自動分析哪些套件被改動,以及哪些套件依賴這些改動- Path filtering 實作簡單,適合服務邊界清晰、套件間依賴關係單純的 Monorepo
- Affected detection 更精準,自動追蹤傳遞依賴(A 依賴 B,B 有變更時 A 也會被觸發),適合套件間關係複雜的 Monorepo
- 兩者可結合使用:先用 path filtering 快速排除明顯無關的服務,再用 affected detection 進行細粒度分析
- 記得在合併到 main 分支時仍執行全量測試,path filtering 只建議用於 MR/PR 階段的快速回饋
- 共享工具函式庫(shared libs)的變更應視為「高風險」,可設定為觸發全量測試以確保安全
SAST / DAST 安全掃描整合
SAST(靜態應用程式安全測試):直接分析原始碼的語法樹與資料流,不需要運行應用程式,可在幾秒內完成掃描,適合整合到每個 commit 的快速回饋迴圈中。
# 使用 GitLab 內建 SAST 模板(需要 GitLab Ultimate)
include:
- template: Security/SAST.gitlab-ci.yml
# 或使用開源的 Semgrep(完全免費,規則集豐富)
sast-semgrep:
stage: test
image: returntocorp/semgrep
script:
- semgrep --config=p/owasp-top-ten --config=p/jwt --json > semgrep-report.json
artifacts:
reports:
sast: semgrep-report.json
allow_failure: true # 初期允許失敗,逐步調整閾值DAST(動態應用程式安全測試):對運行中的應用程式發動掃描,模擬真實攻擊者的行為,能發現 SAST 無法偵測的執行時期漏洞(如 SQL Injection、XSS 等)。需要先啟動應用程式,掃描時間較長,通常在 staging 環境執行。
# 使用 OWASP ZAP 進行 DAST 掃描
dast-zap:
stage: test
image: owasp/zap2docker-stable
services:
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: app
script:
- zap-baseline.py -t http://app:3000 -r zap-report.html
artifacts:
paths:
- zap-report.html
allow_failure: true # 初期設為允許失敗,逐步調緊閾值容器漏洞掃描(Trivy):掃描 Docker 映像中的 OS 套件與應用程式依賴的已知 CVE,是容器化應用不可缺少的防線。
container-scan:
stage: test
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- trivy image
--exit-code 1
--severity HIGH,CRITICAL
--ignore-unfixed
--no-progress
$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
allow_failure: trueallow_failure: true,先讓團隊熟悉報告格式;第二階段設定警告閾值,阻擋 Critical 等級的已知漏洞;第三階段開啟完整的掃描策略並與 Merge Request 審核流程整合,讓高風險 MR 無法在未解決安全問題前合併。效能測試整合(k6 / Locust)
效能回歸往往比功能回歸更難察覺——一個「看起來沒問題」的 code change 可能讓關鍵 API 的 P99 延遲從 200ms 暴增到 2s。將負載測試納入 Pipeline,可在程式碼合併前就量化效能影響,建立客觀的效能品質門檻。
使用 k6 進行輕量負載測試(JavaScript 腳本,內建 threshold 機制):
# k6 效能測試 job
performance-test:
stage: test
image: grafana/k6:latest
services:
- name: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
alias: app
script:
- k6 run --vus 50 --duration 60s
--threshold "http_req_duration{p95}<500"
--threshold "http_req_failed<0.01"
tests/load-test.js
artifacts:
paths:
- k6-summary.json使用 Locust 進行 Python 生態的壓力測試(更貼近真實用戶行為的場景模擬):
locust-test:
stage: test
image: locustio/locust
script:
- locust
--headless
--users 100
--spawn-rate 10
--run-time 2m
--host http://app:8000
-f tests/locustfile.py
--csv=locust-results
artifacts:
paths:
- locust-results_stats.csv成本優化:Self-hosted Runners 與 Spot Instances
CI/CD 的計算成本往往是工程團隊最容易忽略的隱性開銷。隨著 Pipeline 複雜度增加,SaaS CI 的分鐘數費用可能迅速超過預算。以下是業界常見的四種成本控制策略,依實施難度排序:
- Self-hosted Runner:對於計算量大的團隊,自架 Runner 通常比使用 SaaS CI 分鐘數便宜數倍。GitLab Runner 可部署在任何 Linux 機器或 Kubernetes 叢集上,以 Docker executor 執行,維護成本相對可控。
- Spot / Preemptible Instances:AWS Spot、GCP Spot VM 或 Azure Spot 的價格比按需實例便宜 60–90%。CI job 天然適合使用 Spot,因為 job 失敗後可重試,對中斷有天然的容忍性。
- Runner Autoscaling:使用 GitLab Runner 的 autoscaler(搭配 AWS EC2 Fleet 或 GCP MIG)在閒置時縮減至 0 台,高峰時自動擴充,避免為深夜零流量時段的空跑付費。
- 精準的快取策略:良好的
cache設計可降低 50–80% 的 Pipeline 時間(主要是npm install、pip install等依賴安裝步驟),直接減少計費分鐘數,同時也加快開發回饋速度。
# GitLab Runner autoscaler 設定片段(config.toml)
[[runners]]
executor = "docker+machine"
[runners.machine]
MachineDriver = "amazonec2"
MachineName = "gitlab-runner-%s"
MachineOptions = [
"amazonec2-instance-type=c5.xlarge",
"amazonec2-spot-price=0.05", # 使用 Spot 實例
"amazonec2-request-spot-instance=true"
]
IdleCount = 0 # 閒置時縮減到 0
IdleTime = 300 # 閒置 5 分鐘後回收GitOps 工作流程(ArgoCD / Flux)
GitOps 是一種以 Git 為唯一真相來源(single source of truth)的運維模式,將基礎設施和應用程式的期望狀態以宣告式(declarative)YAML 定義在 Git 倉庫中,由部署在叢集內的 Agent 負責持續確保叢集實際狀態與 Git 中的定義一致。
典型 GitOps Pipeline(CI 負責建置映像,CD 由 ArgoCD 接管部署):
# CI 階段:建置映像並更新 GitOps 倉庫中的映像標籤
update-gitops-repo:
stage: deploy
script:
- git clone https://gitlab.com/my-org/k8s-manifests.git
- cd k8s-manifests
- |
# 更新 Kubernetes Deployment 的映像標籤
sed -i "s|image: my-app:.*|image: my-app:$CI_COMMIT_SHA|g" \
apps/my-app/deployment.yaml
- git config user.email "ci@gitlab.com"
- git add -A
- git commit -m "chore: update my-app to $CI_COMMIT_SHORT_SHA"
- git push
# ArgoCD 會自動偵測到 GitOps 倉庫的變更並同步到叢集環境隔離策略:在 GitOps 架構中,不同環境的配置通常透過以下方式隔離,每種方式有其適用場景:
- 分支策略:
main對應生產,staging對應測試,每個環境有獨立的 ArgoCD Application 監聽對應分支,適合環境差異較大、需要獨立審核的場景 - 目錄策略:單一 main 分支下,
envs/prod/、envs/staging/各自存放環境特定設定,結構清晰,避免分支合併帶來的複雜度 - Kustomize Overlays:共用 base 設定,各環境 overlay 只覆蓋差異部分(映像標籤、副本數、資源限制、ConfigMap 值等),DRY 原則的最佳實踐
- Helm Values:不同環境使用不同的
values-prod.yaml、values-staging.yaml,適合已有 Helm Chart 的應用程式,避免環境設定散落各處
- 每個環境對應一個獨立的 ArgoCD Application,避免跨環境設定互相干擾,也讓各環境的同步狀態一目瞭然
- 生產環境的 ArgoCD 設定為手動同步(
syncPolicy: manual,需要人工點擊確認),測試與開發環境設為自動同步 - 使用 Sealed Secrets 或 External Secrets Operator 管理叢集內的敏感資訊,將加密後的 Secret 安全地存入 Git
- 所有對叢集的配置變更都必須先進 Git,禁止直接
kubectl apply到生產環境,確保所有變更都有審計紀錄 - 為 GitOps 倉庫設定 branch protection rules,要求至少一名 SRE 審核後才能合併到 production 分支