Appearance
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工作流,满足各种自动化需求。