从零到一:我在项目中实践持续集成工具的真实故事
开头:为什么我会走上CI之路?

还记得两年前我刚加入一家创业公司的时候,那时候整个团队只有不到10个人。前端、后端、测试,大家都是“多面手”。项目代码量还不大,但每次上线都像在“赌命”——经常有人忘了提交某个配置文件,或者开发环境和生产环境版本不一致,导致线上服务莫名崩溃。
最严重的一次是某天早上上线之后,我们发现数据库连接不上了,排查了一上午才发现是某个开发人员误改了环境变量,把本地配置提上去了。那次事故让我彻底下定决心:我们必须要有持续集成(CI)!
于是,我开始研究 CI 工具、搭建自动化流程,并在一个迭代周期内把它完整地落地到了我们的主项目中。这篇文章就来聊聊这段经历里遇到的坑、踩过的雷,以及收获的那些实实在在的好处。
问题描述:项目初期的混乱状态

我们当时的项目是一个前后端分离的电商平台,技术栈包括 Vue 前端 + Spring Boot 后端 + MySQL 数据库 + Nginx 作为静态资源服务器。虽然规模不大,但随着团队人数增加到十几人,协作效率明显下降。
主要的问题体现在几个方面:
- 手动打包部署效率低
- 每次发布都需要有人手动执行命令,打包上传。
- 版本控制不清晰
- 不同分支合并频繁,经常出现冲突甚至错误合并。
- 构建失败反馈慢
- 有时候提交的代码根本编译不过,但要等到部署时才被发现。
- 缺乏自动测试环节
- 单元测试存在,但从没真正跑过,基本靠人工点击测试。
- 环境差异导致故障频发
- “在我电脑上没问题”的情况层出不穷。
这些痛点让我意识到,如果不建立起一套完整的 CI 流程,这种“作坊式”开发模式迟早会出大问题。
解决方案:选型与实施过程
第一步:选择合适的CI工具
当时摆在面前的主流工具有 Jenkins、GitLab CI、GitHub Actions,后来我们也评估了 CircleCI 和 Travis CI,但考虑到以下几点,最终选择了 GitLab CI:
- 公司用的是自建 GitLab,已经集成了权限管理;
- 不想额外维护 Jenkins 的服务器资源;
- GitHub Actions 虽好,但公司不允许使用外部托管平台;
- GitLab CI 的
.gitlab-ci.yml配置相对简单直观; - 可以直接集成到 GitLab 的流水线视图中,便于查看进度。
第二步:确定CI的核心目标
我们为CI系统设定了以下几个核心目标:
- 自动化构建
- 自动化测试
- 提交后自动触发构建,失败及时提醒
- 区分不同环境(dev / test / prod)
- 构建产物集中管理
第三步:逐步构建流水线
我们最初是从一个简单的构建+测试流程开始的,后面逐渐扩展为完整的流水线结构。
初版CI配置:只做构建和测试
stages:
- build
- test
build_vue:
image: node:18
stage: build
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
test_backend:
image: openjdk:11-jdk
stage: test
script:
- cd backend
- ./mvnw test
这个配置虽然简单,但已经可以让我们在每次推送代码后看到构建结果。如果单元测试失败,流水线就会中断,同时GitLab会通过邮件通知提交者。
中期优化:引入部署任务
随着流程的稳定,我们开始将部署任务也纳入到CI中。
deploy_dev:
image: alpine
stage: deploy
script:
- scp -r dist/* user@dev-server:/var/www/app/
- ssh user@dev-server "systemctl restart nginx"
only:
- dev
这里有一个插曲:一开始我们尝试用 Ansible 来做部署,但因为运维能力不足,反而增加了复杂度。最后还是回归到了 shell 脚本的方式,事实证明更轻量、更容易维护。
最终形态:完整的多环境流水线
现在我们的 .gitlab-ci.yml 已经演化成一个完整的多阶段流程,涵盖不同分支、不同环境的处理逻辑。
variables:
DOCKER_REGISTRY: registry.example.com
before_script:
- echo "全局前置脚本"
stages:
- lint
- build
- test
- package
- deploy
eslint:
stage: lint
script:
- npx eslint .
build_frontend:
stage: build
image: node:18
script:
- npm install
- npm run build
artifacts:
paths:
- dist/
unit_test:
image: openjdk:11
stage: test
script:
- cd backend && ./mvnw test
dockerize:
image: docker:latest
stage: package
services:
- docker:dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $DOCKER_REGISTRY
- docker build -t registry.example.com/app:$CI_COMMIT_TAG .
- docker push registry.example.com/app:$CI_COMMIT_TAG

deploy_to_prod:
image: alpine
stage: deploy
script:
- echo "Deploying to production..."
# 这里调用了部署脚本或K8s更新
when: manual
only:
- master
可以看到,我们已经支持了多阶段、条件判断、人工审批等多个高级功能。
实施效果:带来的变化和收益
自从全面启用CI流程后,我们项目的稳定性有了非常明显的提升。
- 构建失败率显著下降
- 因为每次PR都要跑一遍构建和测试,大部分问题都在合入前就被发现。
- 上线效率提高
- 以前需要花2小时上线,现在一键点击流水线,10分钟就能完成部署。
- 团队协作更加顺畅
- 所有人都知道什么时候该做什么事,不用再依赖口头沟通确认状态。
- 自动化保障质量
- 我们的覆盖率统计和SonarQube也接入进来,代码质量更有保障。
- 出了问题能快速回滚
- 所有镜像都打标签,版本可追溯,出问题时可以直接切回到上一个稳定版本。
而且最关键的是,我们终于不再怕“谁改动了什么”这个问题了。
经验分享:给准备实践CI的朋友的建议
如果你也在考虑或者刚开始接触 CI,下面是我总结的一些实战经验:
1. 不要一上来就想做个完美的CI
我见过不少团队在建设CI之前就想着搞全自动化、多环境部署、灰度发布、熔断机制……其实完全没有必要。
建议:从最简单的构建+测试做起。 稳定后再扩展部署、镜像打包等环节。否则很容易一开始就陷入各种细节和配置陷阱中。
2. 合理利用已有的工具生态
比如你用的是 GitHub,那 GitHub Actions 就很适合;用 GitLab,那就优先用 GitLab CI;Kubernetes 用户也可以看看 Tekton 或 ArgoCD。
别自己造轮子,除非你真的有必要。
3. 写好.gitlab-ci.yml不是终点
YAML 文件只是表面。真正的难点在于:
- 如何组织好各个 job 的顺序
- 怎么传递信息(比如打包后的 artifact)
- 如何避免重复构建浪费时间
- 如何调试失败的任务并恢复流程
我们曾经有个job老是卡住不动,查了好几天才发现是因为某个脚本没有退出码返回,导致整个pipeline以为还在运行。
4. 关注开发者体验
一个好的CI不应该打扰开发节奏,而应该让他们“感觉不到它的存在”。
比如:
- 提供友好的报错提示(而不是一堆日志堆砌)
- 快速反馈构建状态(比如集成 Slack/企业微信通知)
- 易于重试和回滚
5. 定期Review你的CI流程
每个季度抽空 review 一下当前的 CI 配置,是否有些冗余 job?有没有可以优化的空间?是不是有新工具值得尝试?这样能保持流程的先进性和高效性。
结尾:CI不只是工具,更是一种文化

写到这里,我想说,持续集成不仅仅是技术上的实践,更是工程文化的体现。
它教会我们如何高效协作、如何尊重代码质量、如何建立可追溯的工作流。它背后承载的是对交付质量的责任感,是对团队效率的追求。
这两年来,从最初的抗拒到现在的习惯,我已经离不开这套自动化体系了。更重要的是,整个团队的工作方式也因此变得更加规范和自信。
如果你还没开始,不妨从今天就开始试试吧。哪怕只是一个简单的构建任务,也可能成为你迈向专业化的第一步。
“让机器去做重复的事,让人去专注创造价值。”
——这是我始终坚持的理念。

评论 0