4、持续集成入门:CI/CD概念解析、Jenkins vs GitLab CI vs GitHub Actions选型、CI流水线设计原则

4.1 先聊聊CI/CD到底是什么

说实话,我刚入行那会儿,对CI/CD的理解就是「自动编译、自动部署」。后来踩了不少坑才明白,这玩意儿远没那么简单。

持续集成(CI),说白了就是:你每次提交代码,系统自动帮你拉下来、编译、跑测试、做静态检查。我见过太多团队,开发写完代码往仓库一推,第二天才发现编译不过。嗯,这种痛苦我懂。

持续交付(CD),是在CI的基础上,把构建好的产物自动部署到类生产环境。注意,是「类生产」,不是直接上生产。我曾经在一个项目中,CD流水线跑完,测试环境自动就更新了,开发直接能看到效果,效率提升很明显。

持续部署(CD),这个更激进——代码通过所有检查后,自动部署到生产环境。我个人建议,除非你的自动化测试覆盖率达到90%以上,否则别轻易尝试。我在金融项目里就吃过这个亏,一次自动化部署把配置搞错了,差点酿成事故。

核心区别一句话总结:

  • CI:保证代码合入后不出问题
  • 持续交付:保证随时可以发布
  • 持续部署:自动发布到生产

4.2 三大主流工具选型:Jenkins vs GitLab CI vs GitHub Actions

选工具这事儿,我踩过的坑比走过的路还多。咱们一个一个聊。

4.2.1 Jenkins:老牌劲旅,但有点重

Jenkins我用了五六年,插件生态确实丰富,几乎什么都能干。但说实话,它的配置和维护成本不低。我记得有一次升级插件,结果整个流水线崩了,排查了一整天。

适合场景:

  • 企业有自建机房,需要完全控制CI/CD环境
  • 项目复杂,需要高度定制化流水线
  • 团队有专职运维人员

不适合场景:

  • 小团队,没精力维护Jenkins服务器
  • 追求开箱即用

4.2.2 GitLab CI:一体化体验,我比较推荐

GitLab CI是我目前最常用的工具。它跟GitLab仓库深度集成,你只需要在项目根目录放一个.gitlab-ci.yml文件,流水线就自动跑起来了。

我个人特别喜欢它的Runner机制——你可以用共享Runner,也可以自建专用Runner。我在一个数据合规项目里,就是自建了Runner放在内网,既保证了安全,又享受了CI的便利。

小技巧:GitLab CI的rules关键字非常强大,可以精确控制什么时候触发流水线。比如我只想在master分支和MR上跑完整流水线,其他分支只跑单元测试,用rules几行就搞定了。

4.2.3 GitHub Actions:云原生,适合开源项目

GitHub Actions的优势在于生态。你想做什么,去Marketplace搜一下,直接复用别人的Action就行。我曾经用三个Action就搭好了一套完整的CI流水线:代码检查、单元测试、构建镜像,前后不到半小时。

但要注意,GitHub Actions的免费额度有限。我在一个商业项目里,团队人多、提交频繁,一个月就跑超了额度,最后不得不切到自建Runner。

避坑指南:我曾经遇到过GitHub Actions的Workflow运行时间过长,导致并发数被限制。建议把长时间运行的任务拆分成多个Job,或者使用矩阵构建。

4.2.4 选型对比表

维度 Jenkins GitLab CI GitHub Actions
部署方式 自建服务器 SaaS/自建 SaaS
配置复杂度
插件/生态 极丰富 丰富 丰富
维护成本
适合团队 大型企业 中小型团队 开源/小团队

4.3 CI流水线设计原则:我总结的六条铁律

设计流水线这事儿,光看文档没用,得在实践中摸爬滚打。我把自己这些年踩过的坑,总结成六条原则。

4.3.1 原则一:流水线要快

你想想看,如果一次CI跑半小时,开发谁还愿意频繁提交?我见过最夸张的,一个项目CI跑两个小时,结果大家一天只提交一次代码,CI形同虚设。

怎么做:

  • 把测试分层:单元测试(快)和集成测试(慢)分开跑
  • 利用缓存:比如Maven的.m2目录、npm的node_modules
  • 并行执行:把独立的Job拆开并行跑

4.3.2 原则二:尽早失败

流水线应该把最容易失败、耗时最短的步骤放在最前面。比如代码风格检查、编译、单元测试。如果这些没过,后面的集成测试、部署就别跑了,浪费资源。

我的习惯:流水线前三个步骤永远是:代码检查 → 编译 → 单元测试。这三个步骤如果超过5分钟没跑完,我就觉得有问题。

4.3.3 原则三:每次提交都触发

别想着「等代码稳定了再跑CI」。我见过不少团队,只在合并到master时才触发流水线。结果呢?开发分支上的代码烂得一塌糊涂,合并时冲突不断。

正确的做法是:每个分支、每次提交都触发CI。当然,可以针对不同分支设置不同的流水线策略。比如feature分支只跑单元测试,master分支跑完整流水线。

4.3.4 原则四:流水线即代码

Jenkins的Pipeline DSL、GitLab CI的.gitlab-ci.yml、GitHub Actions的.github/workflows/*.yml,都是把流水线配置当成代码来管理。这样做的好处是:版本可控、可回滚、可审查。

我曾经在一个项目里,Jenkins的配置全靠手动在Web界面点来点去。后来服务器挂了,恢复配置花了两天。从那以后,我再也不用手动配置流水线了。

4.3.5 原则五:环境一致性

「在我机器上能跑啊」——这句话是不是很耳熟?CI流水线一定要保证构建环境的一致性。我推荐使用Docker容器来跑CI Job,这样开发、测试、生产环境完全一致。

小技巧:在GitLab CI里,直接用image: node:18-alpine指定Docker镜像,比在Runner上装一堆依赖省心多了。

4.3.6 原则六:反馈要及时

流水线跑完了,结果怎么通知到开发?我建议集成到即时通讯工具里。比如GitLab CI可以配置Webhook通知到企业微信或钉钉。失败时@对应负责人,成功时简单报个平安就行。

嗯,这里要注意:别什么信息都往群里发。我见过一个团队,每次构建成功都发一条消息,结果群里全是「构建成功」的刷屏,真正重要的失败信息反而被淹没了。

4.4 一个简单的CI流水线示例

最后,我拿GitLab CI举个例子,展示一个符合上述原则的流水线配置:

stages:
  - lint
  - test
  - build
  - deploy

# 尽早失败:先做代码检查
lint-job:
  stage: lint
  image: node:18-alpine
  script:
    - npm install
    - npm run lint
  only:
    - merge_requests
    - master

# 并行跑单元测试
unit-test:
  stage: test
  image: node:18-alpine
  script:
    - npm install
    - npm run test:unit
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/

# 集成测试单独跑
integration-test:
  stage: test
  image: node:18-alpine
  script:
    - npm install
    - npm run test:integration
  only:
    - master

# 构建Docker镜像
build-job:
  stage: build
  image: docker:20.10.16
  services:
    - docker:dind
  script:
    - docker build -t myapp:${CI_COMMIT_SHORT_SHA} .
    - docker push myapp:${CI_COMMIT_SHORT_SHA}
  only:
    - master

这个配置看起来简单,但已经包含了「尽早失败」「并行执行」「环境一致性」这几个原则。你想想看,如果每次提交都能这样自动跑一遍,代码质量能差到哪去?

好了,这一章的内容就到这里。下一章咱们聊聊「版本控制与分支策略」,这可是CI/CD的基础设施,搞不好后面全白搭。