流水线基础:概念、阶段、步骤、并行与顺序
记得我刚做 CI 那会儿,以为流水线就是写个脚本把命令串起来跑一遍。结果第一次上线就出了岔子——测试还没跑完,构建已经开始打包了,最后部署了一个有 bug 的版本上去。那天晚上我盯着回滚日志,心里只有一个想法:流水线不是脚本,它是一套有章法的自动化流程。
说白了,流水线就是把「代码提交」到「生产发布」之间的所有环节,拆成一个个清晰的阶段和步骤,让它们按你规定的顺序或并行方式自动执行。你想想看,如果没有流水线,每次上线都得手动敲命令、等结果、再敲下一个命令……人不是机器,总会手抖或者忘掉某一步。流水线就是帮你把这些重复劳动固定下来,可靠、可重复、可追溯。
什么是流水线?——从手工到自动化的转变
一个典型的 CI 流水线长什么样?从最简单的视角来看,它就是一条从代码提交到产出交付物的自动通道。代码一推,流水线自己就动起来了:先检查代码质量,再跑单元测试,然后打包构建,最后部署到对应环境。
但这里有个关键点:流水线不是铁板一块。它被拆成了若干「阶段」,每个阶段里又有若干「步骤」。阶段和步骤之间,有的必须排着队来,有的可以同时跑。这就是本章要讲清楚的事。
我个人习惯把流水线想象成一条汽车组装线。你不能还没装好发动机就去装轮子,但你可以同时给车身喷漆和准备内饰——这就是并行与顺序的直觉。
流水线的阶段:把大象装进冰箱分几步?
阶段是流水线里最粗的粒度。每个阶段代表一个逻辑上的大块任务,它有明确的进入条件和产出物。我见过很多团队上来就把所有命令写到一个 stage 里,结果日志乱成一锅粥,出了错都不知道是哪一步挂的。
划分阶段的核心原则是:一个阶段只做一件事,并且这件事的产出可以被下一个阶段消费。
一般 CI 流水线会包含下面这几个阶段:
| 阶段 | 做的事情 | 能不能并行 |
|---|---|---|
| 代码检查 | Lint、格式检查、安全扫描 | ✅ 内部步骤可并行 |
| 单元测试 | 跑单测、生成覆盖率报告 | ✅ 可并行执行 |
| 构建打包 | 编译代码、打镜像、生成制品 | ❌ 一般顺序执行 |
| 部署测试 | 部署到测试环境、预发布环境 | ✅ 环境之间可并行 |
| 生产发布 | 灰度上线、全量发布 | ❌ 必须按顺序 |
你在实际项目里划分阶段时,记住一句话:阶段的颗粒度要能让你在 10 秒内定位到失败的位置。如果一个阶段里塞了 10 个操作,日志滚好几屏才能看到 error,那这个阶段就拆得太粗了。
步骤:阶段里的最小执行单元
阶段往下拆,就是步骤。步骤是流水线里最小的可执行单元,它通常对应一个具体的命令或操作。比如「代码检查」这个阶段里,可以包含「运行 ESLint」「运行 Prettier 检查」「跑安全扫描」这三个步骤。
步骤和步骤之间,可以是顺序的,也可以是并行的。这完全取决于它们之间有没有依赖关系。举个例子:「编译构建」和「生成测试报告」没有依赖,可以并行;但「编译构建」和「镜像打包」就有依赖——你得先编译出二进制,才能把它打进镜像。
来看一段 Jenkins Pipeline 的代码,它很直观地展示了阶段、步骤、并行和顺序的关系:
pipeline {
agent any
stages {
stage('代码检查') {
parallel {
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('单元测试') {
steps {
sh 'npm test'
}
}
}
}
stage('构建打包') {
steps {
sh 'npm run build'
sh 'docker build -t app:latest .'
}
}
stage('部署测试') {
parallel {
stage('部署测试环境') {
steps {
sh 'deploy-to-test'
}
}
stage('部署预发布环境') {
steps {
sh 'deploy-to-staging'
}
}
}
}
stage('生产发布') {
steps {
sh 'deploy-canary 10%'
sh 'deploy-canary 50%'
sh 'deploy-full'
}
}
}
}
你看,parallel 块里放的就是可以同时跑的步骤,而没有用 parallel 包裹的步骤,就老老实实按顺序执行。这个例子里的「生产发布」阶段,灰度 10%、50% 再到全量,必须一步步来——谁敢一下子把所有流量切到新版本?
并行与顺序:让流水线「跑得快」又「跑得稳」
并行和顺序是流水线里最核心的两种执行策略。选对了,流水线又快又稳;选错了,要么浪费时间,要么埋下隐患。
并行执行:多个步骤或阶段同时跑。适用条件是这些任务之间没有数据依赖,互相不冲突。好处很明显——节省时间。坏处呢?资源竞争。我在项目中遇到过好几次,并行跑测试时因为共享同一个数据库实例,导致数据互相污染,测试结果全红了。后来我们给每个并行分支分配了独立的测试容器,才解决这个问题。
顺序执行:一个跑完下一个再跑。适用场景是有明确的依赖关系,或者出于风险管控的需要。比如生产发布的灰度步骤,必须按百分比一步步来,观察一段时间没问题再继续。顺序执行虽然慢,但稳。
怎么判断哪些可以并行、哪些必须顺序?我一般用两个问题来检验:
- 问依赖:这两个任务之间有没有数据或状态的传递?如果有,顺序执行。
- 问风险:如果它们同时跑,会不会互相影响(比如抢端口、写同一个文件、改同一个数据库)?如果有,顺序执行或隔离资源后并行。
下面这张图把流水线的阶段划分、步骤拆分、并行与顺序的关系画在了一起。你可以看到整个流程从代码提交开始,经过四个阶段到达生产发布,每个阶段内部又标注了步骤的执行策略。
实战避坑指南
讲了这么多理论和图例,最后聊几个我实际碰到的坑,希望能帮你少走弯路。
- 别把所有步骤都并行。并行不是越快越好,无节制的并行会让资源打满,反而拖慢整体速度。我曾经在一个项目里把 8 个测试任务全配成并行,结果 runner 的 CPU 直接 100%,每个测试都跑得比顺序还慢。后来控制在 4 个并行,总耗时反而降了一半。
- 阶段命名要一看就懂。别用
stage1、step2这种名字。流水线日志是给团队看的,不是给自己看的。我一般用「动词+对象」的格式:构建前端、部署测试环境、灰度发布。 - 每个阶段都要有超时控制。万一某个步骤卡住了,流水线不能无限等下去。设置一个合理的超时时间(比如 10 分钟),超时后自动失败,并触发告警。
- 并行任务要做好资源隔离。如果多个并行任务会写同一个目录或数据库,要么各自用独立的 workspace,要么用锁机制。否则你会看到「偶发性失败」——重启一遍又好了,但上线后心里发虚。
流水线的基础就是这些:概念让你知道它是什么,阶段和步骤让你知道怎么拆,并行与顺序让你知道怎么跑。把这几个东西理清楚了,后面的高级技巧才有了立足之地。