跳至主要內容

GitHub Actions CI/CD 自動化:從零建構完整部署流水線

11 分鐘閱讀 1,150 字

GitHub Actions CI/CD 自動化:從零建構完整部署流水線

GitHub Actions 讓 CI/CD 流水線的設定變得前所未有地簡單,直接在 GitHub 倉庫中定義工作流程,不需要額外的 CI 服務。本文從基礎概念到完整的部署流水線,帶你全面了解 GitHub Actions。

核心概念

  • Workflow:由 .github/workflows/*.yml 定義,由事件觸發
  • Job:工作流程中的獨立執行單元,可並行或依序執行
  • Step:Job 中的具體步驟,可執行命令或使用 Action
  • Action:可複用的步驟單元,可從 Marketplace 引用或自訂

第一個 Workflow:PR 自動化測試

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        node-version: [18, 20, 22]  # 多版本矩陣測試
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Run tests
        run: npm test -- --coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

快取策略

正確的快取設定能大幅縮短 CI 時間:

- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

使用 actions/setup-node@v4cache 選項更簡潔(如上方範例)。

完整的 CI/CD 流水線

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

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

jobs:
  # Job 1:測試
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test

  # Job 2:建構 Docker 映像(依賴 test 通過)
  build:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=sha-
            type=ref,event=branch
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # Job 3:部署到正式環境
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.example.com
    
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            cd /opt/myapp
            docker pull ${{ needs.build.outputs.image-tag }}
            docker compose up -d --no-deps app
            docker image prune -f

環境與機密管理

# 使用 GitHub Environments 進行分階段部署
deploy-staging:
  environment: staging  # 需要在 GitHub Settings 中設定
  steps:
    - run: echo "部署到測試環境"

deploy-production:
  needs: deploy-staging
  environment:
    name: production
    url: https://app.example.com
  # 可設定必須手動審核才能繼續
  steps:
    - run: echo "部署到正式環境"

可複用工作流程

# .github/workflows/reusable-deploy.yml
name: Reusable Deploy

on:
  workflow_call:  # 宣告為可被呼叫的工作流程
    inputs:
      environment:
        required: true
        type: string
    secrets:
      deploy-key:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to ${{ inputs.environment }}
        run: echo "Deploying to ${{ inputs.environment }}"
# .github/workflows/main.yml
jobs:
  deploy-prod:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
    secrets:
      deploy-key: ${{ secrets.PROD_DEPLOY_KEY }}

自動化 PR 標籤

# .github/workflows/labeler.yml
name: PR Labeler

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/labeler@v5
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}

定時任務

on:
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨 2 點(UTC)

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - name: Clean up old Docker images
        run: |
          # 清理超過 30 天的舊映像
          echo "Running scheduled cleanup"

費用優化技巧

  1. 使用快取actions/cache 避免重複下載依賴
  2. 條件執行if: github.event_name == 'push' 跳過不需要的 Job
  3. 並行 Job:沒有依賴關係的 Job 設計成並行
  4. self-hosted runner:大量使用時考慮自建 Runner 節省費用
# 只在特定路徑變更時執行
on:
  push:
    paths:
      - 'src/**'
      - 'package.json'
      - '!**.md'  # 排除文件變更

總結

GitHub Actions 的宣告式 YAML 設定、豐富的 Marketplace Actions 和與 GitHub 的深度整合,讓它成為中小型專案的首選 CI/CD 平台。關鍵是設計好 Job 的依賴關係、善用快取和 Environment 功能,以及將常用邏輯抽取為可複用工作流程。

分享這篇文章