CI 流水线基础概念
聊 CI 之前,咱们得先把「流水线」这个概念弄清楚。说白了,CI 流水线就是把代码从提交到交付的整个过程,拆成一连串自动执行的步骤。我刚开始接触时,还以为就是个脚本串起来跑,后来踩了不少坑才明白,流水线的设计直接影响团队效率和交付质量。
流水线到底是什么
你想想看,每次代码变更,从开发环境到生产环境,中间要经过多少道工序?构建、单元测试、集成测试、代码扫描、打包、部署……人工操作的话,不仅慢而且容易出错。CI 流水线的价值就在于:把这些工序编排成自动化流程,每次代码提交自动触发。我个人的习惯是,流水线里的每一个阶段都应该有明确的目标和产出物,不能大而全地塞在一起。
CI 流水线的核心要素
- 触发源:代码推送、合并请求、定时调度等
- 阶段(Stage):逻辑上独立的任务组,例如 build、test、deploy
- 任务(Job):阶段内的具体执行单元,可并行
- 产物(Artifact):阶段之间传递的文件或数据
- 状态报告:成功/失败通知,反馈给开发者
流水线的核心阶段
无论你用 Jenkins、GitLab CI 还是 GitHub Actions,流水线的骨架都差不多。我帮团队迁移过好几次工具,发现阶段划分的逻辑可以复用。下面是一份典型的阶段定义:
| 阶段 | 常见任务 | 我在项目中遇到的坑 |
|---|---|---|
| 代码获取与检查 | 拉取代码、Lint、格式化检查 | 曾经忘记添加 .gitignore,导致 node_modules 被上传,构建爆慢。 |
| 构建(Build) | 编译、打包、生成可执行文件 | 不同开发机环境不一致,构建结果文件路径不对,影响后续部署。 |
| 单元测试 | 运行开发者编写的单测用例 | 部分测试依赖外部服务,导致随机失败,后来强制 mock 才稳定。 |
| 集成测试 | 多模块联合测试、接口测试 | 启动多个服务时端口冲突,后来用 Docker Compose 统一管理。 |
| 部署(Deploy) | 推送到测试/预发/生产环境 | 没有回滚机制,一次部署失败就得手动恢复,耗时半小时。 |
流水线的调度方式:顺序、并行、依赖图
流水线里任务怎么执行,直接影响整体耗时。我见过不少团队一开始只做顺序执行,跑一次要半小时;后来改成并行,十分钟搞定。但并行不是万能的,任务之间有依赖时,强行并行会出幺蛾子。这就引出了三种常见的调度模式:
- 顺序调度 —— 一个阶段跑完,再跑下一个。简单可靠,但慢。
- 并行调度 —— 同一阶段内无依赖的任务同时跑。快,但需要管理资源。
- 依赖图调度 —— 按任务之间的依赖关系(DAG)决定执行顺序。灵活,能最大化利用资源。
举个例子:构建完成后,单元测试和代码扫描没有依赖关系,可以并行;但集成测试需要单元测试通过后的产物,那就必须等它完成。依赖图调度说白了就是把这种「等」的逻辑用有向无环图表示出来,让 CI 引擎自动拓扑排序。
我的建议:新手先别急着上依赖图。先用顺序流水线跑通,再观察哪些阶段可以并行,最后再引入依赖图优化。我曾经直接上 DAG 调度,结果任务依赖链画错了,导致部署时缺少上游产物,线上差点出事。
常见 CI 工具与配置示例
工具有很多,但背后的调度思想大同小异。以 GitLab CI 为例,它的 .gitlab-ci.yml 用 stages 定义阶段顺序,用 needs 指定任务间的依赖,从而实现依赖图调度。
stages:
- build
- test
- deploy
build-job:
stage: build
script:
- npm install
- npm run build
unit-test:
stage: test
needs: ["build-job"]
script:
- npm test
integration-test:
stage: test
needs: ["build-job"]
script:
- npm run test:integration
# 注意:unit-test 和 integration-test 可以并行执行
# 因为它们都只依赖 build-job
deploy-job:
stage: deploy
needs: ["unit-test", "integration-test"]
script:
- npm run deploy
# deploy 必须等两个测试都完成后才能执行
你看,needs 字段就是依赖图的核心表达。每个任务只需声明自己需要什么,CI 引擎自动计算执行顺序。我在项目中经常用这个特性来优化并行度,比如把多个独立测试任务拆成不同的 job,用 needs 控制依赖,而不是塞到一个 job 里用脚本串行跑。
我的一些经验总结
嗯,这里要注意两点:
- 流水线不是越复杂越好。如果你的项目只有两个开发者,顺序执行十几分钟能接受,就别强行搞并行,徒增维护成本。
- 依赖图只适用于任务间有明确依赖关系。如果两个任务完全独立但资源共享(比如同一条数据库连接),强行并行可能导致竞态。我遇到过单元测试和集成测试同时操作同一张测试表,数据互相覆盖,结果两个都失败。后来加了独立库才解决。
避坑指南:我曾经犯过一个低级错误——在依赖图里画了环(A 等 B,B 等 A)。CI 引擎报「DAG cycle detected」,排查了半小时才发现是配置写反了。所以画依赖图时一定要确保无环,最好用可视化工具先验证一遍拓扑结构。
上面这张 SVG 图展示了一个典型的 CI 流水线流程,以及并行任务和依赖关系。你看到的单元测试 A 和 B 可以同时跑,构建完成后触发它们,但集成测试必须等两个单元测试都结束后才能启动——这就是依赖图的核心思想:每个任务只关心自己需要哪些前置任务。
在实际项目中,依赖图往往比这个更复杂。我经常用 needs 字段构建出几十个节点的 DAG,但基础原理是一样的。只要你把任务间的依赖关系梳理清楚,CI 就能跑得又快又稳。