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) 推送到测试/预发/生产环境 没有回滚机制,一次部署失败就得手动恢复,耗时半小时。

流水线的调度方式:顺序、并行、依赖图

流水线里任务怎么执行,直接影响整体耗时。我见过不少团队一开始只做顺序执行,跑一次要半小时;后来改成并行,十分钟搞定。但并行不是万能的,任务之间有依赖时,强行并行会出幺蛾子。这就引出了三种常见的调度模式:

  1. 顺序调度 —— 一个阶段跑完,再跑下一个。简单可靠,但慢。
  2. 并行调度 —— 同一阶段内无依赖的任务同时跑。快,但需要管理资源。
  3. 依赖图调度 —— 按任务之间的依赖关系(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」,排查了半小时才发现是配置写反了。所以画依赖图时一定要确保无环,最好用可视化工具先验证一遍拓扑结构。

CI 流水线核心流程与调度方式 代码提交 构建 单元测试 集成测试 部署 单元测试A 单元测试B 调度方式: 顺序调度(严格串行) 并行调度(无依赖任务同时执行) 依赖图调度(DAG拓扑排序) 提示:上图中单元测试A和B可并行执行,集成测试需等待所有单元测试完成,体现依赖关系。

上面这张 SVG 图展示了一个典型的 CI 流水线流程,以及并行任务和依赖关系。你看到的单元测试 A 和 B 可以同时跑,构建完成后触发它们,但集成测试必须等两个单元测试都结束后才能启动——这就是依赖图的核心思想:每个任务只关心自己需要哪些前置任务。

在实际项目中,依赖图往往比这个更复杂。我经常用 needs 字段构建出几十个节点的 DAG,但基础原理是一样的。只要你把任务间的依赖关系梳理清楚,CI 就能跑得又快又稳。