結論先講

很多團隊的 CI/CD 長這樣:lint → test → build → deploy。看起來有模有樣,但真的出事的時候才發現沒有 rollback、沒有 canary、secrets 是寫死的、build 要跑 20 分鐘。

好的 CI/CD pipeline 不只是「自動化部署」,它是你的品質守門員安全網。這篇列出 11 個檢查點,幫你評估你的 pipeline 夠不夠成熟。


體檢清單

1. 標準 Pipeline 階段

一個合格的 pipeline 至少要有這些階段:

┌──────┐    ┌──────┐    ┌───────┐    ┌────────┐    ┌────────┐
│ Lint │ →  │ Test │ →  │ Build │ →  │ Deploy │ →  │ Verify │
│      │    │      │    │       │    │Staging │    │        │
└──────┘    └──────┘    └───────┘    └────────┘    └────────┘
                                          │
                                     ┌────────┐
                                     │ Deploy │
                                     │  Prod  │
                                     └────────┘
# GitHub Actions 完整範例
name: CI/CD Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
 
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check
 
  test:
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
        ports: ['5432:5432']
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run test:coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/
 
  build:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
 
  deploy-staging:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    environment: staging
    steps:
      - run: echo "Deploy to staging..."
 
  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://myapp.com
    steps:
      - run: echo "Deploy to production..."
  • Lint(ESLint、Prettier、stylelint)
  • Type check(tsc --noEmit
  • Unit test + coverage
  • Build
  • Deploy to staging
  • Deploy to production(需要 approval)

2. 環境隔離

環境觸發條件用途
Preview每個 PR讓 reviewer 看實際效果
Developmentpush to develop開發團隊測試
Stagingpush to mainQA 驗收、與 production 同等環境
Productionmanual approval 或 tag真正的使用者
  • 每個環境有獨立的設定和資料庫
  • PR 有 preview deployment
  • Staging 環境盡量跟 production 一致

3. Rollback 策略

部署失敗不可怕,可怕的是回不去。

# Docker image rollback
kubectl set image deployment/web web=myapp:v1.2.3  # 部署新版
kubectl rollout undo deployment/web                  # 回滾
 
# 或者直接部署舊版
kubectl set image deployment/web web=myapp:v1.2.2
  • 可以在 5 分鐘內回滾到上一版
  • 保留最近 N 個版本的 artifact
  • 資料庫 migration 有 backward compatibility
  • Rollback 有文件化的 SOP

4. Feature Flags

Feature flag 讓你可以部署程式碼但不啟用功能

// 使用 feature flag
if (featureFlags.isEnabled('new-checkout-flow', { userId })) {
  return <NewCheckoutPage />;
} else {
  return <OldCheckoutPage />;
}
工具特色
LaunchDarkly功能最完整、有 targeting
Unleash開源、可自架
Flagsmith開源、有 SaaS
自己做簡單 JSON config
  • 大功能用 feature flag 控制
  • 可以針對特定使用者開啟(gradual rollout)
  • 舊的 flag 有定期清理

5. Canary / Blue-Green Deployment

Blue-Green:
┌──────────┐     ┌──────────┐
│  Blue    │     │  Green   │
│  (v1.0)  │ ←── │  (v1.1)  │  ← 新版部署到 Green
│  Active  │     │  Standby │
└──────────┘     └──────────┘
       ↓ 切換流量
┌──────────┐     ┌──────────┐
│  Blue    │     │  Green   │
│  (v1.0)  │     │  (v1.1)  │
│  Standby │     │  Active  │  ← 流量切到 Green
└──────────┘     └──────────┘

Canary:
┌──────────┐
│  v1.0    │ ← 95% 流量
│  (穩定)   │
└──────────┘
┌──────────┐
│  v1.1    │ ← 5% 流量(金絲雀)
│  (新版)   │     ↓ 觀察 metrics,沒問題就逐步增加
└──────────┘
策略優點缺點
直接部署簡單失敗影響所有使用者
Blue-Green切換快、回滾即時需要雙倍資源
Canary風險最小實作複雜
Rolling不需額外資源新舊版本共存期間
  • Production 不用「直接覆蓋」的方式部署
  • 至少有 rolling update
  • 理想情況用 canary + 自動回滾

6. Secrets Injection

# GitHub Actions secrets
steps:
  - run: npm run deploy
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      API_KEY: ${{ secrets.API_KEY }}
 
# 不好:secrets 寫在 yaml 裡
# 不好:secrets 透過 echo 印出來(會出現在 log 裡)
  • Secrets 用 CI/CD 平台的 secret store
  • 不同環境用不同 secrets
  • Secrets 不會出現在 build log 裡
  • 有 secrets rotation 機制

7. Artifact Management

  • Build output 存成 artifact
  • Docker image 推到 registry(ECR、GCR、Docker Hub)
  • Image 有 tagging 策略(git SHA + semver)
  • 有清理舊 artifact 的機制
# Docker image tagging
docker build -t myapp:${GIT_SHA} -t myapp:v1.2.3 -t myapp:latest .
docker push myapp:${GIT_SHA}
docker push myapp:v1.2.3

8. 通知

  • Build 失敗有通知(Slack、Discord、Email)
  • 部署成功/失敗有通知
  • 通知包含必要資訊(commit、author、link)
# Slack 通知
- uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Deploy ${{ github.sha }} to production: ${{ job.status }}"
      }

9. Build Cache

Pipeline 跑 20 分鐘是不能接受的。

# Node modules cache
- uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
 
# Docker layer cache
- uses: docker/build-push-action@v5
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max
  • 依賴套件有 cache
  • Docker build 有 layer cache
  • CI 總時間 < 10 分鐘(理想 < 5 分鐘)

10. Parallel Execution

# 平行執行 lint 和 test
jobs:
  lint:
    runs-on: ubuntu-latest
    # ...
 
  test-unit:
    runs-on: ubuntu-latest
    # ...
 
  test-e2e:
    runs-on: ubuntu-latest
    # ...
 
  build:
    needs: [lint, test-unit, test-e2e]  # 等所有平行任務完成
    # ...
  • 獨立的 job 平行跑
  • Test suite 可以 shard(分散到多台機器)
  • 有分析 pipeline bottleneck

11. Branch Protection

  • main 分支有保護(不能直接 push)
  • PR 必須通過 CI 才能合併
  • PR 需要至少一個 review
  • Force push 被禁止

CI/CD 平台比較

平台優點缺點適合
GitHub Actions整合好、marketplace 豐富大量使用可能貴GitHub 用戶
GitLab CI功能完整、可自架設定較複雜GitLab 用戶
CircleCI快、UX 好免費額度少中大型團隊
Jenkins超高彈性維護成本高、UI 古老企業、特殊需求
Buildkite可自架 runner需要管理 agent大型團隊

Pipeline 成熟度

等級描述
L0手動部署,沒有 CI
L1有 CI(lint + test),手動 deploy
L2CI + CD(自動部署到 staging)
L3全自動 + rollback + feature flags
L4Canary + 自動回滾 + metrics-based deploy

FAQ

Q1: CI 跑太慢怎麼辦?

三個方向:cache(npm、Docker layer)、parallel(lint 和 test 同時跑)、shard(把 test 分散到多台)。還可以考慮只跑受影響的 test(affected test detection)。

Q2: 要不要 CD 到 production?

看你的團隊和產品。如果有完善的 test、feature flag、canary,全自動 CD 是可行的。如果 test 覆蓋率低或產品容錯率低(金融、醫療),在 production 前加個 manual approval 比較安全。

Q3: Feature flag 會不會變成技術債?

會。所以要有紀律地清理已經全量上線的 flag。建議設定一個規則:feature 上線兩週後,如果沒有要回滾,就清掉 flag。

Q4: Monorepo 的 CI 怎麼做?

用 affected detection。Nx、Turborepo 都有內建功能,只跑被改到的 package 的 CI。不然每次改一行字就跑全部 package 的 test,CI 時間會爆炸。

Q5: 自架 runner 還是用平台的?

小團隊用平台的就好(GitHub Actions runners)。如果你有特殊硬體需求(GPU、ARM)、安全合規要求、或 CI 用量很大,再考慮自架。


系列導航