流水线基础:概念、阶段、步骤、并行与顺序

记得我刚做 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% 再到全量,必须一步步来——谁敢一下子把所有流量切到新版本?

并行与顺序:让流水线「跑得快」又「跑得稳」

并行和顺序是流水线里最核心的两种执行策略。选对了,流水线又快又稳;选错了,要么浪费时间,要么埋下隐患。

并行执行:多个步骤或阶段同时跑。适用条件是这些任务之间没有数据依赖,互相不冲突。好处很明显——节省时间。坏处呢?资源竞争。我在项目中遇到过好几次,并行跑测试时因为共享同一个数据库实例,导致数据互相污染,测试结果全红了。后来我们给每个并行分支分配了独立的测试容器,才解决这个问题。

顺序执行:一个跑完下一个再跑。适用场景是有明确的依赖关系,或者出于风险管控的需要。比如生产发布的灰度步骤,必须按百分比一步步来,观察一段时间没问题再继续。顺序执行虽然慢,但稳。

我曾经踩过的一个坑:在部署阶段用了并行,同时往测试环境和预发布环境部署同一个版本。结果测试环境的数据库迁移脚本跑得慢了 10 秒,预发布环境先跑完了迁移,两个环境的 schema 不一致,导致后面所有的集成测试全部失败。从那以后我定了一条规则:涉及数据库变更的步骤,必须顺序执行。

怎么判断哪些可以并行、哪些必须顺序?我一般用两个问题来检验:

  • 问依赖:这两个任务之间有没有数据或状态的传递?如果有,顺序执行。
  • 问风险:如果它们同时跑,会不会互相影响(比如抢端口、写同一个文件、改同一个数据库)?如果有,顺序执行或隔离资源后并行。

下面这张图把流水线的阶段划分、步骤拆分、并行与顺序的关系画在了一起。你可以看到整个流程从代码提交开始,经过四个阶段到达生产发布,每个阶段内部又标注了步骤的执行策略。

流水线核心架构:阶段、步骤与执行策略 代码提交 代码检查 Lint 检查 单元测试 并行 构建打包 编译构建 镜像打包 部署测试 测试环境部署 预发布环境部署 并行 生产发布 灰度发布 10% 全量发布 顺序 执行策略说明 ▶ 并行策略:无依赖的任务可同时执行,缩短总耗时。需注意资源隔离,避免竞争。 ▶ 顺序策略:有依赖或风险管控的任务需按序执行。保证稳定性,但耗时更长。

实战避坑指南

讲了这么多理论和图例,最后聊几个我实际碰到的坑,希望能帮你少走弯路。

  • 别把所有步骤都并行。并行不是越快越好,无节制的并行会让资源打满,反而拖慢整体速度。我曾经在一个项目里把 8 个测试任务全配成并行,结果 runner 的 CPU 直接 100%,每个测试都跑得比顺序还慢。后来控制在 4 个并行,总耗时反而降了一半。
  • 阶段命名要一看就懂。别用 stage1、step2 这种名字。流水线日志是给团队看的,不是给自己看的。我一般用「动词+对象」的格式:构建前端、部署测试环境、灰度发布。
  • 每个阶段都要有超时控制。万一某个步骤卡住了,流水线不能无限等下去。设置一个合理的超时时间(比如 10 分钟),超时后自动失败,并触发告警。
  • 并行任务要做好资源隔离。如果多个并行任务会写同一个目录或数据库,要么各自用独立的 workspace,要么用锁机制。否则你会看到「偶发性失败」——重启一遍又好了,但上线后心里发虚。
一个小技巧:在流水线的关键节点(比如部署前后)加一个人工确认的步骤。不是所有事情都应该自动化——生产发布的最后一道门,让人看一眼有时比全自动更安全。我习惯在「灰度 10%」之后加一个等待人工确认的步骤,确认没问题再继续往下走。

流水线的基础就是这些:概念让你知道它是什么,阶段和步骤让你知道怎么拆,并行与顺序让你知道怎么跑。把这几个东西理清楚了,后面的高级技巧才有了立足之地。