GitHub Actions 基础:workflow、job 和 step

1. 概述

GitHub Actions 是 GitHub 内置的 CI/CD 工具,帮助你自动化构建、测试、部署,无需单独的 CI 服务器。本文解释三个核心概念:workflow、job 和 step,并附带一个使用 Maven 的 Spring Boot 项目实例。

读完本文后,你将能够编写一个在每次推送代码时自动运行测试的 workflow 文件,理解 job 并行或顺序运行的方式,以及当 workflow 失败时如何调试。

2. Workflow 是什么?

Workflow 是一个 YAML 文件,位于仓库的 .github/workflows/ 目录中。每个 workflow 定义何时运行(trigger)以及做什么(jobs)。

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

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Build with Maven
        run: mvn -B package

解释:

  • 文件开头的 name 是在 GitHub 的 Actions 标签页上显示的名称。
  • on 声明触发器:这里 workflow 在向 main 分支进行 push 或 pull_request 时运行。
  • jobs.build 是一个名为 build 的单一 job,运行在 ubuntu-latest 虚拟机上。

注意: 文件必须位于正确的路径 .github/workflows/*.yml,如果目录错误,GitHub 将无法识别 workflow。

3. Job:独立的执行单元

一个 workflow 可以包含多个 job。每个 job 运行在一个独立的 runner(虚拟机)上。默认情况下,job 是并行运行的,除非你声明 needs。

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B test

  deploy:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - run: echo "Deploying application..."

解释:

  • test 和 deploy 是两个独立的 job,每个 job 拥有干净的 runner 环境,彼此不共享文件系统。
  • needs: test 强制 deploy 必须等待 test 成功后才能运行——创建了顺序关系。
  • 如果没有 needs,GitHub Actions 会同时运行 test 和 deploy。

3.1. Job 间共享数据

由于每个 job 有独立的 runner,如果要在 job 之间传输文件,需要使用 artifacts。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B package
      - uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-jar

解释:

  • upload-artifact 将 build job 中的 .jar 文件保存到 GitHub 临时存储中。
  • deploy job 中的 download-artifact 从新的 runner 上重新下载该文件。
  • 这是在 job 之间传输数据的唯一方式(不能使用普通环境变量)。

4. Step:最小的操作单位

Step 是 job 中的具体操作,从上到下依次执行。Step 主要有两种类型:使用 uses 调用预设的 action,或使用 run 执行 shell 命令。

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Cache Maven dependencies
    uses: actions/cache@v4
    with:
      path: ~/.m2/repository
      key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

  - name: Run tests
    run: mvn -B test

  - name: Print result
    run: echo "Test step finished with status ${{ job.status }}"

解释:

  • Checkout code step 使用预设的 actions/checkout action 将源代码拉取到 runner。
  • Cache Maven dependencies step 缓存 ~/.m2/repository 目录,使后续构建更快,因为不需要重新下载依赖。
  • 缓存的 key 基于 pom.xml 的哈希值——当 pom.xml 变化时,旧缓存被丢弃,创建新缓存。
  • 最后一个 step 使用 run 执行普通 shell 命令,可以引用上下文变量如 ${{ job.status }}。

5. 环境变量和 secrets

你经常需要向 workflow 传入敏感配置(API 密钥、token),但不能在 YAML 文件中硬编码。

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SPRING_PROFILES_ACTIVE: prod
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to server
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying with profile $SPRING_PROFILES_ACTIVE"
          curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy

解释:

  • job 级别的 env 对该 job 中的所有 step 有效(SPRING_PROFILES_ACTIVE)。
  • step 级别的 env 仅对该 step 有效(DEPLOY_TOKEN)。
  • secrets.DEPLOY_TOKEN 从仓库的 Settings → Secrets and variables → Actions 获取,实际值永远不会在日志中显示。

注意: 永远不要直接在日志中打印 secret 值,比如用 echo $DEPLOY_TOKEN——GitHub 会自动屏蔽,但最好还是避免打印 secret。

6. Matrix:同时运行多个版本

需要在多个 Java 版本或操作系统上测试时,matrix 可以避免重复编写 job。

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java: [17, 21]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: 'temurin'
      - run: mvn -B test

解释:

  • strategy.matrix.java 声明了值列表 [17, 21]。
  • GitHub Actions 自动创建 2 个并行 job,每个 job 使用不同的 Java 版本。
  • 这种方法节省了编写重复 YAML 的工作,适合需要在多个环境上验证代码的情况。

7. 通过简单测试验证 workflow

Workflow 不能直接用 JUnit 测试,但你应该确保 workflow 中的 mvn test 步骤真正运行项目的测试套件。以下是一个简单的 OrderService 测试示例,CI 会自动运行:

class OrderServiceTest {

    private final OrderService orderService = new OrderService();

    @Test
    void shouldCalculateTotalPriceCorrectly() {
        Order order = new Order("ORD-001", List.of(
                new OrderItem("Keyboard", 2, BigDecimal.valueOf(50)),
                new OrderItem("Mouse", 1, BigDecimal.valueOf(20))
        ));

        BigDecimal total = orderService.calculateTotal(order);

        assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(120));
    }
}

解释:

  • 该测试位于 src/test/java,会被 workflow 步骤中的 mvn -B test 自动运行。
  • 如果测试失败,Run tests 步骤会报错(退出码非 0),job 停止,依赖的 job(通过 needs)不会继续执行。
  • 这就是 CI 有用的原因:有问题的代码在合并到 main 之前就被阻止了。

在 GitHub Actions UI(Actions 标签页)上的运行结果:

✓ Checkout code
✓ Set up JDK 21
✓ Build with Maven
✓ Run tests (shouldCalculateTotalPriceCorrectly PASSED)

8. 常见错误

  • YAML 缩进错误:GitHub Actions 使用 YAML,缩进错误一个空格会导致 job 或 step 无法识别,通常报错“Invalid workflow file”。
  • 遗漏 runs-on:每个 job 必须有 runs-on,缺少它会导致 workflow 在解析时立即失败。
  • 在另一个 job 中使用本 job 的环境变量:普通的 env 和变量输出不会自动跨 job 传递,必须使用 outputs 或 artifact。
  • 依赖变化时缓存 key 不更新:如果 key 设置过于通用(比如只用 maven-cache),会错误地使用包含过时依赖的旧缓存。
  • 在每次推送时运行繁重的 workflow:如果不限制 paths 或 branches,每个小的提交(比如修改 README)都会触发完整的构建+测试 pipeline,浪费 Actions 的免费分钟数。

注意: 如果项目只需要简单的构建且没有多个环境,就不需要使用 matrix 或 artifact——不必要的复杂性会让 workflow 难以维护。

9. 总结

Workflow 是定义整个 pipeline 的 YAML 文件,job 是在独立 runner 上运行的独立执行块,step 是 job 内的具体操作。理解这三个层次可以帮助你清晰地组织 CI/CD pipeline:哪些 job 并行运行,哪些通过 needs 相互等待,以及何时使用 artifact 共享数据。

刚开始时,保持 workflow 简单(一个 job、几个基础 step:checkout、setup Java、test)。随着项目增长,添加 matrix、cache,并将 build/test/deploy 拆分成独立的 job 以便于管理和调试。

github-actionsci-cddevopsjavaautomation