流水线基础:流水线定义、阶段划分、触发机制、产物管理

流水线(Pipeline)是 CI/CD 的核心执行载体,它将“代码提交”到“生产部署”之间的所有自动化步骤,组织为一条可重复、可观测、可追溯的执行链。理解流水线,不能只把它看作“一串脚本”,而要把握四个维度:定义方式(如何描述)、阶段划分(如何拆分)、触发机制(何时运行)、产物管理(产出什么、如何传递)。

一、核心原理:流水线的声明式描述

现代流水线普遍采用声明式(Declarative)定义,而非命令式脚本。声明式的核心价值在于:你描述“想要什么状态”,而不是“一步步怎么做”。以 Jenkins 为例,一个最小流水线如下:

pipeline {
    agent any
    stages {
        stage('Build') {
            steps { sh 'make build' }
        }
        stage('Test') {
            steps { sh 'make test' }
        }
    }
}

这段定义表达了三个关键信息:执行环境(agent)、阶段列表(stages)、每个阶段的具体动作(steps)。声明式流水线天然具备以下特性:

  • 可读性:结构清晰,新人也能快速理解整体流程。
  • 可重试:失败后可从指定阶段恢复,而非从头执行。
  • 可并行:不同阶段或同一阶段的不同分支可并行执行。
  • 可观测:每个阶段的开始、结束、耗时、状态都被平台记录。
核心认知:流水线不是“脚本的集合”,而是“流程的模型”。脚本关注“如何做”,流水线关注“做什么、按什么顺序、在什么条件下做”。这决定了流水线必须具备阶段、门禁、产物等结构化元素。

二、阶段划分:从提交到上线的标准路径

阶段(Stage)是流水线的骨架。合理的阶段划分应遵循“高内聚、低耦合”原则——每个阶段只做一类事情,阶段之间通过产物(Artifact)或测试报告传递信息。一个典型的部署流水线通常包含以下阶段:

阶段 核心任务 典型产物 失败处理
检出(Checkout) 拉取源码、切换分支、获取指定提交 工作区代码 直接失败,无需继续
构建(Build) 编译、打包、生成可执行文件或镜像 JAR/WAR、Docker 镜像、二进制文件 快速失败,节省资源
测试(Test) 单元测试、集成测试、静态扫描 测试报告、覆盖率报告 失败则阻断后续阶段
部署(Deploy) 推送镜像、更新服务、执行数据库迁移 部署版本号、环境状态 需考虑回滚策略
验证(Verify) 冒烟测试、健康检查、监控指标确认 验证日志、监控快照 失败触发自动回滚

阶段划分的粒度需要平衡:过粗(如“构建+测试”合并)会导致问题定位困难;过细(如每个测试用例一个阶段)则增加调度开销。经验法则是:每个阶段应能在 5 分钟内判断成败,且失败后能明确知道是代码问题、环境问题还是配置问题。

三、触发机制:让流水线在正确时机运行

触发机制决定了流水线的“启动条件”。错误的触发策略会导致资源浪费或漏检。常见的触发方式包括:

  • 代码提交触发(Push):最常用,每次 push 到指定分支即触发。适合开发分支的快速验证。
  • 合并请求触发(Merge Request / Pull Request):在 MR 创建或更新时触发,通常只运行测试和静态检查,不部署。
  • 定时触发(Schedule):如每晚 2 点运行全量测试,适合耗时较长的回归测试。
  • 手动触发(Manual):生产环境部署通常需要人工确认,避免自动化误操作。
  • 上游依赖触发(Upstream):当依赖的镜像或库更新时,自动触发下游项目重建。

以 GitLab CI 为例,通过 rules 关键字可以精细控制触发条件:

deploy-prod:
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
      allow_failure: false
    - when: never
  script:
    - ./deploy.sh --environment=prod

这段配置表达:只有 main 分支的提交才显示“手动部署”按钮,其他分支一律不执行该任务。这种“条件 + 动作”的声明方式,比在脚本里写 if 判断更直观、更安全。

常见误区:不要把所有分支都配置为“提交即部署”。开发分支的频繁提交会触发大量无意义的部署任务,既浪费资源,又可能造成环境冲突。建议:开发分支只跑构建+测试,主干分支才允许部署到预发,生产环境必须手动确认。

四、产物管理:连接阶段的“接力棒”

产物(Artifact)是流水线阶段之间传递的“中间成果”。没有产物管理,每个阶段都要重新拉代码、重新构建,效率极低且无法保证一致性。产物管理需要关注三个层面:

1. 产物的定义与留存:在流水线中明确声明哪些文件需要保留。例如 Jenkins 中:

post {
    success {
        archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true
    }
}

这段配置将构建生成的 JAR 包归档,并记录其指纹(哈希值),便于追溯“哪个构建产生了哪个包”。

2. 产物的版本与命名:产物必须与构建号或提交号关联。推荐命名规则:应用名-版本号-构建号.扩展名,例如 order-service-1.4.2-87.jar。避免使用“latest”或“final”这类模糊名称。

3. 产物的传递与消费:下游阶段(如部署)应直接从产物库拉取,而不是重新构建。在 GitLab CI 中,通过 dependencies 关键字控制:

deploy:
  stage: deploy
  dependencies:
    - build
  script:
    - scp target/app.jar user@server:/opt/app/

这里 dependencies: [build] 表示 deploy 任务只下载 build 任务产生的产物,避免下载无关文件。

五、案例:一个完整的微服务部署流水线

假设有一个 Java 微服务 payment-service,采用 GitLab CI + Kubernetes 部署。流水线定义如下:

stages: [build, test, package, deploy]

build:
  stage: build
  script:
    - mvn compile
  artifacts:
    paths: [target/classes/]

test:
  stage: test
  script:
    - mvn test
  artifacts:
    reports:
      junit: target/surefire-reports/TEST-*.xml

package:
  stage: package
  script:
    - mvn package -DskipTests
    - docker build -t registry.example.com/payment-service:$CI_COMMIT_SHORT_SHA .
    - docker push registry.example.com/payment-service:$CI_COMMIT_SHORT_SHA
  dependencies: [build]

deploy-staging:
  stage: deploy
  script:
    - kubectl set image deployment/payment-service payment-service=registry.example.com/payment-service:$CI_COMMIT_SHORT_SHA
  environment: staging
  only: [main]
  dependencies: [package]

这条流水线的执行逻辑:

  1. build 阶段编译源码,保留 target/classes 供后续阶段使用。
  2. test 阶段运行单元测试,生成 JUnit 报告,测试失败则流水线终止。
  3. package 阶段跳过测试(因为已测过),构建 Docker 镜像并推送仓库,镜像标签为提交 SHA。
  4. deploy-staging 阶段仅 main 分支触发,使用 kubectl set image 滚动更新到预发环境。

该案例体现了三个关键设计:阶段间通过产物传递(build 的 classes 给 test,package 的镜像给 deploy)、触发条件分层(测试每次提交都跑,部署仅 main 分支)、镜像不可变(用提交 SHA 作为标签,避免覆盖)。

六、误区与复盘:流水线设计的常见陷阱

误区 1:把流水线当作“万能脚本执行器”
表现为:在一个 stage 里写几百行 shell,包含编译、测试、部署、发通知。后果:无法定位失败环节,无法并行,无法复用。
正确做法:每个 stage 只做一件事,失败后能立即判断是代码问题还是环境问题。
误区 2:忽略产物清理策略
表现为:每次构建都归档全部产物,从不清理。数月后磁盘爆满,构建开始失败。
正确做法:设置保留策略,如“保留最近 30 次构建的产物”或“按天清理 7 天前的包”。
误区 3:所有环境共用同一条流水线
表现为:开发、测试、生产使用完全相同的阶段和参数,导致生产部署时才发现配置差异。
正确做法:通过环境变量或配置文件区分,但阶段结构保持一致。例如 deploy 阶段根据 $ENVIRONMENT 选择不同的 Kubernetes 命名空间。

复盘要点:设计流水线时,先画一张“阶段-产物-触发条件”的表格,明确每个阶段的输入、输出、失败策略。这张表比写代码更重要。流水线的本质是把部署流程工程化,而不是简单地把命令串起来。好的流水线应该让新人也能看懂流程,让机器自动处理重复劳动,让失败快速暴露且可追溯。

流水线核心要素与执行链路 触发机制 Push 提交 Merge Request 定时 / 手动 上游依赖 流水线阶段(Stages) 检出 Checkout 构建 Build 测试 Test 打包 Package 部署 Deploy 验证 Verify 产物(Artifact)在阶段间传递:JAR / 镜像 / 报告 产物管理 归档 + 指纹 版本命名规则 依赖传递控制 清理与保留策略 执行结果与反馈 成功 → 继续下一阶段 / 失败 → 快速定位 + 自动回滚 失败处理策略 快速失败 / 重试 / 人工审批 质量门禁 测试覆盖率 / 静态扫描 / 安全漏洞 核心设计原则 阶段高内聚 · 产物可追溯 · 触发条件明确 · 失败快速暴露 声明式定义 可观测可追溯 环境一致性 触发 阶段 产物 失败处理

上图展示了流水线的完整执行链路:左侧的触发机制决定何时启动,中间的阶段序列按顺序执行,产物在阶段间传递,右侧的产物管理确保可追溯性,底部则强调失败处理和质量门禁。理解这张图,就掌握了流水线设计的核心框架。