云原生-04-Helm管理测试平台部署时模板values和回滚怎么设计

第一次把测试平台迁到 Kubernetes,都会先把注意力放在两件事上:

  • Helm 能不能把 Deployment、Service、Ingress 一次装起来
  • values.yaml 能不能把镜像、端口、环境变量配进去

这一步通常不难,难的是上线几轮之后出现的长期问题:

  • 测试环境和预发环境的 values 差异越来越大,没人说得清哪些差异是合理的
  • 一个 chart 同时承载 api、worker、scheduler、web,多服务模板互相影响
  • 回滚时 helm rollback 成功了,但数据库迁移、ConfigMap 变更、外部任务队列状态并没有一起回去
  • 某次只是改了一个环境变量,却导致探针、Ingress、资源限制也跟着一起漂
  • 同一套测试平台要支持不同项目组、不同命名空间、不同环境策略,chart 很快变成了大而乱的参数集合

这类问题说明 Helm 在测试平台场景里承担的角色,远不只是“安装工具”,而是:

把模板设计、配置分层、环境差异、发布收口和回滚策略收成一套可长期维护的工程边界。

这篇文章只讨论一个核心问题:

用 Helm 管理测试平台部署时,模板应该怎么拆,values 应该怎么分层,回滚应该怎么设计,才能让平台发布既能持续演进,又不会在配置和回滚上失控。

一、测试平台场景下,为什么 Helm 最容易失控

测试平台和普通单服务应用不同,常见对象至少包括:

  • Web 前端
  • API 服务
  • Worker 或任务执行器
  • 定时任务或调度器
  • Redis、MySQL、消息队列等外部依赖引用
  • Ingress、Secret、ConfigMap、ServiceAccount、PVC

如果这些对象都堆到一份大模板和一份大 values 里,后面很快会出现四类问题。

1. 服务边界不清,模板越改越脆

例如把 api、worker、scheduler 的容器模板写成一套高度条件化的 Deployment,短期看复用高,长期看会带来这些副作用:

  • 任意一个服务新增探针字段,都要改公共模板
  • 某个服务需要 sidecar,结果把全体服务模板都变复杂
  • worker 不需要 Service,却被公共模板强行带出一堆无用字段

2. 环境差异混进业务配置,values 越来越乱

测试平台通常至少有:

  • 本地开发
  • 测试环境
  • 集成环境
  • 预发环境

如果环境差异、业务配置、部署策略、密钥引用全部混在一份 values 文件里,最终会出现:

  • 同名字段语义不一致
  • 某些值只在一个环境里生效
  • 一次改动不知道应该落到 base、环境覆盖还是团队覆盖

3. 发布和回滚只剩命令动作,没有状态治理

把回滚理解成这条命令:

1
helm rollback test-platform 12 -n qa

命令本身没问题,但回滚真正失败的地方通常不在命令,而在下面这些对象没有统一收口:

  • 数据库 schema 已经前滚
  • Job 已经执行过一次且不可逆
  • 镜像标签被覆盖但 ConfigMap 没有同步恢复
  • 平台里的任务状态已经写入 Redis 或数据库
  • Ingress、灰度流量、外部回调配置仍然指向新版本

4. 发布物不可追溯,后面无法排查“到底发了什么”

如果 chart 包、镜像 tag、values 覆盖项、提交版本和发布单没有收口,出现线上故障时往往只能靠人工回忆:

  • 当时部署的是哪一版 chart
  • values 覆盖了哪些字段
  • 是改模板引起的问题,还是改镜像引起的问题
  • 某次紧急 hotfix 是否进入了正式 values 文件

所以 Helm 在测试平台里最应该先解决的问题,不是“渲染成功”,而是:

  • 模板边界是否稳定
  • values 分层是否可解释
  • 发布结果是否可追溯
  • 回滚路径是否真正可执行

二、先拆模板边界,不要先堆参数

Helm chart 越做越乱,根因通常不是参数太少,而是模板边界一开始没拆清。

更适合测试平台长期维护的做法,是按运行职责拆模板,而不是按资源种类堆文件。

1. 一个更实用的 chart 目录骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
charts/test-platform/
├── Chart.yaml
├── values.yaml
├── values-test.yaml
├── values-staging.yaml
├── values-prod.yaml
├── templates/
│ ├── _helpers.tpl
│ ├── serviceaccount.yaml
│ ├── secret-env.yaml
│ ├── configmap-common.yaml
│ ├── ingress.yaml
│ ├── api-deployment.yaml
│ ├── api-service.yaml
│ ├── worker-deployment.yaml
│ ├── scheduler-deployment.yaml
│ ├── migrate-job.yaml
│ ├── hpa-api.yaml
│ └── pdb-api.yaml
└── ci/
├── lint.sh
├── render.sh
└── release.sh

这个结构有几个关键点:

  • apiworkerscheduler 分开,不强行用一个 Deployment 模板套所有服务
  • 公共配置用 _helpers.tplconfigmap-common.yaml 承载,不把全部复用逻辑写进 Deployment
  • 数据迁移单独建 migrate-job.yaml,不和主应用 Pod 生命周期混在一起
  • 环境 values 独立存放,后续便于做 diff 和审计
  • ci/ 下保留渲染、校验、发布脚本,把 chart 使用方式固定下来

2. 哪些内容应该放进公共 helper

适合沉到 helper 的通常是稳定且跨服务通用的东西:

  • 命名规则
  • labels / selector labels
  • 镜像全名拼接
  • 通用 annotations
  • 通用环境变量片段

例如:

1
2
3
4
5
6
{{- define "test-platform.labels" -}}
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/part-of: test-platform
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}

但下面这些内容不适合为了“复用”硬塞进 helper:

  • 各服务不同的探针
  • 不同服务的启动参数
  • sidecar 组合
  • 资源限制
  • 卷挂载

这些内容变化快、差异大,硬做公共模板只会让 chart 更难维护。

3. 哪些对象应该独立模板,不要共用

测试平台里这几类对象最好独立:

  • api-deployment.yaml
    负责 Web/API 入口、探针、Service、Ingress 关联
  • worker-deployment.yaml
    负责队列消费、任务执行、日志输出、资源隔离
  • scheduler-deployment.yaml
    负责 cron 触发、任务下发、轻量守护
  • migrate-job.yaml
    负责数据库迁移、初始化数据、幂等校验

独立的价值不在于文件多,而在于后续修改某一类服务时不会误伤其它组件。

三、values 不该只分环境,还要分语义层

很多 chart 有环境 values,但依然混乱,因为它们只做了“文件拆分”,没有做“语义拆分”。

更能落地的方式,是把 values 先按语义分层,再按环境覆盖。

1. 最小可维护的 values 分层

一套比较稳的分层通常包括四层:

  1. values.yaml
    放默认值、稳定基础配置、开关默认值
  2. values-<env>.yaml
    放环境差异,例如副本数、域名、资源规格、节点选择
  3. values-<team>.yaml 或发布参数
    放项目组或单次发布差异,例如镜像 tag、灰度比例、单次开关
  4. 外部 Secret 引用
    放敏感信息来源,不把明文写进 chart 仓库

如果把所有内容都塞进 values-test.yaml,即使暂时可用,后面也很难解释每一项改动是“平台默认策略”还是“测试环境特例”。

2. 推荐的 values 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
global:
namespace: qa
imageRegistry: registry.example.com
domainSuffix: qa.example.com
pullSecrets:
- regcred

release:
version: "2023.09.09"
commitSha: "9ac52f1"
changeTicket: "REL-2319"

api:
enabled: true
replicaCount: 2
image:
repository: test-platform/api
tag: "2023.09.09-1"
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1
memory: 1Gi
env:
LOG_LEVEL: info
REPORT_EXPIRE_DAYS: "14"
probe:
readinessPath: /healthz
livenessPath: /livez

worker:
enabled: true
replicaCount: 3
queue:
names:
- ui-regression
- api-regression
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "2"
memory: 2Gi

scheduler:
enabled: true
replicaCount: 1
cron:
reconcile: "*/1 * * * *"

config:
externalConfigMap: test-platform-common

secretRefs:
app: test-platform-secret
mysql: mysql-conn
redis: redis-conn

rollback:
allowAutoRollback: false
maxHistory: 20

这个结构的核心不是字段多,而是每个字段尽量只表达一种含义:

  • global 放跨组件通用配置
  • release 放发布追溯信息
  • api/worker/scheduler 分别放组件自己的部署参数
  • configsecretRefs 显式表达“配置来自哪里”
  • rollback 表达发布策略,而不是运行参数

3. 哪些东西不要放 values

下面这些内容不适合直接写进 values 仓库:

  • 明文数据库密码
  • 明文第三方 token
  • 临时排障时加的 debug 参数长期保留
  • 只用于某次紧急修复的临时开关

更合适的方式是:

  • Secret 引用进 values,明文放在外部 Secret 管理系统
  • 临时参数通过单次发布参数注入,发布后清理
  • debug 开关设置默认关闭,并明确失效时间

四、配置策略不能只解决“怎么传值”,还要解决“谁可以改什么”

测试平台在 Helm 发布里很容易出现配置失控,不是因为 values 文件不会写,而是没有配置治理边界。

更适合落地的配置策略通常要回答四个问题:

1. 哪些配置可以跟 chart 一起版本化

适合跟 chart 一起管理的:

  • 副本数默认值
  • 探针路径
  • 资源申请默认值
  • Service 和 Ingress 结构
  • HPA 开关
  • 公共环境变量默认值

2. 哪些配置必须外置

更适合外置的:

  • 数据库连接串
  • Redis 密码
  • 第三方鉴权密钥
  • 对象存储访问凭证
  • 企业微信、钉钉等通知 token

3. 哪些配置只能通过发布流程改

下面这类配置不应该允许随手直接改 values 仓库:

  • 镜像 tag
  • 灰度比例
  • 是否执行迁移 Job
  • 是否启用某个高风险开关
  • 是否切换外部回调地址

这类字段更适合在流水线或发布单里显式填入,并留下审计记录。

4. 哪些配置要做变更门禁

这几类变更建议进入额外校验:

  • 探针调整
  • 资源限制下调
  • Service 端口变更
  • Ingress host/path 变更
  • Secret 引用目标变更
  • migration Job 镜像或命令变更

如果没有这层门禁,后面最常见的故障不是“代码坏了”,而是 values 改坏了。

五、回滚设计不能只靠 helm rollback

Helm 回滚可用,但只覆盖 Helm 能管理的资源版本,不等于整个测试平台就能安全回退。

更能落地的回滚设计应该拆成三层。

1. 模板回滚

模板回滚关注的是:

  • 哪一版 chart 被部署
  • 哪些模板发生过变化
  • 渲染后的资源对象是否能恢复到上一版

这部分可以依赖:

  • helm history
  • helm rollback
  • 发布前渲染产物归档

发布前建议固定保存:

1
2
3
4
5
helm template test-platform ./charts/test-platform \
-n qa \
-f values.yaml \
-f values-test.yaml \
> rendered/test-platform-20230909.yaml

这样回滚失败时,至少可以直接比较两次渲染结果。

2. 配置回滚

配置回滚关注的是:

  • 哪些 values 在本次发布中发生覆盖
  • ConfigMap / Secret 引用是否发生变化
  • 单次发布参数是否还原

这部分建议发布系统额外记录一份“本次实际输入”:

字段 示例
chartVersion 0.5.3
imageTag.api 2023.09.09-1
imageTag.worker 2023.09.09-1
overrideFiles values-test.yaml
setArgs api.replicaCount=3
migrate.enabled true
ingress.host test-platform.qa.example.com

如果没有这份记录,回滚时往往只能回 chart,回不清参数。

3. 数据与状态回滚

这部分最容易被忽略,也是最容易出事故的地方。

测试平台常见的不可逆对象包括:

  • 数据库迁移
  • 队列中的待执行任务
  • Redis 中的运行状态缓存
  • 报告生成后的对象存储索引
  • 回调系统里的发布状态

所以每次发布前都应该先判断这次变更属于哪类:

  • 纯镜像替换,可直接回滚
  • 配置变更,需要配置恢复和进程重启
  • 数据结构变更,需要前置备份和双向迁移方案
  • 外部依赖变更,需要流量切换与回退脚本

如果数据库迁移不可逆,发布策略就不该写成“失败即自动回滚”,而应该写成:

  • 先阻断新流量
  • 保留现场
  • 切回上一版镜像和上一版 ConfigMap
  • 执行兼容性检查
  • 必要时再走数据修复

六、一个更适合测试平台的最小发布与回滚骨架

只讲原则不够,Helm 在测试平台里最好至少固化下面这套最小骨架。

1. 发布前检查

  • 检查 chart lint 是否通过
  • 检查 helm template 是否成功
  • 检查环境 values 是否齐全
  • 检查 Secret 引用是否存在
  • 检查 migration Job 是否需要执行
  • 检查本次是否涉及探针、Ingress、资源限制、回调地址变更

2. 发布执行顺序

1
2
3
4
5
6
7
8
9
1. 生成发布单并记录 chart 版本、镜像版本、values 覆盖项
2. 执行 helm lint
3. 执行 helm template 并归档渲染结果
4. 执行变更 diff,确认高风险项
5. 如涉及迁移,先执行预检或 dry-run
6. 执行 helm upgrade --install
7. 观察 Pod、探针、Ingress、关键接口、worker 心跳
8. 补充执行平台冒烟与回归任务
9. 达到稳定窗口后关闭发布单

3. 回滚执行顺序

1
2
3
4
5
6
7
1. 先判定故障属于模板问题、配置问题还是数据问题
2. 冻结新的发布动作,保留当前渲染结果和 Pod 现场
3. 切回上一版 chart / values 组合
4. 核对 ConfigMap、Secret 引用、Ingress、探针是否同步恢复
5. 核对 migration Job 是否已执行,必要时走数据补偿
6. 执行平台健康检查、关键链路冒烟、任务调度验证
7. 完成事件记录,补回滚原因和后续治理动作

4. 一个更稳的发布命令骨架

1
2
3
4
5
6
7
8
9
10
11
helm upgrade --install test-platform ./charts/test-platform \
-n qa \
-f values.yaml \
-f values-test.yaml \
--set release.version=2023.09.09 \
--set release.commitSha=9ac52f1 \
--set api.image.tag=2023.09.09-1 \
--set worker.image.tag=2023.09.09-1 \
--history-max 20 \
--wait \
--timeout 10m

这里有几个实践点:

  • --history-max 控制 Helm 历史数量,避免 release 记录无限膨胀
  • --wait--timeout 让发布结果更明确,不要在 Pod 尚未 ready 时就宣布成功
  • 版本和提交号显式入参,方便排障和审计

七、常见坑不是 Helm 命令问题,而是设计问题

1. 一个 chart 管所有服务,但没有组件级开关

现象:

  • 某个环境只需要 api + worker,却因为 scheduler 模板固定启用导致发布失败

更合适的做法:

  • 每个组件显式 enabled
  • 模板内部只判断本组件开关,不在多个模板交叉引用复杂条件

2. values 里既有默认值,又有临时排障值

现象:

  • 某次排障把 LOG_LEVEL=debug 写进环境 values,后面长期忘记清理

更合适的做法:

  • 临时调试项走单次发布参数
  • 发布完成后明确回收
  • 高风险调试字段做默认关闭

3. migration Job 和应用升级绑死

现象:

  • 每次 helm upgrade 都顺带跑迁移,失败后应用和数据状态一起卡住

更合适的做法:

  • 迁移 Job 独立控制
  • 迁移前做版本兼容性检查
  • 明确“可逆迁移”和“不可逆迁移”的处理策略

4. 把 Secret 明文直接写进 values 仓库

现象:

  • chart 仓库和流水线日志里都留下敏感信息

更合适的做法:

  • values 只保留 Secret 名称或 key 引用
  • 敏感值交给外部 Secret 管理系统

5. 回滚后只看 Pod Ready,不看平台能力是否恢复

现象:

  • Pod 全绿,但任务调度、报告生成、Webhook 回调仍然异常

更合适的做法:

  • 回滚成功判定必须包含平台关键链路:
    • 登录与鉴权
    • 任务下发
    • worker 消费
    • 报告写入
    • 告警回调

八、真实案例:一次“回滚成功”但平台仍然不可用的发布事故

这一类问题在测试平台里很典型,因为表面上 Helm 和 Kubernetes 都显示成功,但平台本身并没有恢复。

1. 场景

某次测试平台发布的目标是:

  • 升级 api 和 worker 镜像
  • 新增一组报告保留天数配置
  • 调整 worker 的资源限制
  • 同时执行一版数据库迁移

平台部署在 qa 命名空间,使用 Helm 管理,发布前已经保留了上一版 release。

2. 执行

发布流程做了这些动作:

  • 提交了 chart 模板更新
  • values-test.yaml 里新增报告配置
  • 通过流水线执行 helm upgrade --install
  • migration Job 跟随发布自动执行

发布命令返回成功,Pod 也陆续 Ready。

3. 现象

十几分钟后开始出现两类问题:

  • 平台页面可以打开,但新任务提交后一直停留在 排队中
  • worker 日志里不断出现获取任务成功但写报告失败

值班侧最先采取的动作是回滚,于是直接执行了:

1
helm rollback test-platform 17 -n qa

回滚命令成功,Deployment 版本也回去了,但问题依然存在:

  • 页面依然能打开
  • 任务依然无法正常完成
  • 报告依然写不进去

4. 排查

后续排查按下面顺序收敛:

  1. 核对 Helm history,确认模板和镜像确实已经回到上一版
  2. 比对回滚前后的渲染结果,发现 ConfigMap 中新增的报告配置字段已消失
  3. 检查 migration Job 记录,确认数据库迁移已经执行且不可逆
  4. 查看 worker 日志,发现旧版本 worker 在读取新 schema 时字段映射失败
  5. 再检查 Redis 队列状态,发现已有一批新版本生成的任务元数据还在消费链路中

根因最终明确:

  • 这次故障表面上看像是 Helm 发布问题
  • 实际上是“应用版本回去了,但数据结构和运行状态没有一起回去”

也就是说,命令层面的回滚成功了,系统层面的回滚并没有成功。

5. 修复

后续修复动作分成三步:

  1. 先暂停调度器和新任务入口,阻断新增写入
  2. 针对数据库补一版兼容字段映射,让旧版本 worker 能继续消费
  3. 清理 Redis 中由新版本生成且不兼容的任务元数据,再恢复 worker 和 scheduler

这次之后,发布策略做了三项调整:

  • migration Job 不再默认跟随所有发布执行,改为显式开关
  • 涉及 schema 变更的发布必须先给出回退策略,不能只写 helm rollback
  • 发布单中增加“数据与状态回滚方式”字段,没有填清楚不能进入执行

这个案例说明了一件很关键的事:

Helm 能解决资源版本回退,但测试平台的真实回滚必须同时覆盖模板、配置、数据和运行状态。

九、发布治理做得好,Helm 才不会变成新的配置负债

Helm 在测试平台里真正应该沉淀的,不是多复杂的模板技巧,而是下面这些长期能力:

  • 模板边界稳定,服务之间不互相污染
  • values 分层清楚,能解释每一项差异
  • 发布输入可审计,能回答“这次到底发了什么”
  • 回滚不是一句命令,而是一套可执行的恢复路径
  • 每次事故之后,chart、values、发布单和回滚策略都能反向优化

如果这些能力没有建立,Helm 即使暂时能把资源装起来,后面也会慢慢变成另一种形式的配置债务。

而对测试平台来说,最不能接受的不是某次发布失败,而是:

发布已经复杂到没人敢改,回滚已经存在但没人敢用。

所以 Helm 管理测试平台部署时,最值得优先做的并不是追求模板炫技,而是先把三件事收稳:

  • 模板按职责拆开
  • values 按语义分层
  • 回滚按模板、配置、数据三层设计

只有这样,测试平台的 Kubernetes 发布链路才会真正变成可持续演进的工程资产,而不是一次次靠经验救火的操作集合。