某个周三晚上,南京软件大道。楼里空调停了,只剩我们三个。后端在翻日志,测试在群里发截图,我盯着 GitLab 的流水线页面,绿色的小勾停在 deploy staging 那一步,死活不动。
不是代码没编译。不是单元测试挂了。问题出在测试环境的数据库——上一轮部署留下的脏数据,把新迁移脚本卡死了。手动连上去删表?可以。但第二天还会再来一次。

那晚我意识到,CI/CD 不是买来的 YAML 模板。它更像团队协作的体检报告。哪里疼,哪里虚,流水线全给你抖出来。
流水线不是自动贩卖机

很多人把 CI/CD 想成自动贩卖机:代码投进去,商品掉出来。可现实里,投进去的是硬币还是游戏币,机器分不清。持续集成,说白了就是频繁把代码合进主干,每次合完立刻跑构建和测试。持续交付,是保证这些代码随时能发布。持续部署,是直接推到生产。三个词挨着,风险差着量级。
我见过最离谱的流水线,Jenkins 跑 47 分钟。其中 20 分钟在下载依赖,10 分钟在等一个没人维护的 Selenium 用例。开发改一行文案,要等快一个小时才知道有没有把登录页搞崩。这不是自动化,这是自动折磨。
问题往往不在工具。GitLab CI、Jenkins、GitHub Actions、Argo CD,都能干活。真正卡人的,是环境不一致、测试不可靠、权限放太开,还有没人对失败负责。流水线红了,群里艾特一下,然后呢?继续推代码。红着红着,大家就麻了。
把环境当成代码 别当成宠物
宠物和牲口的区别,老运维都懂。宠物病了要治,牲口病了换一头。测试环境如果靠人手改配置、手动装依赖、手动导数据,那它就是宠物。还是那种脾气很差的宠物。
我们的做法很土:用 Docker Compose 把中间件拉起来,数据库迁移脚本跟代码一起进仓库,测试数据用工厂函数生成,不直接用生产脱敏数据。流水线里每次部署先重建 schema,再跑迁移,再灌最小数据集。慢一点。但可重复。

有人问,生产数据更真实啊。对。但真实数据里藏着半年没人清理的脏记录,还有字段长度超标的意外惊喜。用它们做自动化测试,等于每天开盲盒。偶尔开一次可以,天天开,团队会疯。
配置也一样。别把 staging 的数据库密码写在 Jenkins 的 shell 脚本里。用凭据管理,按环境隔离。最小权限这四个字,出事之前像废话,出事之后像遗嘱。
让失败快点发生 让恢复更便宜

CI/CD 的目标不是永远不失败。是失败的时候,你早点知道,而且能很快退回去。快,便宜。就这两个词。
所以测试要分层。单元测试几十秒跑完,接口测试几分钟,端到端测试别塞太多。很多团队反过来,单元测试稀稀拉拉,E2E 用例又长又脆,最后流水线变成薛定谔的猫。你永远不知道这次红是因为代码,还是因为某个按钮的文案变了。
部署策略也是。小团队别一上来就搞蓝绿、金丝雀、服务网格。先把回滚做明白。发布包版本化,数据库迁移可向前兼容,旧版本和新版本能同时跑一阵。这比什么花哨的发布策略都实在。
监控要接进流水线。部署完成不是终点,是观察期的起点。错误率、延迟、日志关键词,哪怕先接一个简单的健康检查。别等用户打电话说页面白了,你才知道生产挂了。
还有一点,流水线要有主人。不是某个人,是某个值班角色。谁红谁看,看不了就拉人。没有主人的流水线,最后会变成公司里最贵的装饰品。
小团队的行动清单

如果你正在从零搭 CI/CD,或者想救活一条半死不活的流水线,可以按下面这几步走。不复杂,但有用。
- 先跑通最短路径:代码提交、构建、单元测试、部署到测试环境。别急着加代码扫描、性能测试、安全扫描。先让主流程稳定。
- 把构建时间压到 10 分钟以内:缓存依赖,并行任务,拆掉无关步骤。超过 15 分钟,开发者就会开始摸鱼。
- 测试失败必须可复现:随机失败的用例,要么修,要么删。别留着过年。
- 生产部署要有刹车:手动审批、发布窗口、一键回滚。自动部署不等于无人驾驶。
- 记录每次失败:不是写检讨,是看趋势。同一类问题重复三次,就该改流程,而不是骂人。
说句实话。CI/CD 不会让烂代码变好,也不会让混乱的团队突然自律。它只是把原本藏在深夜、藏在某个人电脑里的问题,提前摆到桌面上。桌面上不好看。但比生产事故好看。
那晚我们最后没删表。加了一个迁移前的数据校验,把脏数据挡住。流水线重新跑,绿了。凌晨两点,新街口打车排队 30 人。我站在路边想,所谓工程效率,有时候就是让你少加一次这种班。