結論先講

每個微服務必須有自己獨立的 CI/CD Pipeline。 一個服務改了一行程式碼,不應該觸發其他 4 個服務的 build + test + deploy。Mono-repo 用路徑過濾(path filter)實現,multi-repo 天生就是獨立的。Shared library 用語意版號(semver)管理,不要用 latest——否則某天 shared lib 一更新,5 個服務同時爆炸。


單體 vs 微服務的 CI/CD 差異

單體:
  git push → build 整個專案 → 跑全部測試 → deploy 一包

微服務:
  git push(改了 order-service)
    → 只 build order-service
    → 只跑 order-service 的測試
    → 只 deploy order-service
    → 其他 4 個服務完全不動

聽起來很直覺,但實作起來有兩個大問題:

  1. 怎麼知道「只改了 order-service」? → 取決於 mono-repo 或 multi-repo
  2. 改了 shared library 怎麼辦? → 所有依賴它的服務都要重新 build

Mono-repo vs Multi-repo

Multi-repo:每個服務一個 Git repo

github.com/myteam/order-service      → 自己的 CI/CD
github.com/myteam/payment-service    → 自己的 CI/CD
github.com/myteam/user-service       → 自己的 CI/CD
github.com/myteam/shared-lib         → 發布到 npm / PyPI

優點:Pipeline 天生獨立,權限管理簡單(誰能改哪個 repo)。

缺點:跨服務改動要開多個 PR,shared library 更新要逐一升版。

Mono-repo:所有服務在同一個 Git repo

myproject/
├── services/
│   ├── order-service/
│   ├── payment-service/
│   └── user-service/
├── libs/
│   └── shared-utils/
└── .gitlab-ci.yml

優點:跨服務改動一個 PR 搞定,shared library 不用發佈到 registry。

缺點:CI/CD 需要路徑過濾,否則改一行就 build 全部。

怎麼選

團隊狀況建議
< 10 人,服務 < 5 個Mono-repo(溝通成本低)
10-30 人,跨團隊Multi-repo(權限隔離)
> 30 人,各團隊獨立交付Multi-repo + 內部 package registry

GitLab CI:Mono-repo 的路徑過濾

# .gitlab-ci.yml(根目錄)
 
stages:
  - build
  - test
  - deploy
 
# Order Service Pipeline
order-build:
  stage: build
  rules:
    - changes:
        - services/order-service/**/*
        - libs/shared-utils/**/*      # shared lib 改了也要觸發
  script:
    - cd services/order-service
    - docker build -t order-service:$CI_COMMIT_SHA .
 
order-test:
  stage: test
  rules:
    - changes:
        - services/order-service/**/*
        - libs/shared-utils/**/*
  script:
    - cd services/order-service
    - npm test
 
order-deploy:
  stage: deploy
  rules:
    - changes:
        - services/order-service/**/*
        - libs/shared-utils/**/*
      if: $CI_COMMIT_BRANCH == "main"
  script:
    - kubectl set image deployment/order-service order-service=order-service:$CI_COMMIT_SHA

GitHub Actions 的等價寫法用 paths filter:

# .github/workflows/order-service.yml
on:
  push:
    paths:
      - 'services/order-service/**'
      - 'libs/shared-utils/**'

Build → Test → Deploy 的標準流程

每個服務的 Pipeline:

1. Build
   - docker build(產出 image)
   - tag = commit SHA(不要用 latest)

2. Test
   - Unit test(服務內部邏輯)
   - Integration test(DB、Redis 用 docker compose 起)
   - Contract test(API 合約有沒有破壞)← 這個超重要

3. Deploy
   - Staging:自動部署,跑 smoke test
   - Production:手動觸發或自動(看團隊信心)

Contract Test:避免改 A 壞 B

Order Service 依賴 Payment Service 的 POST /payments API。

Contract Test 做的事:
  Payment Service 發布新版前,跑 consumer-driven contract test
  → 確認 Order Service 期望的 request/response 格式沒變
  → 如果變了 → Pipeline 失敗 → 不准 deploy

工具:Pact(最成熟)、Spring Cloud Contract

這是微服務 CI/CD 最關鍵的一環。沒有 contract test,你遲早會遇到「Payment Service 改了回傳格式,Order Service 噴 500」的慘劇。


Shared Library 的版本管理

❌ 錯誤做法:
  shared-utils: "latest"
  → shared-utils 某天改了一個函式簽名
  → 5 個服務同時 build failure

✅ 正確做法:
  shared-utils: "^2.3.0"
  → 每個服務明確鎖定版本
  → shared-utils 升版 → 各服務「主動」升級 + 跑測試

版本升級策略

升級類型做法自動化程度
Patch(bug fix)自動升級(CI bot PR)全自動
Minor(新功能)自動升級 + 人工 review半自動
Major(breaking change)手動升級 + migration guide手動

工具推薦:Renovate Bot 或 Dependabot。它會自動開 PR 告訴你哪些 dependency 有新版本。


常見的坑

1. Pipeline 太慢

5 個服務各跑 10 分鐘 = 50 分鐘(如果串行)

解法:
- 平行化(GitLab CI 的 parallel、GitHub Actions 的 matrix)
- Docker layer cache(別每次從頭 build)
- 只跑被影響的測試(test impact analysis)

2. 環境不一致

「在我的電腦上跑得過啊」
→ CI 用 Docker,本機也用 Docker
→ 固定 base image 版本(node:20.11-alpine,不要 node:latest)

3. Deploy 順序有依賴

Payment Service v2 需要 Order Service v3 的新 API。

錯誤:先 deploy Payment v2 → 呼叫還不存在的 API → 500
正確:先 deploy Order v3 → 確認新 API 正常 → 再 deploy Payment v2

→ 用 API 版本化(/v2/orders)避免這個問題
→ 詳見 [[micro-service/49-zero-downtime-deploy|下一篇的部署策略]]

下一篇

Zero Downtime Deployment — Pipeline 跑完了,怎麼部署才不會中斷服務?Rolling Update、Blue-Green、Canary 三種策略的取捨。


本系列文章

完整 68 篇目錄見 系列首頁

← 上一篇:資料一致性(二):Eventual Consistency 用戶能不能接受 → 下一篇:Zero Downtime Deployment:Rolling Update vs Blue-Green vs Canary