進階技巧

Matrix Build、Monorepo 策略、安全掃描、效能測試、成本優化與 GitOps 工作流程。

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
Matrix Build 讓 6 個測試組合(3 個 Node 版本 × 2 個 OS)並行執行,而非序列等待。原本需要 30 分鐘的測試矩陣可縮短至 5 分鐘,唯一的瓶頸是可用 Runner 的數量。對於有嚴格版本相容性需求的套件維護者,這是不可或缺的工具。

跨平台 Docker 映像建置(同時產出 linux/amd64linux/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 .
若要將多架構映像合併為單一 manifest(讓 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 test
動態 Pipeline 適合服務數量不固定的 Monorepo,避免每新增一個服務就要手動修改 CI 設定檔。detect-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 會自動分析哪些套件被改動,以及哪些套件依賴這些改動

SAST / DAST 安全掃描整合

DevSecOps 的核心理念:安全不是部署前才做的一道關卡,而是從第一行程式碼就開始的持續實踐。將安全掃描嵌入 Pipeline,讓開發者在本地提交時就能看到安全問題,而非在 code review 或上線後才發現,大幅降低修復成本。

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: true
安全掃描的導入建議分三個階段:第一階段設定 allow_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
建議在 staging 環境執行效能測試,而非直接對 CI 中的臨時容器測試。CI 容器的 CPU 與記憶體資源受限,測試結果不具代表性。更好的做法是在 staging 環境跑完負載測試後,將結果與上一個基準版本比較,若 P95 延遲上升超過 20% 則阻擋部署。

成本優化:Self-hosted Runners 與 Spot Instances

CI/CD 的計算成本往往是工程團隊最容易忽略的隱性開銷。隨著 Pipeline 複雜度增加,SaaS CI 的分鐘數費用可能迅速超過預算。以下是業界常見的四種成本控制策略,依實施難度排序:

# 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 分鐘後回收
成本優化不只是省錢,也是讓 Pipeline 更快。當 Pipeline 從 20 分鐘縮短到 5 分鐘,開發者等待回饋的時間減少,上下文切換成本降低,整體工程效率才能真正提升。建議先用 GitLab 的 Pipeline 分析報告找出最耗時的 job,再針對性地優化。

GitOps 工作流程(ArgoCD / Flux)

GitOps 是一種以 Git 為唯一真相來源(single source of truth)的運維模式,將基礎設施和應用程式的期望狀態以宣告式(declarative)YAML 定義在 Git 倉庫中,由部署在叢集內的 Agent 負責持續確保叢集實際狀態與 Git 中的定義一致。

GitOps 的關鍵轉變:傳統 CI/CD 是「推送(push)」模式——CI 伺服器主動推送變更到叢集;GitOps 是「拉取(pull)」模式——部署在叢集內的 Agent(ArgoCD 或 Flux)定期拉取 Git 狀態並自動同步。這個改變讓叢集存取憑證不需要暴露在 CI 伺服器中,顯著提升安全性。

典型 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 架構中,不同環境的配置通常透過以下方式隔離,每種方式有其適用場景: