TT Lab
开始
学习 学习路径 课程

CI/CD 流水线

开发环境过了、生产却不过的真正原因

在 TT Lab 中继续学习

一句话总结

如果每个环境都重新构建,每个环境得到的就是不同的东西。把只构建一次的制品原样晋级,是解决这个问题唯一的根本办法。

为什么需要它

很多地方的流水线是这个样子的。

dev 브랜치  → 빌드 → dev 배포
stage 브랜치 → 빌드 → stage 배포
main 브랜치  → 빌드 → prod 배포

构建了三次。源码相同,所以人们以为结果也相同,其实不然。两次构建之间,这些东西会发生变化:

所以,在 dev 上通过的测试,对 prod 镜像来说毫无意义。因为测试的东西和部署的东西不是同一个东西。

构建一次,晋级多次

소스 커밋 → 빌드 1회 → 아티팩트(불변) ─┬→ dev 배포 → 테스트
                                      ├→ stage 배포 → 검증
                                      └→ prod 배포

关键在于制品是不可变的。在 dev 验证过的那些字节,原样送往 prod。这样“因为环境而不同”这个变量就消失了,剩下的差异只有配置和数据——这是我们可以控制的。

晋级不是重新构建,而是更换引用。

# gitops/prod/kustomization.yaml
images:
  - name: registry/backend
    newTag: v0820-1500      # ← 이 한 줄을 고치는 커밋이 배포다

LabHub 自身就是这样运转的。镜像只构建一次,提升 dev overlay 的标签进行验证,通过之后,再把同一个标签提升到 prod overlay。部署记录就是 git 日志。

配置放在制品之外

要让同一个制品用于多个环境,就必须把随环境而变的东西挪到外面。

项目 镜像内 镜像外
应用代码 ✅
运行时、库 ✅
DB 地址、外部 API URL ✅ 环境变量/ConfigMap
凭据 ✅ Secret
日志级别、功能开关 ✅ 配置

一旦把 application-prod.yml 构建进镜像,这个镜像就成了 prod 专用,晋级模型也就垮了。

用什么来标识

制品需要一个可以回溯的名字。

latest 不能用在晋级模型中。无从知道它是哪个时间点的 latest,所以无法确定要回滚到什么。

门禁放在晋级之间

빌드 → [단위 테스트] → dev → [통합 테스트] → stage → [수동 승인] → prod

每个箭头前面的方括号就是门禁。门禁必须用退出码表达通过或阻止。如果在日志里打印“失败”,却返回 exit 0,流水线就会直接放行——这是实际中很常见的 bug。

回滚是晋级的反向操作

如果晋级是提升标签的提交,那么回滚就是退回到之前标签的提交。既不需要重新构建,也不需要紧急补丁。因为之前的制品仍然留在注册表里。

要做到这一点,就不能删除制品。制定保留策略时,如果只保留“最近 N 个”,就回不到比它们更旧的版本。

晋级实际上会在哪里出偏差

“只构建一次,只做晋级”这条原则很简单,但执行起来,会在三个地方漏掉。

标签会移动。 晋级了 myapp:v1.2.3,却有人用同一个标签再次推送,那么昨天验证的和今天部署的就成了不同的东西。用摘要部署,标签只当作人读的标牌使用。 如果注册表有标签不可变的设置,也要一并打开。

docker buildx imagetools inspect myapp:v1.2.3 --format '{{.Manifest.Digest}}'

按环境构建会悄悄复活。 只要混进一个 --build-arg ENV=prod 之类的参数,开发环境中测试过的镜像和生产镜像就在那一刻变成了不同的东西。差异只能放在运行时的配置里。构建参数里只要出现环境名称,就是一个信号。

配置跑进了镜像。 为图方便把默认配置文件放进镜像,那些值总有一天会在生产环境被使用。配置应当从外部注入,并让它缺失时启动失败,这样更安全。悄悄地靠默认值运行是最糟的。

门禁放在晋级之间,但同一件事不要测两遍。 单元测试在构建时做一次,集成测试在晋级到开发环境之后做一次,压力测试在晋级到 staging 之后做一次。有不少流水线会在生产晋级前再跑一遍单元测试,那只是在浪费时间,不会带来任何新信息。

回退必须是重新晋级旧摘要。 为了回退而重新构建的话,因为这期间依赖发生了变化,回退得到的东西就与以前不同。所以不要删除旧制品,至少保留几代。

记录什么在哪里。 如果不能在一个画面上看到每个环境现在是哪个摘要,出故障时就得花时间去查明“生产环境上到底跑着什么”。

在现场相遇的样子

下一项检查要关注什么

接下来的实验会亲手验证本文的主张。先根据提交给发布命名,在相当于开发环境的仓库中只生成一次制品,然后不用标签而用摘要来指定,把它迁移到 staging 和生产环境,抓住标签开始指向不同内容的那一刻,并用一个文件记录每个环境里有哪个摘要。实验之后的测验,则要判断构建与晋级的区别、不可变摘要、外部配置和保留策略对回滚可行性的影响。