3、CI 流水线结构:阶段(Stage)、任务(Job)、步骤(Step)的概念与日志对应关系
做日志分析的第一步,不是盯着报错信息死磕,而是先搞清楚报错到底出在流水线的哪一层。说白了,你得知道这错误是「哪个阶段」爆的、「哪个任务」跑的、「哪一步」执行的。
CI 流水线不是一团乱麻,而是分了三层结构:Stage(阶段)、Job(任务)、Step(步骤)。我见过太多人,一拿到日志就翻到底,结果连是哪个 Job 报的错都没分清。嗯,这里咱们得彻底讲清楚。
Stage:流水线的骨架
Stage 是最高级别的逻辑分组。比如一个典型的 CI 流水线,会分成:
- Build(构建):编译代码、打包制品
- Test(测试):跑单元测试、集成测试
- Deploy(部署):推送到测试环境或生产环境
每个 Stage 只关心一件事。我在项目中遇到过一个问题:某次构建被拆成了三个 Stage,但日志里 Stage 名称用了默认的「Stage 1」「Stage 2」「Stage 3」——等你排查的时候根本分不清哪个阶段干了什么。从那以后,我规定团队必须给 Stage 起有业务含义的名字。
Job:Stage 里的执行单元
Stage 里面会有多个 Job,它们可以并行执行。举个例子,「测试」这个 Stage 里可能有:
- Job: unit-test(单元测试)
- Job: integration-test(集成测试)
- Job: lint-check(代码规范检查)
每个 Job 是独立的最小执行单元,失败只影响自身。日志里会清晰标出哪个 Job 失败了。我曾经遇到一个诡异场景:并行 Job 全部失败,但错误内容却不一样。最终发现是其中一个 Job 的环境变量写死了,影响了共用的缓存目录。如果没有按 Job 隔离分析,根本找不到根因。
Step:Job 里的原子操作
Step 是最小执行单元。常见的 Step 包括:
- 运行 shell 命令
- 下载依赖包
- 执行测试用例
- 上传制品
每个 Step 都有开始和结束标记。日志里典型的 Step 输出像这样:
[Step 1/3] 开始执行:npm install
...
[Step 2/3] 开始执行:npm run build
...
[ERROR] 构建失败,退出码 1
[Step 2/3] 执行结束:失败
你想想看,如果日志里只写了「构建失败」,你根本不知道是 npm install 出的问题还是 build 出的问题。但是一旦有了 Step 标记,一眼就能锁定是第 2 步出错了。
三层结构与日志的对应关系
我整理了一个对照表,方便你在日志里快速定位:
| 层级 | 日志常见标识 | 失败时典型表现 | 排查要点 |
|---|---|---|---|
| Stage | [Stage: Build] 或 ##[section]Starting: Build |
整个 Stage 变红,内部所有 Job 可能被跳过 | 先确认是哪个 Stage 失败,再进入下一层 |
| Job | [Job: compile] 或 Job 'test' failed |
只有当前 Job 标红,其他 Job 正常执行 | 查看 Job 级别的日志摘要,找退出码 |
| Step | Step X/Y 或 + command |
报错信息紧跟在 Step 输出之后 | 查看 Step 开始/结束标记,定位具体命令 |
实战:从日志中读取出三层结构
下面是一个真实的 GitLab CI 日志片段(我稍微改动了内容):
Running with gitlab-runner 14.0
Preparing the "docker" executor
...
[Stage: Build]
[Job: compile-app]
Step 1: docker build -t myapp .
Sending build context to Docker daemon 2.048kB
Step 1/8 : FROM node:16
...
Step 8/8 : RUN npm install
npm ERR! code ECONNRESET
npm ERR! network request to https://registry.npmjs.org/...
Step 1 failed with exit code 1
Job compile-app failed
Stage Build failed
你看,从下往上读,先看到 Stage,再找到 Job,最后定位到 Step 1 里的 npm install 网络超时。如果没有这种层级意识,你可能被 ECONNRESET 吓到,然后跑去调 Docker 配置——其实换个思路,把 npm registry 换成国内镜像就行了。
为什么会这样?因为日志分析不是瞎猜,而是按层级做「漏斗式」排查。Stage 给你方向,Job 缩小范围,Step 给出真相。
SVG:CI 流水线三层结构图
下面这张图把 Stage、Job、Step 的关系画清楚了。你可以把它当成排查时的「地图」:
不同 CI 工具下的日志标识差异
不同工具用的标记格式不一样,但核心结构是相同的。我列个对比:
| CI 工具 | Stage 标记 | Job 标记 | Step 标记 |
|---|---|---|---|
| Jenkins Pipeline | Stage 'Build' running |
Running job 'compile' |
[Pipeline] // step |
| GitLab CI | Stage: build |
Job: compile |
Step 1/3 : docker build |
| Azure Pipelines | ##[section]Starting: Build |
Job compile |
##[command]docker build |
| GitHub Actions | Run: build (workflow 内) |
run: npm install |
无显式 Step 编号,按顺序输出 |
echo "Step X: ..." 来标记。我自己的团队就是这么干的——工具不给力,咱们自己补。
踩过的坑:Stage 失败但 Job 成功?
你可能觉得奇怪:Stage 失败了,里面所有 Job 不都应该失败吗?不一定。我曾经碰到过一个情况:Pipeline 设置了 allow_failure: true(允许任务失败),结果某个 Job 报错了,Stage 却还是绿色的。后来我排查日志,发现 Stage 的状态是由「所有必须成功(required)的 Job」决定的,而不是看全部 Job。
所以读日志的时候,一定要看每个 Job 独立的退出码,别被 Stage 的颜色骗了。
一句话总结本章
Stage 定方向,Job 定范围,Step 定细节。拿到日志,先按这个三层结构做「剥洋葱」,把问题一层层剥出来,别一上来就扎进报错文本里。