Skip to content
On this page

GitHub Actions作业与步骤

GitHub Actions工作流由作业(Jobs)和步骤(Steps)组成。作业是运行在运行器上的独立任务单元,而步骤是作业中的具体执行指令。理解作业和步骤的配置与管理是构建高效工作流的关键。

作业(Jobs)详解

基本作业结构

yaml
# .github/workflows/basic-jobs.yml
name: Basic Jobs Example

on: [push]

jobs:
  # 作业1:构建
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: Install dependencies
        run: npm ci
      - name: Build project
        run: npm run build
  
  # 作业2:测试
  test:
    runs-on: ubuntu-latest
    needs: build  # 依赖build作业
    steps:
      - name: Run tests
        run: npm test

runs-on配置

指定作业运行的环境:

yaml
jobs:
  # 使用GitHub托管的运行器
  github-hosted:
    runs-on: ubuntu-latest    # Ubuntu最新版
    # runs-on: windows-latest # Windows最新版
    # runs-on: macos-latest   # macOS最新版
    steps:
      - run: echo "Running on GitHub hosted runner"
  
  # 使用自托管运行器
  self-hosted:
    runs-on: [self-hosted, linux, x64]
    steps:
      - run: echo "Running on self-hosted runner"
  
  # 使用表达式动态选择
  dynamic-runner:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    steps:
      - run: echo "Running on ${{ runner.os }}"

作业依赖(needs)

定义作业间的依赖关系:

yaml
jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - name: Setup environment
        run: echo "Setup completed"
  
  build:
    needs: setup  # 等待setup作业完成
    runs-on: ubuntu-latest
    steps:
      - name: Build application
        run: echo "Build completed"
  
  test:
    needs: build  # 等待build作业完成
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - name: Test with Node ${{ matrix.node-version }}
        run: npm test
  
  deploy:
    needs: [setup, build, test]  # 等待多个作业完成
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'  # 仅在main分支执行
    steps:
      - name: Deploy to production
        run: echo "Deploying to production"

作业条件执行(if)

基于条件控制作业执行:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Always runs"
  
  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'  # 仅在develop分支执行
    steps:
      - run: echo "Deploying to staging"
  
  deploy-production:
    needs: build
    runs-on: ubuntu-latest
    if: |
      github.ref == 'refs/heads/main' &&     # 在main分支
      github.event_name == 'push' &&         # 推送事件
      !contains(github.event.head_commit.message, '[skip deploy]')  # 不包含跳过部署标记
    steps:
      - run: echo "Deploying to production"
  
  debug-mode:
    runs-on: ubuntu-latest
    if: contains(github.event.head_commit.message, 'debug')
    steps:
      - run: echo "Debug mode enabled"

作业输出(outputs)

作业可以输出值供其他作业使用:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      build-number: ${{ steps.version.outputs.build-number }}
      artifact-name: ${{ steps.package.outputs.name }}
    steps:
      - name: Generate build number
        id: version
        run: |
          BUILD_NUM=$(date +%s)
          echo "build-number=$BUILD_NUM" >> $GITHUB_OUTPUT
        shell: bash
      
      - name: Package application
        id: package
        run: |
          PKG_NAME="app-${{ steps.version.outputs.build-number }}"
          echo "name=$PKG_NAME" >> $GITHUB_OUTPUT
  
  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Test build number
        run: |
          echo "Testing build ${{ needs.build.outputs.build-number }}"
          echo "Artifact name: ${{ needs.build.outputs.artifact-name }}"
  
  deploy:
    needs: [build, test]
    runs-on: ubuntu-latest
    steps:
      - name: Deploy with build info
        run: |
          echo "Deploying build ${{ needs.build.outputs.build-number }}"
          echo "Using artifact: ${{ needs.build.outputs.artifact-name }}"

步骤(Steps)详解

步骤基本结构

yaml
jobs:
  example:
    runs-on: ubuntu-latest
    steps:
      # 使用action的步骤
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 获取完整历史
      
      # 运行命令的步骤
      - name: Install dependencies
        run: npm install
      
      # 条件执行的步骤
      - name: Run production build
        if: github.ref == 'refs/heads/main'
        run: npm run build:prod

使用action(uses)

使用预定义的action:

yaml
steps:
  # 使用官方action
  - name: Checkout code
    uses: actions/checkout@v4
  
  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '18'
      cache: 'npm'
      cache-dependency-path: '**/package-lock.json'
  
  - name: Setup Java
    uses: actions/setup-java@v4
    with:
      distribution: 'temurin'
      java-version: '17'
      cache: 'maven'
  
  # 使用仓库中的action
  - name: Run local action
    uses: ./path/to/local/action
  
  # 使用Docker Hub的action
  - name: Run Docker action
    uses: docker://alpine:latest
    with:
      args: /bin/sh -c 'echo "Hello from Docker"'

运行命令(run)

执行shell命令:

yaml
steps:
  # 简单命令
  - name: Print current directory
    run: pwd
  
  # 多行命令
  - name: Multi-line command
    run: |
      echo "First command"
      echo "Second command"
      ls -la
  
  # 使用特定shell
  - name: PowerShell command
    run: Write-Host "Hello from PowerShell"
    shell: pwsh
  
  # 使用bash
  - name: Bash command
    run: |
      if [ -f "package.json" ]; then
        echo "package.json exists"
      fi
    shell: bash
  
  # 条件命令
  - name: Conditional command
    run: echo "This runs conditionally"
    if: github.event_name == 'push'

步骤参数详解

yaml
steps:
  - name: Example step with all parameters
    id: example-step
    uses: actions/checkout@v4
    with:
      repository: ${{ github.repository }}
      ref: ${{ github.ref }}
      token: ${{ secrets.GITHUB_TOKEN }}
    env:
      NODE_ENV: production
      DEBUG: true
    continue-on-error: false  # 默认为false,设为true时即使失败也继续
    timeout-minutes: 10       # 设置超时时间
    if: always()             # 使用表达式控制执行条件
    run: echo "This step demonstrates all parameters"

矩阵构建(Matrix)

基础矩阵配置

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
        os: [ubuntu-latest, windows-latest, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm test
      - name: Report OS and Node version
        run: |
          echo "OS: ${{ matrix.os }}"
          echo "Node: ${{ matrix.node-version }}"

高级矩阵配置

yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        node-version: [16, 18, 20]
        os: [ubuntu-latest, windows-latest, macos-latest]
        include:  # 包含特定组合
          - node-version: 16
            experimental: true
          - node-version: 20
            experimental: false
        exclude:  # 排除特定组合
          - os: windows-latest
            node-version: 16
          - os: macos-latest
            node-version: 16
      fail-fast: false  # 一个矩阵作业失败时继续其他作业
      max-parallel: 2   # 最大并行执行的矩阵作业数
    steps:
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test
      
      - name: Conditional experimental test
        if: matrix.experimental == true
        run: npm run experimental-test

环境和上下文

全局环境变量

yaml
env:
  NODE_VERSION: '18.x'
  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"
          echo "CI: $CI"

步骤级环境变量

yaml
steps:
  - name: Set environment for step
    run: |
      echo "API_URL is $API_URL"
      echo "BUILD_NUMBER is $BUILD_NUMBER"
    env:
      API_URL: ${{ secrets.API_URL }}
      BUILD_NUMBER: ${{ github.run_number }}
      TIMESTAMP: ${{ github.event.repository.updated_at }}

上下文使用

yaml
steps:
  - name: Use GitHub context
    run: |
      echo "Repository: ${{ github.repository }}"
      echo "Actor: ${{ github.actor }}"
      echo "SHA: ${{ github.sha }}"
      echo "Ref: ${{ github.ref }}"
      echo "Branch: ${{ github.ref_name }}"
      echo "Event: ${{ github.event_name }}"
      echo "Run number: ${{ github.run_number }}"
  
  - name: Use runner context
    run: |
      echo "OS: ${{ runner.os }}"
      echo "Arch: ${{ runner.arch }}"
      echo "Temp: ${{ runner.temp }}"
      echo "Tool cache: ${{ runner.tool_cache }}"
  
  - name: Use job context
    run: |
      echo "Job status: ${{ job.status }}"

高级作业配置

并发控制

yaml
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy with concurrency control
        run: |
          echo "Only one deployment at a time"
          echo "Cancelling in-progress deployments"

环境配置

yaml
jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging  # 使用环境变量和保护规则
    steps:
      - run: echo "Deploying to staging"
  
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com  # 部署URL
    steps:
      - run: echo "Deploying to production"

权限配置

yaml
jobs:
  security-scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read          # 读取内容权限
      packages: write         # 写入包权限
      id-token: write         # OIDC令牌权限
      security-events: write  # 安全事件权限
    steps:
      - uses: actions/checkout@v4
      - name: Run security scan
        run: echo "Scanning with appropriate permissions"

实用作业模式

构建-测试-部署流水线

yaml
name: CI/CD Pipeline

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

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      build-id: ${{ steps.build.outputs.id }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      - name: Install dependencies
        run: npm ci
      - name: Build application
        id: build
        run: |
          npm run build
          BUILD_ID=$(date +%s)
          echo "id=$BUILD_ID" >> $GITHUB_OUTPUT
  
  test:
    needs: build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18'
      - run: npm ci
      - run: npm test
      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.os }}
          path: test-results/
  
  security-scan:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Security scan
        run: |
          npm audit --audit-level moderate
          # 其他安全扫描工具
  
  deploy-staging:
    needs: [build, test, security-scan]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment: staging
    steps:
      - name: Deploy to staging
        run: echo "Deploying build ${{ needs.build.outputs.build-id }} to staging"
  
  deploy-production:
    needs: [build, test, security-scan]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment: production
    permissions:
      id-token: write
    steps:
      - name: Deploy to production
        run: echo "Deploying build ${{ needs.build.outputs.build-id }} to production"

条件作业执行

yaml
jobs:
  check-changes:
    runs-on: ubuntu-latest
    outputs:
      frontend-changed: ${{ steps.changes.outputs.frontend }}
      backend-changed: ${{ steps.changes.outputs.backend }}
    steps:
      - uses: actions/checkout@v4
      - name: Check what changed
        id: changes
        run: |
          CHANGES=$(git diff --name-only HEAD^ HEAD)
          if echo "$CHANGES" | grep -qE '^(src/frontend/|frontend/)'; then
            echo "frontend=true" >> $GITHUB_OUTPUT
          else
            echo "frontend=false" >> $GITHUB_OUTPUT
          fi
          if echo "$CHANGES" | grep -qE '^(src/backend/|backend/)'; then
            echo "backend=true" >> $GITHUB_OUTPUT
          else
            echo "backend=false" >> $GITHUB_OUTPUT
          fi
  
  frontend-tests:
    needs: check-changes
    runs-on: ubuntu-latest
    if: needs.check-changes.outputs.frontend-changed == 'true'
    steps:
      - run: echo "Running frontend tests"
  
  backend-tests:
    needs: check-changes
    runs-on: ubuntu-latest
    if: needs.check-changes.outputs.backend-changed == 'true'
    steps:
      - run: echo "Running backend tests"

错误处理和重试

错误继续执行

yaml
steps:
  - name: Lint code
    run: npm run lint
    continue-on-error: true  # 即使失败也继续执行后续步骤
  
  - name: Run tests
    run: npm test
    continue-on-error: false  # 默认值,失败时停止

超时设置

yaml
steps:
  - name: Long running operation
    run: |
      # 模拟长时间运行的命令
      sleep 120
    timeout-minutes: 5  # 5分钟后超时

重试逻辑

yaml
steps:
  - name: Retry on failure
    run: |
      # 尝试多次执行命令
      for i in {1..3}; do
        if npm run deploy; then
          echo "Deployment successful"
          exit 0
        else
          echo "Deployment attempt $i failed"
          if [ $i -eq 3 ]; then
            echo "All attempts failed"
            exit 1
          fi
          sleep 10
        fi
      done
    timeout-minutes: 10

最佳实践

1. 作业命名规范

yaml
jobs:
  # 好的命名
  build-application:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building application"
  
  run-unit-tests:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running unit tests"
  
  # 避免模糊命名
  # job1, job2, task1等

2. 合理的依赖关系

yaml
# 明确的依赖关系
jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Setup"
  
  build:
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - run: echo "Build"
  
  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Test"

3. 矩阵使用优化

yaml
# 限制矩阵大小以控制成本和时间
strategy:
  matrix:
    node-version: [16, 18]  # 限制版本数量
    os: [ubuntu-latest]     # 可能只在Linux上测试
  max-parallel: 2           # 限制并行度
  fail-fast: false          # 允许收集所有测试结果

通过合理配置作业和步骤,可以构建高效、可靠、可维护的GitHub Actions工作流,满足各种自动化需求。