Skip to content
On this page

GitHub Actions最佳实践

GitHub Actions最佳实践涵盖了工作流设计、安全、性能优化和维护等方面的最佳做法,帮助您构建高效、安全、可维护的CI/CD管道。

安全最佳实践

1. 最小权限原则

始终为工作流提供最小必需的权限:

yaml
# .github/workflows/secure-workflow.yml
name: Secure Workflow

on: [push, pull_request]

# 明确声明所需权限
permissions:
  contents: read  # 只读访问仓库内容
  packages: write # 写入包权限(仅在需要时)
  id-token: write # OIDC令牌权限(仅在需要时)

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          persist-credentials: false  # 防止凭据泄露
      
      - name: Build application
        run: npm ci && npm run build

2. 保护敏感信息

yaml
jobs:
  secure-deployment:
    runs-on: ubuntu-latest
    steps:
      - name: Use secrets safely
        run: |
          # 密钥不会在日志中显示
          curl -X POST \
            -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d '{"message": "Deploy successful"}' \
            ${{ secrets.API_ENDPOINT }}
        env:
          # 敏感信息通过环境变量传递
          API_TOKEN: ${{ secrets.API_TOKEN }}
          API_ENDPOINT: ${{ secrets.API_ENDPOINT }}
      
      - name: Avoid logging sensitive data
        run: |
          # 避免在日志中直接打印敏感信息
          echo "Processing deployment..."  # 好的做法
          # echo "Token: $TOKEN"          # 避免这样做

3. 验证外部Action

yaml
jobs:
  safe-actions:
    runs-on: ubuntu-latest
    steps:
      # 使用固定版本的Action(推荐)
      - name: Checkout with fixed version
        uses: actions/checkout@a5ac7e51c2d5a6a4e9c19e32d7e8ea8dd9d5e459  # v4.1.7的完整SHA
      
      # 或者使用主要版本标签(次选)
      - name: Setup Node.js
        uses: actions/setup-node@v4
      
      # 避免使用main分支(不安全)
      # - uses: some-user/some-action@main  # 不推荐

性能优化最佳实践

1. 合理使用缓存

yaml
jobs:
  optimized-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # 使用actions/setup-*内置缓存
      - name: Setup Node.js with cache
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          cache-dependency-path: package-lock.json
      
      # 或使用actions/cache手动缓存
      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.npm
            ~/.cache/Cypress
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build with cache
        run: npm run build

2. 并行执行优化

yaml
jobs:
  parallel-tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        # 控制矩阵大小
        node-version: [16, 18, 20]
        os: [ubuntu-latest]
      # 限制并行度以节省资源
      max-parallel: 2
      fail-fast: false  # 收集所有测试结果
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

3. 资源优化

yaml
jobs:
  resource-efficient:
    runs-on: ubuntu-latest
    timeout-minutes: 30  # 设置合理的超时时间
    steps:
      - name: Limit resource usage
        run: |
          # 在资源受限的环境中运行
          npm run build -- --max-old-space-size=4096  # 限制内存使用
        timeout-minutes: 10  # 设置步骤超时
        
      - name: Clean up resources
        if: always()  # 无论成功或失败都执行清理
        run: |
          # 清理临时文件
          rm -rf /tmp/build-cache
          docker system prune -f  # 清理Docker资源

工作流设计最佳实践

1. 作业依赖管理

yaml
jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      build-id: ${{ steps.generate-id.outputs.id }}
    steps:
      - name: Generate build ID
        id: generate-id
        run: |
          BUILD_ID=$(date +%s)-${{ github.run_number }}
          echo "id=$BUILD_ID" >> $GITHUB_OUTPUT
  
  build:
    needs: setup
    runs-on: ubuntu-latest
    outputs:
      artifact-name: ${{ steps.package.outputs.name }}
    steps:
      - uses: actions/checkout@v4
      - name: Build with ID
        run: npm run build -- --build-id ${{ needs.setup.outputs.build-id }}
      - name: Package artifact
        id: package
        run: |
          ARTIFACT_NAME="app-${{ needs.setup.outputs.build-id }}"
          echo "name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
  
  test:
    needs: [setup, build]
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
    steps:
      - uses: actions/checkout@v4
      - name: Download artifact
        run: echo "Using artifact from build job"
  
  deploy:
    needs: [setup, build, test]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - name: Deploy with build ID
        run: echo "Deploying build ${{ needs.setup.outputs.build-id }}"

2. 条件执行

yaml
jobs:
  conditional-execution:
    runs-on: ubuntu-latest
    steps:
      - name: Skip on certain conditions
        if: |
          github.event_name == 'push' &&
          github.ref == 'refs/heads/main' &&
          !contains(github.event.head_commit.message, '[skip ci]')
        run: echo "Running on main branch push without skip ci"
      
      - name: Environment-specific execution
        if: github.ref == 'refs/heads/main'
        run: echo "Running on production environment"
        env:
          NODE_ENV: production
          API_URL: ${{ secrets.PROD_API_URL }}
      
      - name: Pull request specific
        if: github.event_name == 'pull_request'
        run: echo "Running on pull request"
        env:
          NODE_ENV: development
          API_URL: ${{ secrets.DEV_API_URL }}

错误处理和监控

1. 容错设计

yaml
jobs:
  fault-tolerant:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false  # 继续运行其他矩阵作业
    steps:
      - name: Retry on failure
        run: |
          # 重试机制
          for i in {1..3}; do
            if npm run test; then
              echo "Tests passed on attempt $i"
              break
            elif [ $i -eq 3 ]; then
              echo "Tests failed after 3 attempts"
              exit 1
            else
              echo "Attempt $i failed, retrying in 10 seconds..."
              sleep 10
            fi
          done
      
      - name: Continue on error
        continue-on-error: true  # 即使失败也继续执行后续步骤
        run: |
          # 可能失败但不影响整体流程的步骤
          npm run lint || echo "Lint failed but continuing..."
      
      - name: Cleanup always runs
        if: always()  # 无论前面步骤成功或失败都执行
        run: |
          echo "Performing cleanup..."
          # 清理资源

2. 监控和通知

yaml
jobs:
  monitored-job:
    runs-on: ubuntu-latest
    outputs:
      status: ${{ steps.monitor.outputs.status }}
    steps:
      - name: Start monitoring
        id: start-time
        run: echo "start_time=$(date -u +%s)" >> $GITHUB_OUTPUT
      
      - name: Main operation
        id: main-op
        run: |
          # 主要操作
          npm run deploy
          echo "exit_code=$?" >> $GITHUB_OUTPUT
      
      - name: Monitor and log
        id: monitor
        run: |
          END_TIME=$(date -u +%s)
          DURATION=$((END_TIME - ${{ steps.start-time.outputs.start_time }}))
          EXIT_CODE=${{ steps.main-op.outputs.exit_code }}
          
          if [ $EXIT_CODE -eq 0 ]; then
            STATUS="success"
          else
            STATUS="failure"
          fi
          
          echo "duration=$DURATION" >> $GITHUB_OUTPUT
          echo "status=$STATUS" >> $GITHUB_OUTPUT
          
          # 记录到外部监控系统
          curl -X POST https://monitoring.example.com/api/logs \
            -H "Content-Type: application/json" \
            -d "{
              \"job\": \"${{ github.job }}\",
              \"run_id\": \"${{ github.run_id }}\",
              \"status\": \"$STATUS\",
              \"duration\": $DURATION,
              \"repository\": \"${{ github.repository }}\"
            }"
  
  notifications:
    needs: monitored-job
    runs-on: ubuntu-latest
    if: always()  # 总是运行以发送通知
    steps:
      - name: Send notification on failure
        if: needs.monitored-job.outputs.status == 'failure'
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -H "Content-Type: application/json" \
            -d '{
              "text": "❌ Deployment failed in ${{ github.repository }}",
              "channel": "#deployments"
            }'
      
      - name: Send notification on success
        if: needs.monitored-job.outputs.status == 'success'
        run: |
          curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
            -H "Content-Type: application/json" \
            -d '{
              "text": "✅ Deployment succeeded in ${{ github.repository }}",
              "channel": "#deployments"
            }'

代码组织最佳实践

1. 工作流分离

yaml
# .github/workflows/ci.yml
name: Continuous Integration

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      - run: npm ci
      - run: npm test
      - run: npm run lint

---
# .github/workflows/cd.yml
name: Continuous Deployment

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - name: Deploy application
        run: echo "Deploying to production"

2. 可重用组件

yaml
# 使用复合Action创建可重用组件
# reusable-build/action.yml
name: 'Reusable Build Action'
description: 'Common build steps'

inputs:
  node-version:
    description: 'Node.js version'
    required: true
    default: '18'
  build-target:
    description: 'Build target'
    required: false
    default: 'dist'

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'
    
    - name: Install dependencies
      shell: bash
      run: npm ci
    
    - name: Build project
      shell: bash
      run: npm run build -- --target ${{ inputs.build-target }}
    
    - name: Run tests
      shell: bash
      run: npm test

3. 环境变量管理

yaml
# 在工作流级别定义公共环境变量
env:
  NODE_VERSION: '18'
  BUILD_PATH: './dist'
  CI: 'true'

jobs:
  build:
    runs-on: ubuntu-latest
    # 作业级别的环境变量
    env:
      NODE_ENV: 'production'
    steps:
      - name: Use environment variables
        run: |
          echo "Node version: $NODE_VERSION"  # 来自工作流级别
          echo "Build path: $BUILD_PATH"      # 来自工作流级别
          echo "Node environment: $NODE_ENV"  # 来自作业级别
        # 步骤级别的环境变量
        env:
          STEP_VAR: 'step-specific-value'

审计和合规

1. 审计日志

yaml
jobs:
  audited-workflow:
    runs-on: ubuntu-latest
    steps:
      - name: Log workflow execution
        run: |
          echo "Workflow: ${{ github.workflow }}"
          echo "Run ID: ${{ github.run_id }}"
          echo "Actor: ${{ github.actor }}"
          echo "Repository: ${{ github.repository }}"
          echo "Ref: ${{ github.ref }}"
          echo "SHA: ${{ github.sha }}"
          echo "Timestamp: $(date -u)"
          
          # 发送到审计系统
          curl -X POST ${{ secrets.AUDIT_LOG_URL }} \
            -H "Authorization: Bearer ${{ secrets.AUDIT_TOKEN }}" \
            -H "Content-Type: application/json" \
            -d "{
              \"event\": \"workflow_execution\",
              \"workflow\": \"${{ github.workflow }}\",
              \"run_id\": \"${{ github.run_id }}\",
              \"actor\": \"${{ github.actor }}\",
              \"repository\": \"${{ github.repository }}\",
              \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
            }"

2. 合规检查

yaml
jobs:
  compliance-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Check for secrets in code
        run: |
          # 扫描代码中是否包含密钥字面量
          if git grep -l "password\|secret\|token\|key" -- "*.yml" "*.yaml" "*.json" "*.js" "*.py" "*.ts"; then
            echo "Potential secrets found in code. Please review."
            exit 1
          fi
      
      - name: License compliance check
        run: |
          # 检查依赖项许可证合规性
          npm run license-check

维护和文档

1. 工作流文档

yaml
# .github/workflows/documentation.yml
name: Documentation and Maintenance

on:
  schedule:
    - cron: '0 2 * * 1'  # 每周一凌晨2点
  workflow_dispatch:

jobs:
  maintain-workflows:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
      
      - name: Validate workflow files
        run: |
          # 验证工作流文件语法
          # 可以使用第三方工具或自定义脚本
          echo "Validating workflow files..."
      
      - name: Update dependencies
        run: |
          # 更新Action依赖到最新安全版本
          echo "Updating Action dependencies..."
      
      - name: Generate workflow documentation
        run: |
          # 生成工作流文档
          echo "# Workflow Documentation" > WORKFLOW_DOCS.md
          echo "Generated on $(date)" >> WORKFLOW_DOCS.md

2. 版本管理

yaml
# 在README.md中记录工作流信息
# README.md
"""
## CI/CD Pipelines

### Workflows

- **Continuous Integration** (`.github/workflows/ci.yml`): Runs on every push and PR
  - Tests: Unit, integration, and end-to-end tests
  - Linting: Code style validation
  - Security: Dependency vulnerability scanning

- **Continuous Deployment** (`.github/workflows/cd.yml`): Runs on main branch pushes
  - Builds production-ready artifacts
  - Deploys to staging environment
  - Runs post-deployment tests
"""

实际应用示例

完整的生产级工作流

yaml
name: Production CI/CD Pipeline

on:
  push:
    branches: [main, develop]
    tags: ['v*']
  pull_request:
    branches: [main, develop]

# 限制并发执行
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  NODE_VERSION: '18'
  APP_NAME: 'my-app'

jobs:
  # 安全扫描(仅在非PR时运行)
  security-scan:
    if: github.event_name != 'pull_request'
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - name: Run security scan
        uses: github/super-linter@v4
        env:
          VALIDATE_ALL_CODEBASE: false
          DEFAULT_BRANCH: main
  
  # 构建和测试
  build-and-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm run test:ci
      
      - name: Upload coverage
        uses: actions/upload-artifact@v4
        with:
          name: coverage-${{ matrix.node-version }}
          path: coverage/
        if: matrix.node-version == '18'  # 只上传一个版本的覆盖率
  
  # 构建生产版本
  build-production:
    needs: [security-scan, build-and-test]
    runs-on: ubuntu-latest
    outputs:
      build-id: ${{ steps.build-info.outputs.id }}
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      - name: Build production
        run: npm run build:prod
      
      - name: Generate build info
        id: build-info
        run: |
          BUILD_ID="${{ github.sha }}-${{ github.run_number }}"
          echo "id=$BUILD_ID" >> $GITHUB_OUTPUT
      
      - name: Upload production build
        uses: actions/upload-artifact@v4
        with:
          name: production-build
          path: dist/
  
  # 部署到预发布环境
  deploy-staging:
    needs: build-production
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment: staging
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: production-build
          path: dist/
      
      - name: Deploy to staging
        run: echo "Deploying build ${{ needs.build-production.outputs.build-id }} to staging"
        env:
          DEPLOY_TOKEN: ${{ secrets.STAGING_DEPLOY_TOKEN }}
  
  # 部署到生产环境
  deploy-production:
    needs: [build-production, security-scan]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/')
    environment: production
    permissions:
      id-token: write
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: production-build
          path: dist/
      
      - name: Deploy to production
        run: echo "Deploying build ${{ needs.build-production.outputs.build-id }} to production"
        env:
          DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }}

通过遵循这些最佳实践,您可以构建安全、高效、可维护的GitHub Actions工作流,提高软件交付的质量和效率。