返回文章列表

GitHub Actions 自動化工作流程

深入探討 GitHub Actions 的核心概念與實作技巧,涵蓋事件觸發機制、作業與步驟設計、Secrets 管理、除錯技巧,以及藍綠佈署、滾動佈署、金絲雀佈署等進階佈署策略,並整合 DORA 指標與 DevSecOps 實踐,提供完整的 CI/CD 自動化工作流程指南。

DevOps 軟體工程

在現代軟體開發中,自動化不僅是提升效率的手段,更是確保軟體品質和縮短交付週期的關鍵因素。GitHub Actions 作為 GitHub 平台內建的 CI/CD 工具,以其深度整合、強大的生態系統和靈活的配置能力,成為眾多開發團隊的首選自動化解決方案。從簡單的程式碼檢查到複雜的多環境佈署,GitHub Actions 都能夠勝任,並且持續演進以滿足現代軟體開發的需求。

本文將深入探討 GitHub Actions 的核心概念和進階應用,從基礎的事件觸發機制到複雜的佈署策略,從安全管理到效能指標追蹤。我們不僅會介紹如何建立和配置工作流程,還會探討各種佈署策略的優缺點和適用場景,以及如何透過 DevOps 指標來持續改進開發流程。無論您是剛接觸 CI/CD 的新手,還是希望最佳化現有流程的資深工程師,都能從本文中獲得有價值的見解。

GitHub Actions 核心架構

GitHub Actions 的設計理念是將軟體開發的各個環節自動化,從程式碼提交到生產環境佈署,形成一個完整的流水線。理解其核心架構是有效使用這個工具的基礎。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' GitHub Actions 架構圖
package "GitHub Actions 架構" {
    rectangle "事件觸發器" as trigger {
        card "Push" as push
        card "Pull Request" as pr
        card "Schedule" as schedule
        card "Workflow Dispatch" as dispatch
    }

    rectangle "工作流程" as workflow {
        rectangle "作業 1" as job1 {
            card "步驟 1" as step1
            card "步驟 2" as step2
            card "步驟 3" as step3
        }

        rectangle "作業 2" as job2 {
            card "步驟 A" as stepA
            card "步驟 B" as stepB
        }
    }

    rectangle "執行器" as runner {
        card "GitHub Hosted" as gh_runner
        card "Self-Hosted" as self_runner
    }
}

trigger --> workflow
job1 --> job2 : 依賴
workflow --> runner

@enduml

GitHub Actions 的架構由幾個核心元件組成。工作流程(Workflow)是最頂層的單位,定義了整個自動化流程。每個工作流程由一個或多個作業(Job)組成,作業之間可以平行執行或按照依賴關係順序執行。每個作業包含多個步驟(Step),步驟按順序執行,可以是執行命令或呼叫預定義的 Action。執行器(Runner)是實際執行作業的環境,可以是 GitHub 提供的虛擬機器,也可以是自架的機器。

這種層次化的架構設計提供了極大的靈活性。開發者可以根據需求設計簡單的單作業工作流程,也可以設計包含多個平行和依賴作業的複雜流水線。每個作業可以在不同的執行環境中運行,這對於跨平台測試特別有用。

事件觸發機制

事件是啟動 GitHub Actions 工作流程的關鍵。GitHub 提供了豐富的事件類型,涵蓋了軟體開發生命週期的各個階段。正確選擇和配置觸發事件對於建立高效的自動化流程至關重要。

# .github/workflows/comprehensive-triggers.yml
# 這個工作流程展示了各種事件觸發機制的配置方式

name: 綜合觸發器示範

# 定義多種觸發事件
# GitHub Actions 支援豐富的事件類型,可以根據需求組合使用
on:
  # 推播事件:當程式碼被推播到指定分支時觸發
  # 這是最常見的觸發方式,適用於持續整合場景
  push:
    branches:
      - main
      - develop
      - 'feature/**'  # 支援萬用字元匹配
    paths:
      - 'src/**'      # 只有當這些路徑下的檔案變更時才觸發
      - 'package.json'
    paths-ignore:
      - '**.md'       # 忽略 Markdown 檔案的變更
      - 'docs/**'

  # Pull Request 事件:當 PR 被建立、更新或合併時觸發
  # 適用於程式碼審查和自動化測試
  pull_request:
    branches: [ main, develop ]
    types: [ opened, synchronize, reopened, closed ]

  # 排程事件:使用 Cron 語法定期執行
  # 適用於定期任務,如每日建置、定期清理等
  schedule:
    # 每天凌晨 2:00 UTC 執行
    - cron: '0 2 * * *'
    # 每週一早上 8:00 UTC 執行
    - cron: '0 8 * * 1'

  # 手動觸發事件:允許從 GitHub UI 或 API 手動執行
  # 適用於需要人工介入的任務,如手動佈署
  workflow_dispatch:
    inputs:
      environment:
        description: '選擇佈署環境'
        required: true
        default: 'staging'
        type: choice
        options:
          - development
          - staging
          - production
      debug_mode:
        description: '啟用除錯模式'
        required: false
        type: boolean
        default: false

  # 其他工作流程完成時觸發
  # 適用於需要串聯多個工作流程的場景
  workflow_run:
    workflows: ["Build and Test"]
    types: [ completed ]
    branches: [ main ]

  # 當有新的 Release 發布時觸發
  # 適用於發布流程自動化
  release:
    types: [ published, prereleased ]

jobs:
  demonstrate_triggers:
    runs-on: ubuntu-latest

    steps:
      # 顯示觸發事件的詳細資訊
      - name: 顯示事件資訊
        run: |
          # 輸出觸發事件的詳細資訊,便於除錯和追蹤
          echo "事件名稱: ${{ github.event_name }}"
          echo "觸發者: ${{ github.actor }}"
          echo "儲存庫: ${{ github.repository }}"
          echo "分支: ${{ github.ref }}"
          echo "提交 SHA: ${{ github.sha }}"

      # 根據不同的觸發事件執行不同的邏輯
      - name: 處理推播事件
        if: github.event_name == 'push'
        run: |
          # 推播事件特定的處理邏輯
          echo "處理推播事件"
          echo "推播者: ${{ github.event.pusher.name }}"
          echo "提交訊息: ${{ github.event.head_commit.message }}"

      - name: 處理手動觸發事件
        if: github.event_name == 'workflow_dispatch'
        run: |
          # 使用手動觸發時傳入的參數
          echo "手動觸發工作流程"
          echo "目標環境: ${{ github.event.inputs.environment }}"
          echo "除錯模式: ${{ github.event.inputs.debug_mode }}"

      - name: 處理排程事件
        if: github.event_name == 'schedule'
        run: |
          # 排程任務的處理邏輯
          echo "執行排程任務"
          echo "排程時間: $(date)"

事件觸發機制的設計考慮了各種使用場景。推播事件是持續整合的基礎,可以透過分支和路徑篩選來精確控制觸發條件。Pull Request 事件支援多種類型,可以在 PR 生命週期的不同階段執行不同的任務。排程事件使用標準的 Cron 語法,適合定期執行的任務。手動觸發事件提供了人工介入的能力,可以定義輸入參數讓使用者在觸發時提供必要的資訊。

正確配置事件觸發可以顯著提升工作流程的效率。例如,透過路徑篩選可以避免不必要的執行,只在相關檔案變更時才觸發工作流程。透過分支篩選可以為不同的分支設定不同的處理邏輯,例如功能分支只執行測試,主分支則執行完整的測試和佈署。

作業與步驟設計

作業和步驟是工作流程的執行單元。良好的作業設計可以充分利用平行執行的能力,提高整體執行效率。步驟的設計則需要考慮可重用性和維護性。

# .github/workflows/advanced-jobs.yml
# 這個工作流程展示了進階的作業配置技巧

name: 進階作業配置示範

on:
  push:
    branches: [ main ]

# 定義環境變數
# 這些變數可以在所有作業中使用
env:
  NODE_VERSION: '18'
  BUILD_CONFIGURATION: 'Release'

jobs:
  # 建置作業:編譯和打包應用程式
  build:
    name: 建置應用程式
    runs-on: ubuntu-latest

    # 使用矩陣策略在多個環境中執行
    # 這對於跨平台測試和多版本相容性測試非常有用
    strategy:
      matrix:
        node-version: [16, 18, 20]
        os: [ubuntu-latest, windows-latest]
      # 即使某些組合失敗,也繼續執行其他組合
      fail-fast: false

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4
        with:
          # 獲取完整的 Git 歷史記錄,用於版本號生成等
          fetch-depth: 0

      - name: 設定 Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: 安裝相依套件
        run: npm ci
        # 設定工作目錄
        working-directory: ./app

      - name: 執行建置
        run: |
          # 執行建置命令並捕獲輸出
          npm run build
          echo "建置完成"
        env:
          # 傳遞建置時需要的環境變數
          CI: true
          NODE_ENV: production

      - name: 上傳建置產物
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ matrix.os }}-node${{ matrix.node-version }}
          path: ./app/dist
          retention-days: 5

    # 定義作業輸出,供後續作業使用
    outputs:
      build_version: ${{ steps.version.outputs.version }}

  # 測試作業:依賴建置作業
  test:
    name: 執行測試
    needs: build  # 等待 build 作業完成
    runs-on: ubuntu-latest

    # 使用服務容器
    # 這對於需要資料庫或其他服務的整合測試非常有用
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        # 設定健康檢查,確保服務啟動完成
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 設定 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: 安裝相依套件
        run: npm ci

      - name: 執行單元測試
        run: npm run test:unit
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379

      - name: 執行整合測試
        run: npm run test:integration
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379

      - name: 上傳測試報告
        uses: actions/upload-artifact@v4
        if: always()  # 即使測試失敗也上傳報告
        with:
          name: test-reports
          path: ./coverage

  # 佈署作業:依賴測試作業
  deploy:
    name: 佈署到生產環境
    needs: [build, test]  # 等待多個作業完成
    runs-on: ubuntu-latest

    # 使用環境保護規則
    # 這提供了額外的安全層,可以要求審核者批准
    environment:
      name: production
      url: https://example.com

    # 設定佈署作業的並行限制
    # 確保同一時間只有一個佈署在執行
    concurrency:
      group: production-deploy
      cancel-in-progress: false

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 下載建置產物
        uses: actions/download-artifact@v4
        with:
          name: build-ubuntu-latest-node18
          path: ./dist

      - name: 佈署到生產環境
        run: |
          # 執行佈署指令碼
          echo "正在佈署到生產環境..."
          # 實際佈署邏輯會在這裡執行
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
          ENVIRONMENT: production

      - name: 通知佈署完成
        if: success()
        run: |
          # 發送佈署成功通知
          echo "佈署成功完成"

作業設計的關鍵在於平衡執行效率和資源使用。透過矩陣策略,可以在多個環境中平行執行相同的任務,這對於確保跨平台相容性非常重要。服務容器的使用簡化了整合測試的環境設定,不需要額外配置測試資料庫或快取服務。作業之間的依賴關係確保了執行順序,而並行限制則防止了資源競爭問題。

Secrets 與變數管理

在自動化流程中,妥善管理敏感資訊和配置變數是確保安全性的關鍵。GitHub Actions 提供了完善的機制來處理這些需求。

# .github/workflows/secrets-management.yml
# 這個工作流程展示了 Secrets 和變數的管理技巧

name: Secrets 與變數管理示範

on:
  workflow_dispatch:
    inputs:
      target_environment:
        description: '目標環境'
        required: true
        type: choice
        options:
          - development
          - staging
          - production

# 工作流程層級的環境變數
# 這些變數可以在所有作業中使用
env:
  APP_NAME: my-application
  BUILD_NUMBER: ${{ github.run_number }}

jobs:
  demonstrate_secrets:
    runs-on: ubuntu-latest

    # 作業層級的環境變數
    # 這些變數只在此作業中可用
    env:
      JOB_SPECIFIC_VAR: 'job-value'

    steps:
      # 展示如何使用 Secrets
      - name: 使用 Secrets 進行認證
        run: |
          # Secrets 在日誌中會被自動遮罩,顯示為 ***
          # 這確保了敏感資訊不會被意外洩漏

          # 使用 API 金鑰進行認證
          curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \
               -H "Content-Type: application/json" \
               https://api.example.com/health

      # 使用 Secrets 設定環境變數
      - name: 配置資料庫連線
        env:
          DB_HOST: ${{ secrets.DB_HOST }}
          DB_USER: ${{ secrets.DB_USER }}
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          DB_NAME: ${{ secrets.DB_NAME }}
        run: |
          # 在這個步驟中可以安全地使用資料庫連線資訊
          echo "正在連線到資料庫..."
          # 實際的資料庫操作會在這裡執行

      # 使用 GitHub 內建的 Token
      - name: 使用 GITHUB_TOKEN
        run: |
          # GITHUB_TOKEN 是 GitHub 自動提供的權杖
          # 可用於執行 GitHub API 操作

          # 取得儲存庫資訊
          curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
               https://api.github.com/repos/${{ github.repository }}

          # 建立 Issue 評論
          # 注意:需要適當的許可權設定

      # 在不同環境中使用不同的 Secrets
      - name: 環境特定配置
        run: |
          # 根據目標環境選擇對應的 Secrets
          # 這種模式適合多環境佈署

          case "${{ github.event.inputs.target_environment }}" in
            "development")
              echo "使用開發環境配置"
              # 使用開發環境的 Secrets
              ;;
            "staging")
              echo "使用測試環境配置"
              # 使用測試環境的 Secrets
              ;;
            "production")
              echo "使用生產環境配置"
              # 使用生產環境的 Secrets
              ;;
          esac

      # 將 Secrets 寫入檔案(用於需要檔案形式的憑證)
      - name: 設定 SSH 金鑰
        run: |
          # 有些工具需要金鑰以檔案形式存在
          # 可以將 Secret 寫入檔案,但要確保權限正確

          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa

          # 添加已知主機
          ssh-keyscan github.com >> ~/.ssh/known_hosts

      # 使用 Secrets 進行容器登入
      - name: 登入容器倉庫
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 使用環境變數進行應用程式配置
      - name: 應用程式配置
        run: |
          # 建立配置檔案
          cat > config.json << EOF
          {
            "app_name": "${{ env.APP_NAME }}",
            "build_number": "${{ env.BUILD_NUMBER }}",
            "environment": "${{ github.event.inputs.target_environment }}",
            "commit_sha": "${{ github.sha }}"
          }
          EOF

          cat config.json

Secrets 管理的核心原則是最小權限原則。只為工作流程提供必要的存取權限,並且定期輪換敏感憑證。GitHub 會自動遮罩 Secrets 在日誌中的顯示,但開發者仍需要注意不要透過其他方式洩漏這些資訊,例如將它們寫入公開的檔案或在錯誤訊息中暴露。

對於需要在多個環境中使用不同配置的場景,可以使用環境(Environment)功能。每個環境可以有自己的 Secrets 和變數,並且可以設定保護規則,例如要求審核者批准才能佈署到生產環境。

除錯與最佳化技巧

除錯是確保工作流程正確執行的重要環節。GitHub Actions 提供了多種除錯工具和技巧,幫助開發者快速定位和解決問題。

# .github/workflows/debugging.yml
# 這個工作流程展示了除錯技巧

name: 除錯技巧示範

on:
  workflow_dispatch:

jobs:
  debugging_demo:
    runs-on: ubuntu-latest

    steps:
      # 啟用詳細日誌
      - name: 啟用除錯模式
        run: |
          # 可以透過設定 ACTIONS_STEP_DEBUG 來獲得更詳細的日誌
          # 這通常在儲存庫的 Secrets 中設定為 true

          # 輸出環境資訊用於除錯
          echo "GitHub 事件: ${{ github.event_name }}"
          echo "工作流程執行 ID: ${{ github.run_id }}"
          echo "執行器名稱: ${{ runner.name }}"
          echo "執行器作業系統: ${{ runner.os }}"
          echo "執行器架構: ${{ runner.arch }}"

      # 輸出詳細的上下文資訊
      - name: 輸出 GitHub 上下文
        run: |
          # 將 GitHub 上下文轉換為 JSON 格式輸出
          # 這對於理解可用的資訊非常有幫助
          echo '${{ toJson(github) }}'

      - name: 輸出作業上下文
        run: |
          echo '${{ toJson(job) }}'

      - name: 輸出步驟上下文
        run: |
          echo '${{ toJson(steps) }}'

      # 使用條件表達式進行除錯
      - name: 條件執行除錯
        if: ${{ runner.debug == '1' }}
        run: |
          # 這個步驟只在啟用除錯模式時執行
          echo "除錯模式已啟用"
          # 輸出更詳細的除錯資訊

      # 錯誤處理和日誌輸出
      - name: 錯誤處理示範
        id: error_handling
        continue-on-error: true  # 即使失敗也繼續執行
        run: |
          # 使用 set -e 讓命令失敗時立即停止
          set -e

          echo "執行可能失敗的操作..."

          # 模擬一個可能失敗的操作
          if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
            echo "這是手動觸發"
          else
            exit 1
          fi

      # 檢查前一步驟的結果
      - name: 檢查執行結果
        run: |
          if [ "${{ steps.error_handling.outcome }}" == "failure" ]; then
            echo "前一步驟失敗,執行回復操作..."
          else
            echo "前一步驟成功"
          fi

      # 使用工作流程命令進行除錯
      - name: 使用工作流程命令
        run: |
          # 設定輸出變數
          echo "version=1.0.0" >> $GITHUB_OUTPUT

          # 設定環境變數供後續步驟使用
          echo "MY_VAR=my-value" >> $GITHUB_ENV

          # 添加摘要
          echo "## 建置摘要" >> $GITHUB_STEP_SUMMARY
          echo "- 版本: 1.0.0" >> $GITHUB_STEP_SUMMARY
          echo "- 建置時間: $(date)" >> $GITHUB_STEP_SUMMARY

          # 分組日誌輸出
          echo "::group::詳細資訊"
          echo "這是一些詳細資訊"
          echo "可以被摺疊顯示"
          echo "::endgroup::"

          # 輸出警告和錯誤訊息
          echo "::warning file=app.js,line=10,col=5::這是一個警告訊息"
          echo "::notice::這是一個通知訊息"

      # 使用快取提升效能
      - name: 快取相依套件
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      # 超時設定
      - name: 設定超時
        timeout-minutes: 5
        run: |
          # 這個步驟最多執行 5 分鐘
          echo "執行限時操作..."

除錯的關鍵在於獲取足夠的資訊來診斷問題。GitHub Actions 提供了豐富的上下文物件,包含了執行環境、事件資訊、作業狀態等詳細資料。透過輸出這些上下文,可以快速了解工作流程的執行狀態。工作流程命令(如設定輸出、環境變數、摘要等)是與 GitHub Actions 執行環境互動的重要工具。

效能最佳化也是重要的考量。快取機制可以顯著減少相依套件的下載時間,特別是對於 npm、pip 等套件管理器。合理設定超時可以防止異常情況導致的長時間等待。矩陣策略的 fail-fast 選項可以在發現問題時快速終止其他執行,節省資源。

藍綠佈署策略

藍綠佈署是一種零停機佈署策略,透過維護兩個相同的環境來實現無縫切換。這種策略的核心優勢是可以快速回復,如果新版本出現問題,只需要將流量切換回舊環境即可。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' 藍綠佈署流程圖
rectangle "負載平衡器" as lb

package "藍色環境 (v1.3)" as blue {
    rectangle "藍色伺服器群" as blue_servers
}

package "綠色環境 (v1.4)" as green {
    rectangle "綠色伺服器群" as green_servers
}

lb --> blue_servers : 目前流量
lb ..> green_servers : 準備切換

note right of green
  新版本已佈署並測試完成
  準備接收生產流量
end note

@enduml

藍綠佈署的工作原理是維護兩個完全相同的生產環境。在任一時刻,只有一個環境接收生產流量(稱為「藍色」環境),而另一個環境則用於準備新版本(稱為「綠色」環境)。當新版本準備就緒並通過測試後,負載平衡器會將流量切換到綠色環境。如果發現問題,可以立即切換回藍色環境。

# .github/workflows/blue-green-deploy.yml
# 這個工作流程實作了藍綠佈署策略

name: 藍綠佈署

on:
  workflow_dispatch:
    inputs:
      version:
        description: '要佈署的版本'
        required: true

env:
  AWS_REGION: ap-northeast-1
  CLUSTER_NAME: production-cluster

jobs:
  # 準備綠色環境
  prepare_green:
    name: 準備綠色環境
    runs-on: ubuntu-latest

    outputs:
      green_target_group: ${{ steps.create_green.outputs.target_group_arn }}

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 配置 AWS 憑證
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: 建立綠色目標群組
        id: create_green
        run: |
          # 建立新的目標群組用於綠色環境
          # 這個目標群組將接收新版本的流量

          GREEN_TG_ARN=$(aws elbv2 create-target-group \
            --name "green-tg-${{ github.run_number }}" \
            --protocol HTTP \
            --port 80 \
            --vpc-id ${{ secrets.VPC_ID }} \
            --health-check-path /health \
            --health-check-interval-seconds 30 \
            --healthy-threshold-count 2 \
            --query 'TargetGroups[0].TargetGroupArn' \
            --output text)

          echo "target_group_arn=$GREEN_TG_ARN" >> $GITHUB_OUTPUT
          echo "建立綠色目標群組: $GREEN_TG_ARN"

      - name: 佈署到綠色環境
        run: |
          # 更新 ECS 服務以使用新版本映像檔
          # 這將啟動新的任務並註冊到綠色目標群組

          aws ecs update-service \
            --cluster ${{ env.CLUSTER_NAME }} \
            --service green-service \
            --task-definition "app:${{ github.event.inputs.version }}" \
            --force-new-deployment

          echo "正在佈署版本 ${{ github.event.inputs.version }} 到綠色環境"

      - name: 等待綠色環境就緒
        run: |
          # 等待服務穩定
          # 這確保所有新任務都已啟動並通過健康檢查

          aws ecs wait services-stable \
            --cluster ${{ env.CLUSTER_NAME }} \
            --services green-service

          echo "綠色環境已就緒"

  # 測試綠色環境
  test_green:
    name: 測試綠色環境
    needs: prepare_green
    runs-on: ubuntu-latest

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 執行煙霧測試
        run: |
          # 對綠色環境執行基本的功能測試
          # 這些測試確保新版本的核心功能正常運作

          GREEN_URL="${{ secrets.GREEN_ENV_URL }}"

          # 測試健康檢查端點
          HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$GREEN_URL/health")
          if [ "$HTTP_STATUS" != "200" ]; then
            echo "健康檢查失敗,狀態碼: $HTTP_STATUS"
            exit 1
          fi

          # 測試 API 端點
          HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$GREEN_URL/api/v1/status")
          if [ "$HTTP_STATUS" != "200" ]; then
            echo "API 測試失敗,狀態碼: $HTTP_STATUS"
            exit 1
          fi

          echo "煙霧測試通過"

      - name: 執行效能測試
        run: |
          # 使用簡單的負載測試確保效能符合預期
          # 實際專案中可能使用更專業的工具如 k6 或 Artillery

          echo "執行效能測試..."
          # 效能測試邏輯

  # 切換流量
  switch_traffic:
    name: 切換流量
    needs: test_green
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com

    steps:
      - name: 配置 AWS 憑證
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: 切換負載平衡器流量
        run: |
          # 修改負載平衡器規則,將流量切換到綠色環境
          # 這是藍綠佈署的核心步驟

          aws elbv2 modify-listener \
            --listener-arn ${{ secrets.ALB_LISTENER_ARN }} \
            --default-actions Type=forward,TargetGroupArn=${{ needs.prepare_green.outputs.green_target_group }}

          echo "流量已切換到綠色環境"

      - name: 監控新版本
        run: |
          # 持續監控新版本的健康狀態
          # 如果發現問題,可以快速回復

          for i in {1..10}; do
            echo "監控第 $i 次檢查..."

            # 檢查錯誤率
            # 實際實作會從監控系統獲取指標

            sleep 30
          done

          echo "監控完成,新版本運作正常"

      - name: 清理舊環境
        if: success()
        run: |
          # 佈署成功後,可以選擇保留或清理藍色環境
          # 通常會保留一段時間以便需要時快速回復

          echo "佈署成功,舊環境已標記為可清理"

  # 回復機制
  rollback:
    name: 回復到藍色環境
    needs: switch_traffic
    if: failure()
    runs-on: ubuntu-latest

    steps:
      - name: 配置 AWS 憑證
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: 切換回藍色環境
        run: |
          # 將流量切換回藍色環境
          # 這是藍綠佈署的回復操作

          aws elbv2 modify-listener \
            --listener-arn ${{ secrets.ALB_LISTENER_ARN }} \
            --default-actions Type=forward,TargetGroupArn=${{ secrets.BLUE_TARGET_GROUP_ARN }}

          echo "已回復到藍色環境"

      - name: 發送告警通知
        run: |
          # 發送佈署失敗的通知
          echo "佈署失敗,已自動回復到藍色環境"

藍綠佈署的優點是實現零停機和快速回復,但缺點是需要雙倍的基礎設施資源。這種策略適合對可用性要求高、且有足夠資源的應用程式。資料庫遷移是藍綠佈署的一個挑戰,因為兩個環境需要共用相同的資料,需要確保資料庫架構變更是向後相容的。

滾動佈署策略

滾動佈署是一種逐步更新叢集節點的策略,透過分批更新來降低風險並確保高可用性。這種策略不需要額外的基礎設施資源,適合資源受限的環境。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' 滾動佈署流程圖
rectangle "滾動佈署流程" {
    rectangle "階段 1" as stage1 {
        card "節點 1" as n1_old #LightGray
        card "節點 2" as n2_old #LightGray
        card "節點 3" as n3_old #LightGray
        note right: 全部為舊版本
    }

    rectangle "階段 2" as stage2 {
        card "節點 1" as n1_new #LightBlue
        card "節點 2" as n2_old2 #LightGray
        card "節點 3" as n3_old2 #LightGray
        note right: 更新第一個節點
    }

    rectangle "階段 3" as stage3 {
        card "節點 1" as n1_new2 #LightBlue
        card "節點 2" as n2_new #LightBlue
        card "節點 3" as n3_old3 #LightGray
        note right: 更新第二個節點
    }

    rectangle "階段 4" as stage4 {
        card "節點 1" as n1_new3 #LightBlue
        card "節點 2" as n2_new2 #LightBlue
        card "節點 3" as n3_new #LightBlue
        note right: 全部更新完成
    }
}

stage1 --> stage2
stage2 --> stage3
stage3 --> stage4

end note

end note

end note

end note

@enduml

滾動佈署的核心概念是逐步替換舊版本的實例。在更新過程中,始終保持一定數量的實例在線服務,確保應用程式的可用性。Kubernetes 原生支援滾動佈署,可以透過配置最大不可用數量和最大超量數量來控制更新速度。

# .github/workflows/rolling-deploy.yml
# 這個工作流程實作了滾動佈署策略

name: 滾動佈署

on:
  push:
    branches: [ main ]
    paths:
      - 'src/**'
      - 'Dockerfile'
      - 'kubernetes/**'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    name: 建置映像檔
    runs-on: ubuntu-latest

    outputs:
      image_tag: ${{ steps.meta.outputs.tags }}

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 設定 Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: 登入容器倉庫
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: 提取中繼資料
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch
            type=semver,pattern={{version}}

      - name: 建置並推送映像檔
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    name: 滾動佈署
    needs: build
    runs-on: ubuntu-latest

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 設定 kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.28.0'

      - name: 配置 Kubernetes 上下文
        run: |
          # 配置 kubectl 以連接到叢集
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=$(pwd)/kubeconfig

      - name: 更新 Deployment 映像檔
        run: |
          # 使用 kubectl set image 觸發滾動更新
          # Kubernetes 會自動執行滾動佈署策略

          kubectl set image deployment/app \
            app=${{ needs.build.outputs.image_tag }} \
            --record

          echo "開始滾動佈署..."

      - name: 監控佈署狀態
        run: |
          # 等待佈署完成
          # 如果超時或失敗,kubectl 會返回非零狀態碼

          kubectl rollout status deployment/app --timeout=5m

          echo "滾動佈署完成"

      - name: 驗證佈署
        run: |
          # 驗證新版本已正確佈署
          # 檢查 Pod 狀態和映像檔版本

          echo "=== 目前 Pod 狀態 ==="
          kubectl get pods -l app=app

          echo "=== 佈署詳情 ==="
          kubectl describe deployment app | grep Image

      - name: 自動回復(如果失敗)
        if: failure()
        run: |
          # 如果佈署失敗,自動回復到上一個版本
          # Kubernetes 會保留歷史版本,可以快速回復

          echo "佈署失敗,執行回復..."
          kubectl rollout undo deployment/app

          echo "已回復到上一個版本"

# Kubernetes Deployment 配置範例(供參考)
# 這個配置定義了滾動佈署的行為

# apiVersion: apps/v1
# kind: Deployment
# metadata:
#   name: app
# spec:
#   replicas: 3
#   strategy:
#     type: RollingUpdate
#     rollingUpdate:
#       # 更新過程中最多可以有多少個 Pod 不可用
#       maxUnavailable: 1
#       # 更新過程中最多可以超出期望數量多少個 Pod
#       maxSurge: 1
#   selector:
#     matchLabels:
#       app: app
#   template:
#     metadata:
#       labels:
#         app: app
#     spec:
#       containers:
#       - name: app
#         image: ghcr.io/example/app:latest
#         ports:
#         - containerPort: 8080
#         # 存活探針:檢查容器是否正常運作
#         livenessProbe:
#           httpGet:
#             path: /health
#             port: 8080
#           initialDelaySeconds: 30
#           periodSeconds: 10
#         # 就緒探針:檢查容器是否準備好接收流量
#         readinessProbe:
#           httpGet:
#             path: /ready
#             port: 8080
#           initialDelaySeconds: 5
#           periodSeconds: 5

滾動佈署的優點是不需要額外的資源,並且可以逐步驗證新版本。缺點是更新過程較慢,且在更新期間會同時運行新舊版本,需要確保它們能夠相容。健康檢查(存活探針和就緒探針)對於滾動佈署至關重要,它們確保只有健康的實例才會接收流量。

金絲雀佈署策略

金絲雀佈署是一種漸進式釋出策略,先將新版本佈署給一小部分使用者進行測試,然後根據測試結果逐步擴大釋出範圍。這種策略提供了更精細的風險控制,但需要更複雜的流量管理機制。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' 金絲雀佈署流程圖
rectangle "金絲雀佈署" {
    rectangle "負載平衡器" as lb

    package "穩定版本 (v1.3)" as stable {
        rectangle "實例群組" as stable_instances
    }

    package "金絲雀版本 (v1.4)" as canary {
        rectangle "金絲雀實例" as canary_instances
    }

    lb --> stable_instances : 90% 流量
    lb --> canary_instances : 10% 流量
}

note bottom of canary
  金絲雀實例接收少量流量
  用於驗證新版本的穩定性
end note

@enduml

金絲雀佈署的名稱來自於礦工使用金絲雀來檢測礦井中有毒氣體的做法。在軟體佈署中,金絲雀實例就像是哨兵,先行承受可能的風險。如果新版本有問題,只會影響少數使用者,不會造成大規模故障。

# .github/workflows/canary-deploy.yml
# 這個工作流程實作了金絲雀佈署策略

name: 金絲雀佈署

on:
  workflow_dispatch:
    inputs:
      version:
        description: '要佈署的版本'
        required: true
      initial_traffic:
        description: '初始流量百分比'
        required: true
        default: '10'
      auto_promote:
        description: '是否自動晉升'
        type: boolean
        default: false

jobs:
  deploy_canary:
    name: 佈署金絲雀版本
    runs-on: ubuntu-latest

    outputs:
      canary_deployment: ${{ steps.deploy.outputs.deployment_name }}

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4

      - name: 設定 kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.28.0'

      - name: 配置 Kubernetes 上下文
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=$(pwd)/kubeconfig

      - name: 建立金絲雀 Deployment
        id: deploy
        run: |
          # 建立金絲雀版本的 Deployment
          # 使用不同的標籤來區分金絲雀和穩定版本

          CANARY_NAME="app-canary-${{ github.run_number }}"

          cat <<EOF | kubectl apply -f -
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: $CANARY_NAME
            labels:
              app: app
              version: canary
          spec:
            replicas: 1
            selector:
              matchLabels:
                app: app
                version: canary
            template:
              metadata:
                labels:
                  app: app
                  version: canary
              spec:
                containers:
                - name: app
                  image: ghcr.io/${{ github.repository }}:${{ github.event.inputs.version }}
                  ports:
                  - containerPort: 8080
          EOF

          echo "deployment_name=$CANARY_NAME" >> $GITHUB_OUTPUT
          echo "金絲雀 Deployment 已建立: $CANARY_NAME"

      - name: 配置流量分配
        run: |
          # 配置 Istio VirtualService 或類似工具來分配流量
          # 這裡以 Istio 為例

          CANARY_WEIGHT=${{ github.event.inputs.initial_traffic }}
          STABLE_WEIGHT=$((100 - CANARY_WEIGHT))

          cat <<EOF | kubectl apply -f -
          apiVersion: networking.istio.io/v1alpha3
          kind: VirtualService
          metadata:
            name: app
          spec:
            hosts:
            - app
            http:
            - route:
              - destination:
                  host: app
                  subset: stable
                weight: $STABLE_WEIGHT
              - destination:
                  host: app
                  subset: canary
                weight: $CANARY_WEIGHT
          EOF

          echo "流量分配: 穩定版本 $STABLE_WEIGHT%, 金絲雀 $CANARY_WEIGHT%"

  analyze_canary:
    name: 分析金絲雀指標
    needs: deploy_canary
    runs-on: ubuntu-latest

    outputs:
      analysis_result: ${{ steps.analyze.outputs.result }}

    steps:
      - name: 等待收集指標
        run: |
          # 等待一段時間讓金絲雀版本收集足夠的指標
          echo "等待 5 分鐘收集指標..."
          sleep 300

      - name: 分析金絲雀指標
        id: analyze
        run: |
          # 從監控系統獲取金絲雀和穩定版本的指標
          # 比較錯誤率、延遲等關鍵指標

          # 這裡模擬指標分析
          # 實際實作會從 Prometheus、Datadog 等獲取資料

          echo "分析金絲雀指標..."

          # 模擬指標
          CANARY_ERROR_RATE=0.5
          STABLE_ERROR_RATE=0.3
          THRESHOLD=2.0

          # 計算錯誤率比值
          # 如果金絲雀錯誤率超過穩定版本太多,則判定失敗
          RATIO=$(echo "$CANARY_ERROR_RATE / $STABLE_ERROR_RATE" | bc -l)

          if (( $(echo "$RATIO > $THRESHOLD" | bc -l) )); then
            echo "result=failed" >> $GITHUB_OUTPUT
            echo "金絲雀分析失敗:錯誤率過高"
          else
            echo "result=passed" >> $GITHUB_OUTPUT
            echo "金絲雀分析通過"
          fi

      - name: 產生分析報告
        run: |
          # 產生詳細的分析報告
          echo "## 金絲雀分析報告" >> $GITHUB_STEP_SUMMARY
          echo "- 分析時間: $(date)" >> $GITHUB_STEP_SUMMARY
          echo "- 分析結果: ${{ steps.analyze.outputs.result }}" >> $GITHUB_STEP_SUMMARY

  promote_or_rollback:
    name: 晉升或回復
    needs: [deploy_canary, analyze_canary]
    runs-on: ubuntu-latest

    steps:
      - name: 配置 Kubernetes 上下文
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=$(pwd)/kubeconfig

      - name: 晉升金絲雀版本
        if: needs.analyze_canary.outputs.analysis_result == 'passed' && github.event.inputs.auto_promote == 'true'
        run: |
          # 金絲雀測試通過,將其晉升為穩定版本
          # 逐步增加流量直到 100%

          echo "開始晉升金絲雀版本..."

          # 逐步增加流量
          for weight in 25 50 75 100; do
            stable_weight=$((100 - weight))

            cat <<EOF | kubectl apply -f -
          apiVersion: networking.istio.io/v1alpha3
          kind: VirtualService
          metadata:
            name: app
          spec:
            hosts:
            - app
            http:
            - route:
              - destination:
                  host: app
                  subset: stable
                weight: $stable_weight
              - destination:
                  host: app
                  subset: canary
                weight: $weight
          EOF

            echo "流量已調整: 金絲雀 $weight%"
            sleep 60  # 等待一分鐘觀察
          done

          echo "金絲雀版本已完全晉升"

      - name: 回復金絲雀佈署
        if: needs.analyze_canary.outputs.analysis_result == 'failed'
        run: |
          # 金絲雀測試失敗,回復佈署
          # 移除金絲雀版本並恢復 100% 流量到穩定版本

          echo "金絲雀測試失敗,執行回復..."

          # 將所有流量切換回穩定版本
          cat <<EOF | kubectl apply -f -
          apiVersion: networking.istio.io/v1alpha3
          kind: VirtualService
          metadata:
            name: app
          spec:
            hosts:
            - app
            http:
            - route:
              - destination:
                  host: app
                  subset: stable
                weight: 100
          EOF

          # 刪除金絲雀 Deployment
          kubectl delete deployment ${{ needs.deploy_canary.outputs.canary_deployment }}

          echo "已回復到穩定版本"

      - name: 發送通知
        run: |
          RESULT="${{ needs.analyze_canary.outputs.analysis_result }}"
          if [ "$RESULT" == "passed" ]; then
            echo "通知:金絲雀佈署成功"
          else
            echo "告警:金絲雀佈署失敗,已自動回復"
          fi

金絲雀佈署的關鍵在於指標分析。需要定義明確的成功標準,例如錯誤率、延遲、吞吐量等,並持續監控這些指標。如果金絲雀版本的指標明顯劣於穩定版本,則判定測試失敗並執行回復。流量管理通常需要服務網格(如 Istio)或智慧負載平衡器的支援。

DORA 指標與 DevOps 最佳實踐

DORA(DevOps Research and Assessment)指標是衡量軟體交付效能的四個關鍵指標,它們提供了客觀的方式來評估和改進 DevOps 實踐。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

' DORA 指標關係圖
package "DORA 四大關鍵指標" {
    rectangle "交付速度指標" as speed {
        card "佈署頻率" as df
        card "變更交付時間" as lt
    }

    rectangle "交付穩定性指標" as stability {
        card "變更失敗率" as cfr
        card "平均修復時間" as mttr
    }
}

rectangle "軟體交付效能" as performance

df --> performance
lt --> performance
cfr --> performance
mttr --> performance

note bottom of performance
  這四個指標共同決定了
  團隊的軟體交付效能等級:
  精英、高、中、低
end note

@enduml

四個 DORA 指標分別是:佈署頻率(Deployment Frequency)衡量團隊多頻繁地將變更佈署到生產環境;變更交付時間(Lead Time for Changes)衡量從程式碼提交到生產佈署所需的時間;變更失敗率(Change Failure Rate)衡量導致生產環境故障的佈署百分比;平均修復時間(Mean Time to Recover)衡量從故障中恢復所需的平均時間。

# .github/workflows/dora-metrics.yml
# 這個工作流程自動收集 DORA 指標

name: DORA 指標收集

on:
  # 每次佈署完成後收集指標
  workflow_run:
    workflows: ["藍綠佈署", "滾動佈署", "金絲雀佈署"]
    types: [completed]

  # 每日定時收集彙總指標
  schedule:
    - cron: '0 0 * * *'

jobs:
  collect_metrics:
    name: 收集 DORA 指標
    runs-on: ubuntu-latest

    steps:
      - name: 檢出程式碼
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 獲取完整歷史記錄

      - name: 計算佈署頻率
        id: deployment_frequency
        run: |
          # 計算過去 30 天的佈署次數
          # 使用 Git 標籤或佈署記錄來追蹤

          DAYS=30
          START_DATE=$(date -d "$DAYS days ago" +%Y-%m-%d)

          # 統計佈署次數(以標籤為例)
          DEPLOY_COUNT=$(git tag --sort=-creatordate | while read tag; do
            TAG_DATE=$(git log -1 --format=%ai "$tag" 2>/dev/null | cut -d' ' -f1)
            if [[ "$TAG_DATE" > "$START_DATE" ]]; then
              echo "$tag"
            fi
          done | wc -l)

          # 計算每日平均佈署次數
          DAILY_AVG=$(echo "scale=2; $DEPLOY_COUNT / $DAYS" | bc)

          echo "deployment_count=$DEPLOY_COUNT" >> $GITHUB_OUTPUT
          echo "daily_average=$DAILY_AVG" >> $GITHUB_OUTPUT

          echo "過去 $DAYS 天佈署次數: $DEPLOY_COUNT"
          echo "每日平均: $DAILY_AVG"

      - name: 計算變更交付時間
        id: lead_time
        run: |
          # 計算從提交到佈署的平均時間
          # 這需要追蹤每個佈署包含的提交

          # 獲取最近 10 次佈署的交付時間
          TOTAL_TIME=0
          COUNT=0

          for tag in $(git tag --sort=-creatordate | head -10); do
            # 獲取標籤建立時間
            TAG_TIME=$(git log -1 --format=%ct "$tag")

            # 獲取該標籤包含的最早提交時間
            # 這裡簡化為與前一個標籤之間的提交
            PREV_TAG=$(git describe --tags --abbrev=0 "$tag^" 2>/dev/null || echo "")

            if [ -n "$PREV_TAG" ]; then
              FIRST_COMMIT_TIME=$(git log --format=%ct "$PREV_TAG..$tag" | tail -1)
              if [ -n "$FIRST_COMMIT_TIME" ]; then
                LEAD_TIME=$((TAG_TIME - FIRST_COMMIT_TIME))
                TOTAL_TIME=$((TOTAL_TIME + LEAD_TIME))
                COUNT=$((COUNT + 1))
              fi
            fi
          done

          if [ $COUNT -gt 0 ]; then
            AVG_LEAD_TIME=$((TOTAL_TIME / COUNT))
            AVG_HOURS=$((AVG_LEAD_TIME / 3600))
          else
            AVG_HOURS=0
          fi

          echo "lead_time_hours=$AVG_HOURS" >> $GITHUB_OUTPUT
          echo "平均變更交付時間: $AVG_HOURS 小時"

      - name: 計算變更失敗率
        id: change_failure_rate
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # 計算導致生產問題的佈署百分比
          # 這需要追蹤佈署後的問題報告

          # 獲取過去 30 天的佈署數
          TOTAL_DEPLOYS=50  # 從之前的步驟獲取

          # 獲取同期間的生產問題數(以 Issue 為例)
          FAILURES=$(gh issue list \
            --label "production-incident" \
            --state closed \
            --json createdAt \
            --jq '[.[] | select(.createdAt > (now - 30*24*3600 | todate))] | length')

          if [ $TOTAL_DEPLOYS -gt 0 ]; then
            FAILURE_RATE=$(echo "scale=2; $FAILURES * 100 / $TOTAL_DEPLOYS" | bc)
          else
            FAILURE_RATE=0
          fi

          echo "failure_rate=$FAILURE_RATE" >> $GITHUB_OUTPUT
          echo "變更失敗率: $FAILURE_RATE%"

      - name: 計算平均修復時間
        id: mttr
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # 計算從問題發現到解決的平均時間
          # 使用 Issue 的開啟和關閉時間

          # 獲取已關閉的生產問題
          ISSUES=$(gh issue list \
            --label "production-incident" \
            --state closed \
            --json createdAt,closedAt \
            --jq '.[] | "\(.createdAt) \(.closedAt)"')

          TOTAL_TIME=0
          COUNT=0

          while read -r created closed; do
            if [ -n "$created" ] && [ -n "$closed" ]; then
              CREATED_TS=$(date -d "$created" +%s)
              CLOSED_TS=$(date -d "$closed" +%s)
              DURATION=$((CLOSED_TS - CREATED_TS))
              TOTAL_TIME=$((TOTAL_TIME + DURATION))
              COUNT=$((COUNT + 1))
            fi
          done <<< "$ISSUES"

          if [ $COUNT -gt 0 ]; then
            AVG_MTTR=$((TOTAL_TIME / COUNT))
            AVG_HOURS=$((AVG_MTTR / 3600))
          else
            AVG_HOURS=0
          fi

          echo "mttr_hours=$AVG_HOURS" >> $GITHUB_OUTPUT
          echo "平均修復時間: $AVG_HOURS 小時"

      - name: 產生指標報告
        run: |
          # 產生綜合指標報告

          cat >> $GITHUB_STEP_SUMMARY << EOF
          ## DORA 指標報告

          ### 交付速度
          - **佈署頻率**: ${{ steps.deployment_frequency.outputs.deployment_count }} 次/30天(平均 ${{ steps.deployment_frequency.outputs.daily_average }} 次/天)
          - **變更交付時間**: ${{ steps.lead_time.outputs.lead_time_hours }} 小時

          ### 交付穩定性
          - **變更失敗率**: ${{ steps.change_failure_rate.outputs.failure_rate }}%
          - **平均修復時間**: ${{ steps.mttr.outputs.mttr_hours }} 小時

          ### 效能等級評估

          根據 DORA 研究的基準:
          - 精英團隊:每天多次佈署,交付時間 < 1小時,失敗率 < 15%,MTTR < 1小時
          - 高效能團隊:每週多次佈署,交付時間 1天-1週,失敗率 0-15%,MTTR < 1天
          - 中等團隊:每週-每月佈署,交付時間 1週-1月,失敗率 0-15%,MTTR 1天-1週
          - 低效能團隊:每月-每半年佈署,交付時間 1月-6月,失敗率 46-60%,MTTR 1週-1月

          EOF

      - name: 發送指標到監控系統
        run: |
          # 將指標發送到 Prometheus、Datadog 等監控系統
          # 這樣可以在儀表板上追蹤趨勢

          echo "發送指標到監控系統..."
          # 實際實作會使用 curl 或 SDK 發送指標

追蹤 DORA 指標可以幫助團隊了解自己的表現並找到改進的方向。重要的是要將這些指標與團隊的具體情況結合起來解讀,而不是盲目追求數字。例如,提高佈署頻率如果沒有配合足夠的自動化測試,可能會導致變更失敗率上升。這些指標應該作為持續改進的參考,而不是評判團隊的工具。

總結

本文深入探討了 GitHub Actions 的各個面向,從基礎的工作流程設計到進階的佈署策略實作。GitHub Actions 的強大之處在於其與 GitHub 平台的深度整合和豐富的生態系統,這讓開發團隊能夠快速建立和維護 CI/CD 流水線。

在工作流程設計方面,我們介紹了事件觸發機制、作業與步驟的配置、Secrets 管理和除錯技巧。這些基礎知識是建立穩定可靠的自動化流程的前提。正確的事件配置可以避免不必要的執行,節省資源並提高效率。妥善的 Secrets 管理確保了安全性,而有效的除錯技巧則加速了問題的定位和解決。

在佈署策略方面,我們詳細介紹了藍綠佈署、滾動佈署和金絲雀佈署三種主要策略。每種策略都有其優缺點和適用場景。藍綠佈署提供了零停機和快速回復的能力,但需要雙倍的資源。滾動佈署不需要額外資源,但更新過程較慢且需要確保新舊版本的相容性。金絲雀佈署提供了最精細的風險控制,但需要更複雜的流量管理和指標分析機制。

最後,我們介紹了 DORA 指標作為衡量 DevOps 效能的客觀標準。這四個指標(佈署頻率、變更交付時間、變更失敗率、平均修復時間)提供了全面的視角來評估軟體交付效能。透過持續追蹤這些指標,團隊可以識別瓶頸、設定改進目標,並驗證改進措施的效果。

成功的 CI/CD 實踐不僅僅是工具和技術的問題,更需要團隊文化的支持和持續改進的心態。希望本文能夠幫助讀者更好地理解和應用 GitHub Actions,建立高效可靠的軟體交付流程。