第二章:CI/CD基础与持续集成流程归纳总结

聊聊CI/CD吧。很多人一听到这个词就想到Jenkins、GitLab CI、GitHub Actions这些工具,但我觉得理解它背后的逻辑更重要。CI——持续集成,说白了就是让开发团队频繁地把代码合入主干,然后自动跑构建和测试。CD呢,可以是持续交付或持续部署,这取决于你愿不愿意一键把东西推上线。

我自己刚带团队那会儿,对CI的理解就卡在“自动编译”这个层面。以为上了流水线就算完成了。结果呢?代码合进去,编译是过了,但一跑单元测试就挂,一跑集成测试就炸。后来我意识到,真正的CI不是“能编译”,而是“能验证”。

2.1 从“手工作坊”到“自动流水线”

先回忆一下没有CI的日子。开发写完代码,手动上传到测试服务器,跑一遍测试,发现问题再改……这种模式有个名字——集成地狱(Integration Hell)。

传统集成 持续集成
代码提交间隔长(天/周) 代码提交频繁(小时/天)
手动构建、手动测试 自动构建、自动测试
发现问题晚,修复成本高 问题暴露快,修复成本低
发布周期长 随时可发布

嗯,这张表我当年在团队里贴过。有个开发兄弟跟我说:“我们每天就合一次代码,够频繁了吧?”我说:“那你们每天看一次集成结果,发现失败时,中间已经过了8个commit了。查错累不累?”他沉默了。实际落地时,我建议至少每2~3小时合入一次,配合自动验证,效果比预期好得多。

2.2 持续集成流水线的标准阶段

一条像样的CI流水线,至少包含下面这几个环节。我在多个项目中迭代过这个模板,它很稳。

  1. 代码提交与触发 —— push、PR、定时都可以触发。我个人习惯用Webhook实时触发,避免人为等待。
  2. 静态分析与代码检查 —— 检查语法、风格、安全漏洞。我记得有个项目因为没做这个步骤,一个SQL注入风险在线上躺了三个月。
  3. 构建 —— 编译、打包成可执行的产物。这里要注意环境一致性,我踩过一个大坑:开发机器上编译OK,CI机器上却报错,因为jdk版本差了0.0.1。
  4. 单元测试 —— 跑所有单元测试,要求覆盖率门槛。我一般设80%作为硬性门禁。
  5. 集成测试/契约测试 —— 验证模块间的交互。尤其涉及Mock回放的地方,这一步是核心。后面几章会深入讲。
  6. 制品归档与通知 —— 把构建产物存到仓库,通知团队结果。成功与失败都要发。
一个小技巧: 把流水线分成“快速反馈”和“深度验证”两个阶段。快速阶段10分钟内必须跑完,深度阶段可以并行跑半小时以上。这样开发不用等太久就能知道大部分问题。

2.3 自己画的持续集成流程总图

下面这张图是我在培训时经常展示的,它把上面几个步骤串了起来。每个节点都是必要环节,缺一个都可能出问题。

持续集成流程(标准Pipeline) 代码提交/PR 静态分析 构建 & 打包 单元测试 集成测试 制品归档 通知团队 说明: ▸ 单元测试通过后,一条路径继续往下走集成测试,另一条路径开始归档制品(异步)。 ▸ 我喜欢把集成测试设计成“回放式”,因为这样快且稳定。Mock就是它的核心。

2.4 CI的核心原则(我的经验提炼)

这么多年下来,我总结了几条铁律。不一定适合所有项目,但至少能让流水线少出幺蛾子。

  • 每次提交都触发全量验证 —— 别偷懒只跑增量测试。我有过一次惨痛经历:只加了几个新方法的测试,老的被改了却没测试覆盖,结果上线炸了。
  • 失败即为阻塞 —— 流水线变红,就应该冻结代码合入,除非是已知的、非关键的例外。我曾经允许团队在红线上继续合代码,结果三天后修复成本翻了5倍。
  • 环境一致性 —— 开发环境、CI环境、生产环境尽量镜像。我最推荐用Docker image固化,包括工具链版本。
  • 反馈要快 —— 20分钟以上就算慢。想办法拆分并行阶段,或者用Mock减少外部依赖。

2.5 一个简单的CI配置文件示例

拿GitLab CI举个例子。这是我比较常用的模板,去掉了业务细节,只保留骨架。

stages:
  - lint
  - build
  - test
  - integration
  - archive

lint_job:
  stage: lint
  script:
    - eslint src/

build_job:
  stage: build
  script:
    - docker build -t myapp:$CI_PIPELINE_ID .
  artifacts:
    untracked: false

unit_test_job:
  stage: test
  script:
    - npm test -- --coverage
  coverage: '/Lines\:\s(\d+\.\d+%)/'

integration_test_job:
  stage: integration
  script:
    - npm run test:integration
  dependencies:
    - build_job

archive_job:
  stage: archive
  script:
    - docker push registry.example.com/myapp:$CI_PIPELINE_ID
  only:
    - main
曾经踩过一个坑: 我让单元测试和集成测试用同一个Docker镜像,但集成测试需要依赖外部Mock服务。结果单元测试阶段Mock服务还没就绪,导致测试全挂。后来我把Mock服务的启动提前到`before_script`中,并且用wait-for-it脚本等它ready。

2.6 小结与下一步方向

这一章我们把CI/CD的骨架搭起来了。持续集成不是花架子,它需要你认真对待每个环节。尤其是集成测试这一步,很多人用真实服务跑,慢且不稳定。所以我才在后期大力推广Mock回放技术——它能让集成测试跑得像单元测试一样快。