Jobs 与运行器
这一课你将能做到
- 配置多个 job,理解默认并行与用
needs 串行的差别,并让部署 job 挂在测试 job 之后
- 解释 runner 是「一次性虚拟机」:为什么每个 job 都要重新 checkout、装工具
- 写一个入门级
strategy.matrix,用同一套步骤测两个 Node(或 Java)版本
为什么要学
一个 workflow 里往往不止一件事:本站要测 backend 与 platform。若塞进同一个 job 的一长串 step,一边失败难看清边界,也无法并行省时间。
Job 是「一台 runner 上的一组 steps」;多 job 默认各开一台机器一起跑。需要「先构建再部署」时,用 needs 排依赖。搞不清并行/串行时,人要么白白串行浪费时间,要么在没产物的情况下就去部署。
理解 runner 原理还有一层面试价值:「你们的 CI 跑在什么机器上?为什么每次都要重新装依赖?」这类问题考的就是对临时虚拟机生命周期的理解。
先建立图像
workflow 被触发
│
├─ job: backend ── runs-on ubuntu ── steps…
│ (独立机器 A)
│
└─ job: platform ── runs-on ubuntu ── steps…
(独立机器 B,默认同刻开跑)
若 platform 写了 needs: [backend]
则:backend 成功 → 才启动 platform;backend 失败 → platform 直接 skip
人话:每个 job 像一条独立厨房线。默认两条线同时炒菜;写了 needs 就是「头菜出锅后才能做主菜」。
| 概念 | 人话 |
|---|
| runner | 执行 job 的虚拟机(托管或自建) |
runs-on | 选哪类机器镜像 |
needs | 本 job 依赖哪些 job 成功 |
| matrix | 同一 job 模板 × 多组变量 = 多次运行 |
timeout-minutes | 防卡死的保险丝 |
跟着做
步骤 1:写两个并行 job
在 .github/workflows/ci.yml 中:
name: CI
on:
workflow_dispatch:
permissions:
contents: read
jobs:
backend:
runs-on: ubuntu-latest
steps:
- name: 假装测 Java
run: echo "backend ok"
platform:
runs-on: ubuntu-latest
steps:
- name: 假装测 Node
run: echo "platform ok"
手动跑一次:Actions 图上应看到 两个 job 几乎同时开始(或短间隔内都变绿)。
步骤 2:加上 needs 看串行
改 platform:
platform:
runs-on: ubuntu-latest
needs: backend # backend 成功后才跑
steps:
- run: echo "platform after backend"
再跑:backend 先完成,platform 后启动。故意让 backend 失败(run: exit 1):platform 应被跳过(skipped)。笔记写:「失败会沿 needs 传播。」
report:
needs: [backend]
if: always() # 无论成败都跑
steps:
- run: echo "收尾清理"
步骤 3:对照 monorepo 真实切分
jobs:
backend:
runs-on: ubuntu-latest
# checkout → setup-java → ./mvnw verify
platform:
runs-on: ubuntu-latest
# checkout → setup-node → npm ci → lint/test/build
二者无 needs 时并行,总墙钟时间接近「较慢的那一侧」,而不是两者相加。
步骤 4:试一次 matrix(Node)
jobs:
platform-matrix:
runs-on: ubuntu-latest
strategy:
fail-fast: false # 一腿挂了,其它版本继续跑完
matrix:
node: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: node -v
应看到两个子 run(node 20 / 22)。Java 可用 java: [17, 21] 配 setup-java 同理。
步骤 5:理解「干净机器」含义
每个 job 默认不共享上一个 job 的磁盘——runner 是临时虚拟机,跑完即销毁。因此每个需要代码的 job 都要自己 actions/checkout;需要把文件交给别的 job,用 upload-artifact / download-artifact。
口头发一句:「并行省时间,但不要假设另一 job 的 target/ 还在。」
步骤 6:加保险丝与防抖
backend:
runs-on: ubuntu-latest
timeout-minutes: 15 # 卡死自动取消,不白烧分钟
steps:
- run: echo "..."
workflow 顶层可加 concurrency,同一分支的新 push 自动取消还在跑的旧 run:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
讲清楚要点
- 因为每个 job 在独立 runner 上,所以默认互不共享工作区——需要代码就各自 checkout。
- 因为无
needs 时 job 并行,所以 monorepo 两侧检查很适合拆成两 job——墙钟更短、失败域更清晰。
- 因为
needs 表达依赖,所以「先测后发布」用串行——而不是把部署塞进测试 step 末尾含糊带过,意图可读、失败可跳。
- 因为 matrix 是「模板 × 变量」,所以多版本兼容用 matrix——而不是复制粘贴两份几乎相同的 job,改步骤只改一处。
- 因为 runner 是短暂的,所以装在机器上的工具要靠 setup action 或自己安装——不要假设机器上有你本机 IDE 里那套 JDK。
- 因为失败默认使依赖方 skip,所以修 CI 时先看最早红的那个 job——从根因修起,别逐个重跑。
你可能会懵的地方
-
「为什么两个 job 都要 checkout?太浪费?」
它们是两台机器。checkout 很快;硬塞成单 job 反而失去并行与清晰失败点。大产物用 artifact 传递。
-
「ubuntu-latest 具体是哪个版本?」
会随 GitHub 滚动升级。需要可重复时锁定如 ubuntu-24.04。面试可答:latest 方便但有漂移风险,生产流水线建议钉具体版本。
-
「matrix 失败一个算不算整个 workflow 失败?」
默认任一矩阵腿失败,job 失败(fail-fast 还会取消其余腿)。配 fail-fast: false 让其它版本跑完,便于一次看清兼容面。
-
「自托管 runner 什么时候用?」
要访问内网、要特殊硬件(GPU)、或要控成本时。代价是要自己打补丁、管安全——教学主路径用托管 runner 即可。面试若被问「runner 架构」,答:runner 是监听任务分配的代理进程,托管的由 GitHub 生命周期管理,自托管的由你负责更新与隔离。
-
「job 之间怎么传数据?」
磁盘不共享。小数据用 job outputs(声明 outputs + GITHUB_OUTPUT),文件用 artifact。跨 job 直接读路径是必踩的坑。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 全部步骤塞一个 job 串行测两边 | 慢、日志难拆 | backend / platform 两 job 并行 |
部署 job 不写 needs: [ci] | 测试红了仍可能部署 | needs 挂上检查 job |
| matrix 复制成两个几乎相同的 job | 双倍维护 | strategy.matrix |
以为 job 之间能直接读对方 ./target | 磁盘不共享 | artifact 或重新构建 |
没设 timeout-minutes | 卡死白烧额度 | 每个长 job 加超时 |
卡点急救
- job 一直停在 Queued:托管 runner 短时繁忙或私有仓库额度耗尽(查 Billing);自托管 runner 则查代理进程是否在线、标签是否与
runs-on 匹配。
- 后续 job 显示 skipped 而不是 failed:说明
needs 的上游 job 失败了,失败沿依赖链传播。去展开上游 job 找红 step;若确实需要失败也执行(如通知),加 if: always()。
- matrix 某一腿红导致整体红,但看不到其它腿结果:默认 fail-fast 会取消其余腿。调试兼容性时加
strategy.fail-fast: false,让所有版本跑完一次看全。
面试常问
-
GitHub Actions 的 runner 是什么?和 Jenkins agent 有什么区别?
runner 是真正执行 job 的虚拟机,job 结束即销毁,环境每次全新。托管 runner 由 GitHub 维护镜像与升级,按分钟计费;Jenkins agent 通常是长期存在的节点,需要自己维护插件与环境一致性,容易积累「节点漂移」。所以 Actions 的答案更干净:环境不可变,构建可重复。
-
如何缩短 CI 总耗时?
分层答:① 并行化——独立模块拆 job;② 缓存——依赖缓存(Maven/npm)、Docker 层缓存;③ 过滤——paths 只跑改动侧,concurrency 取消过时 run;④ 分级——PR 只跑快速检查,全量验证放 main 或夜间任务;⑤ matrix 控制规模,不为凑数多测版本。结合本站:backend 与 platform 并行、两侧各开依赖缓存、文档改动不触发 CI。
-
needs 的失败传播规则是什么?怎么让失败后也跑清理 job?
默认 needs 的上游失败,下游直接 skipped;想失败也执行用 if: always()(无条件跑)或 if: failure()(仅失败时跑)。这个细节常用来做「测试失败自动收集日志」的模式。
自测三问
- 两个 job 都不写
needs 时,执行顺序通常怎样?
- 为什么每个 job 常常都要
actions/checkout?
- matrix 解决什么问题?举一个 Node 或 Java 的例子。
小结一句话
job = 一台临时 runner 上的一组 steps;默认并行,用 needs 串行,用 matrix 做多版本。
延伸