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的基础设施,搞不好后面全白搭。