CI/CD核心概念:持续集成、持续交付、持续部署

聊到CI/CD,很多刚入行的朋友容易把这三个词搞混。我记得有一次面试,候选人信誓旦旦地说「我们团队已经做到持续部署了」,结果一问,其实只是每天手动打一次包扔到测试环境。嗯,这里面的门道,咱们今天一次说清楚。

持续集成(Continuous Integration)

持续集成,说白了就是「频繁地把代码合并到主干」。我个人习惯是每天至少合并两到三次。为什么要这么频繁?你想想看,如果大家各写各的,憋了两周才合并一次,那冲突解决起来简直是一场噩梦。

我在项目中遇到过最夸张的一次,两个团队各自开发了半个月,合并时产生了300多个冲突文件。那天整个团队加班到凌晨三点,就为了把代码缝起来。从那以后,我强制要求所有成员每天至少push一次代码。

持续集成的核心要点:

  • 代码频繁合并到主干(每天多次)
  • 每次合并自动触发构建和测试
  • 快速发现集成问题(通常在几分钟内)
  • 问题不隔夜,当天发现当天修

持续集成最关键的,其实是「自动化」。每次代码提交,自动拉代码、编译、跑单元测试、做静态分析。如果哪一步失败了,立刻通知到提交者。我曾经见过一个团队,CI跑一次要40分钟,大家提交完代码就去刷手机等结果。这其实已经违背了CI的初衷——快速反馈。

持续交付(Continuous Delivery)

持续交付是持续集成的自然延伸。代码通过CI验证后,自动部署到类生产环境,做进一步的验证。但注意,这里的关键区别是:部署到生产环境这个动作,仍然需要人工审批。

为什么需要人工?说白了,有些决策机器做不了。比如某个版本要配合市场活动发布,或者要等法务审核通过。我建议在持续交付阶段,保留一个「一键发布」的按钮,但按钮按不按,由人来决定。

我的经验:持续交付做得好,团队可以做到「随时可发布」。代码永远处于可部署状态,只是发不发由业务决定。我曾经带过一个项目,持续交付跑了一年多,每次发布前只需要点一下按钮,整个流程15分钟搞定。

持续部署(Continuous Deployment)

持续部署,就是完全自动化了。代码从提交到上线,中间没有任何人工干预。听起来很爽对吧?但我要泼一盆冷水:不是所有项目都适合持续部署。

我曾经在一个金融项目上尝试过持续部署,结果差点出事。那次是一个配置文件的改动,CI通过了,自动化测试也过了,但上线后导致某个交易接口超时。虽然很快回滚了,但领导层对自动化的信任度大打折扣。

避坑指南:我曾经以为持续部署就是「无脑自动化」,后来发现大错特错。持续部署的前提是:

  • 完善的自动化测试覆盖(单元测试、集成测试、端到端测试)
  • 灰度发布能力(先让1%的流量走新版本)
  • 快速回滚机制(秒级回滚到上一个版本)
  • 完善的监控和告警(发现问题自动停止发布)

三者的区别

用一个表格来对比,会更清晰:

维度 持续集成 持续交付 持续部署
自动化程度 构建+测试自动化 构建+测试+部署到预发布 全流程自动化
人工介入 需要人工合并代码 需要人工审批上线 无需人工介入
发布频率 每天多次 随时可发布 每次提交都发布
风险等级
适用场景 所有项目 大部分商业项目 互联网SaaS、内部工具

CI/CD流水线的基本组成

聊完了概念,咱们看看流水线到底长什么样。我习惯把流水线分成几个阶段,每个阶段都有明确的输入和输出。

阶段一:代码提交与触发

一切从开发者执行 git push 开始。流水线的触发方式有两种:

  • Push触发:任何分支的push都触发,适合开发分支
  • PR触发:只有创建Pull Request时才触发,适合主分支

我个人习惯在开发分支上用Push触发,主分支上用PR触发。这样既能快速反馈,又能保证主分支的质量。

阶段二:代码检查

这个阶段我称之为「守门员」。代码还没开始编译,先做静态检查:

# 一个典型的代码检查阶段
- lint(ESLint / Pylint / Checkstyle)
- 代码格式化检查(Prettier / Black)
- 安全扫描(SonarQube / Snyk)
- 代码复杂度检查

我曾经见过一个项目,代码里居然有硬编码的数据库密码。如果代码检查阶段没拦住,后果不堪设想。所以我现在对安全扫描特别重视。

阶段三:构建与编译

代码检查通过后,开始真正的构建。这个阶段要做的事情:

  • 编译源代码
  • 打包成可部署的制品(jar、docker镜像、npm包等)
  • 生成版本号并打标签

小技巧:我建议每次构建都生成唯一的版本号,比如用 git commit SHA + 构建序号。这样出了问题,能精确追溯到是哪次提交、哪个构建。

阶段四:自动化测试

测试是流水线的核心环节。我一般按这个顺序跑:

  1. 单元测试:跑得最快,覆盖最细,先跑
  2. 集成测试:测试模块间的交互
  3. 端到端测试:模拟用户操作,跑得最慢,放最后

为什么要按这个顺序?说白了,如果单元测试都挂了,后面的测试根本没必要跑。尽早失败,尽早反馈。

阶段五:部署与发布

测试通过后,进入部署阶段。这里根据你的CI/CD成熟度,可以选择:

  • 部署到测试环境(持续集成阶段)
  • 部署到预发布环境(持续交付阶段)
  • 部署到生产环境(持续部署阶段)

注意:生产环境的部署一定要有「一键回滚」的能力。我见过太多团队,部署上去容易,回滚回来要折腾半小时。这其实是不合格的。

一个完整的流水线示例

最后,给大家看一个我常用的流水线配置(简化版):

stages:
  - lint
  - build
  - test
  - deploy

lint:
  script: npm run lint

build:
  script: npm run build
  artifacts:
    paths:
      - dist/

test:
  script: 
    - npm run test:unit
    - npm run test:integration
    - npm run test:e2e

deploy:
  script: 
    - docker build -t myapp:$CI_COMMIT_SHA .
    - docker push myapp:$CI_COMMIT_SHA
    - kubectl set image deployment/myapp myapp=myapp:$CI_COMMIT_SHA
  only:
    - main

这个流水线看起来简单,但每个阶段背后都有很多细节。比如测试阶段,我建议加上超时机制,防止某个测试卡住导致整个流水线挂掉。还有部署阶段,最好加上「人工确认」的步骤,除非你确定要做持续部署。

好了,CI/CD的核心概念和流水线组成就聊到这里。下一章咱们会深入讲讲如何搭建一条真正可用的流水线,包括工具选型、环境配置、以及我在实际项目中踩过的坑。到时候见。