第 14 篇:Helm 与 Kustomize 多环境治理
0. 本篇定位
这是容器技术路线的第 14 篇。上一篇我们已经把后端服务放进 Kubernetes,并能用 Deployment、Service、ConfigMap、Secret、Ingress 等对象描述一个可运行的部署模板。本篇继续往前走一步:当同一个服务要部署到 dev、test、staging、prod 多个环境时,如何避免 YAML 复制粘贴,如何让差异可审查,如何让发布结果可回滚。
本文重点不是背 helm install 或 kubectl kustomize 的命令参数,而是建立一套工程判断:哪些内容应该放在 Chart、哪些内容应该放在 values、哪些内容适合用 Kustomize overlay patch、哪些内容必须交给密钥系统或 CI/CD 注入。读完后,你应该能解释一条后端发布链路中,模板输入、渲染结果、集群对象、release 记录和排障证据之间的关系。
本篇会把 Helm 与 Kustomize 放在同一张图里讨论,但不会把它们简单归为“二选一”。Helm 更擅长把一组可参数化的 Kubernetes 对象打包成可发布单元;Kustomize 更擅长在原生 YAML 上做环境级覆盖。真实团队常见的治理方式,是选一个作为主线,再用另一个补足边界,而不是把所有差异都塞进同一种机制。
1. 问题背景
多环境治理最常见的失败方式,是把 dev、test、prod 写成三份看似相同、实际不断漂移的 YAML。今天测试环境改了镜像 tag,明天生产环境忘了同步资源限制,后天预发环境临时加了一个环境变量,几周之后团队已经无法回答“这三个环境到底差在哪里”。此时发布不再是声明期望状态,而变成了对历史手工操作的猜测。
Helm 和 Kustomize 解决的正是这类问题:把“相同部分”沉淀成可复用结构,把“不同部分”显式表达出来,把“发布前最终会应用什么 YAML”变成可以在 CI 中渲染、校验、diff 和归档的证据。对后端工程师来说,这比单次部署成功更重要,因为生产事故往往不是某个命令不会写,而是发布输入不透明、环境差异不可见、回滚时不知道应该回到哪个版本。
需要先建立一个边界意识:环境差异并不等于所有东西都可以按环境随意改。镜像 tag、replica 数量、资源限制、域名、探针阈值、开关配置、依赖地址、密钥引用方式,都可能按环境不同;但对象类型、端口契约、健康检查语义、权限模型、数据持久化策略,不应该在不同环境里无约束漂移。多环境治理的目标,是让差异被看见、被限制、被验证,而不是让差异变得更方便地失控。
2. 核心概念
Helm 和 Kustomize 都作用在 Kubernetes manifest 生成阶段,但抽象方向不同。Helm 从“发布包”视角组织对象:Chart 里有模板、默认值、依赖和元数据;发布时输入 values,渲染出最终 YAML,并形成 release 记录。Kustomize 从“原生资源组合”视角组织对象:base 保存通用 YAML,overlay 通过 patch、namePrefix、images、configMapGenerator 等机制生成某个环境的最终 YAML。
| 对象 | 主要职责 | 不应该承担的职责 | 排障时先看什么 |
|---|---|---|---|
| Chart | 打包一组可发布的 Kubernetes 对象 | 承担业务运行逻辑 | Chart.yaml、templates/、渲染结果 |
| values | 给模板提供变量输入 | 保存真实生产密钥 | 实际传入的 values 层级 |
| template render | 生成最终 manifest | 保证集群语义一定正确 | helm template 输出与 schema 校验 |
| release | 记录一次安装或升级 | 替代 Git 变更记录 | helm history/get values/get manifest |
| base | 保存可复用原生 YAML | 包含所有环境差异 | base 对象是否稳定、可读 |
| overlay | 对 base 做环境覆盖 | 承载复杂条件逻辑 | kubectl kustomize 最终输出 |
| patch | 精确修改资源字段 | 大面积重写资源结构 | target 是否命中、字段路径是否正确 |
2.1 Helm 的发布包视角
一个 Chart 可以理解成“后端服务部署包”。它通常包含 Chart.yaml、values.yaml、templates/ 和可选的 charts/ 依赖目录。Chart.yaml 描述 Chart 名称、版本和应用版本;values.yaml 提供默认输入;templates/ 中的 Deployment、Service、Ingress、ConfigMap 等模板会读取 values 并渲染成 Kubernetes YAML。
Helm 的关键价值不是“少写 YAML”,而是把一组对象作为一个 release 管理。一次 helm upgrade --install 不只是 apply 多个文件,还会记录这次 release 使用的 Chart、values 和 manifest。出问题时可以用 helm history 看发布序列,用 helm get values 看实际输入,用 helm get manifest 看当时渲染结果,再结合 Kubernetes 事件和 Pod 日志判断问题发生在模板输入、渲染结果、集群准入还是应用运行阶段。
因此 Chart 的工程边界要尽量清晰:模板可以表达对象结构和少量条件分支,但不应该写成复杂程序;values 可以表达环境差异,但不应该保存密钥明文;helper template 可以复用 labels、name、annotations,但不应该隐藏关键字段,让评审者看不到最终对象关系。
2.2 Kustomize 的原生 YAML 视角
Kustomize 的核心思想是“先有一份可读的原生 YAML,再按环境叠加差异”。base 目录里保存 Deployment、Service、ConfigMap 等通用对象;overlays/dev、overlays/prod 等目录通过 kustomization.yaml 引用 base,并用 patch 修改副本数、镜像 tag、资源限制、Ingress host 等环境字段。
这种方式的优点是接近 Kubernetes 原生对象,评审者不需要先理解模板语法,就能看懂 base 的目标状态。它特别适合对象结构稳定、环境差异集中在少量字段上的场景。例如一个内部 API 服务在不同环境只改变镜像、replica、资源和域名,用 Kustomize overlay 会比写大量 Helm 条件语句更直观。
但 Kustomize 也有边界。patch 过多时,最终结果会变得难以预测;overlay 层级过深时,读者必须在多个目录之间跳转;如果把复杂业务条件塞进 patch,治理成本会迅速上升。Kustomize 最适合表达“在这份基础对象上,某个环境明确改了哪些字段”,不适合替代发布包管理、依赖管理和 release 历史。
2.3 环境差异的治理边界
多环境治理的核心问题不是“差异能不能配置”,而是“差异是否有边界”。建议把字段分成四类:第一类是必须统一的契约字段,例如容器端口、Service selector、健康检查路径、权限模型;第二类是允许按环境变化的运行字段,例如 replica、resources、autoscaling、ingress host;第三类是由 CI/CD 注入的发布字段,例如 image tag、git sha、build number;第四类是不能进入普通 values 的敏感字段,例如数据库密码、第三方 token、证书私钥。
一个可靠的 values 分层通常长这样:Chart 内置 values.yaml 放安全默认值;仓库中保存 values-dev.yaml、values-staging.yaml、values-prod.yaml 表达环境差异;CI 使用 --set image.tag=$GIT_SHA 或临时 values 文件注入构建产物;密钥只传引用名,例如 existingSecret: order-service-db,真实密钥由 External Secrets、Sealed Secrets、SOPS 或云厂商密钥系统管理。
治理边界还要体现在评审规则上。不是所有 values 改动都应该同等看待:改 replica 和改数据库地址的风险不同,改镜像 tag 和改 Service selector 的风险不同,改 ConfigMap 和改 Secret 引用的验证方式也不同。好的模板结构会让这些差异在 PR diff 中清晰可见,而不是藏在一长串条件渲染逻辑里。
3. 运行机制
从发布链路看,Helm 与 Kustomize 都属于“manifest 生成与发布前治理”层。它们的输出不是直接的业务行为,而是一组 Kubernetes 对象声明。真正的运行仍然由 Kubernetes 控制器完成:Deployment 创建 ReplicaSet,ReplicaSet 创建 Pod,Service 选择 endpoints,Ingress 或 Gateway 接入流量,ConfigMap 和 Secret 注入配置。
一条可治理的链路通常是:源码合并后构建镜像并生成不可变 tag;CI 选择目标环境的 values 或 overlay;渲染最终 manifest;执行 lint、schema 校验、策略校验和 diff;通过审批后执行 upgrade 或 apply;发布系统记录输入、输出、操作者、时间和结果;出现异常时从 release 记录、渲染 YAML、集群事件、Pod 状态和应用日志逐层追证。
3.1 从输入到最终 manifest
Helm 的输入链路一般是 Chart + values layers + CLI overrides。当执行 helm template 或 helm upgrade 时,Helm 会按优先级合并 values:Chart 默认值最低,多个 -f 文件按顺序覆盖,--set 或 --set-file 的优先级更高。模板读取合并后的 .Values,再生成最终 manifest。
Kustomize 的输入链路一般是 base + overlay kustomization + patches/generators/images。overlay 引用 base 后,可以统一替换镜像、生成 ConfigMap、给资源加 label,也可以用 strategic merge patch 或 JSON patch 修改具体字段。最终输出必须通过 kubectl kustomize overlays/prod 或 kustomize build overlays/prod 查看,而不能只看某个 patch 文件就判断结果。
这两条链路都要求团队保存“最终 manifest”这个证据。因为出问题时,单独看模板或 patch 都不够:模板可能渲染错,patch 可能没有命中,values 可能被 CI 覆盖,集群准入控制也可能拒绝某些字段。最终 manifest 是连接发布输入和集群状态的关键中间证据。
3.2 release 与期望状态
Helm 的 release 是一个很重要的治理对象。它记录“某个命名空间里,某个 release 名称对应的一次部署历史”。当你执行 helm upgrade --install order-service ./chart -f values-prod.yaml,Helm 不只是把 YAML 交给 Kubernetes,还会记录 revision。后续可以用 helm rollback 回到某个 revision,也可以用 helm diff upgrade 在升级前查看变更。
但 release 不是万能回滚。它能回滚 Kubernetes manifest,却不能自动回滚数据库 schema、外部密钥版本、消息队列状态或业务数据。生产发布时必须明确哪些变化属于 Helm release 可覆盖范围,哪些变化需要额外的迁移脚本、审批单或人工确认。否则“回滚成功”可能只代表对象声明回去了,应用依赖的外部状态并没有回去。
Kustomize 本身不提供 release 历史,它通常依赖 GitOps 工具或 CI/CD 系统记录提交、渲染结果和 apply 结果。如果使用 Argo CD、Flux 这类工具,Git commit 与应用状态之间会形成另一种 release 证据链。无论工具如何选择,核心都是同一件事:必须能回答“当前集群状态来自哪次代码变更和哪份环境输入”。
3.3 渲染、校验与 diff 的闭环
发布前闭环至少包含三类检查。第一类是渲染检查:Helm 用 helm lint、helm template,Kustomize 用 kubectl kustomize,确保模板或 overlay 可以生成 YAML。第二类是 Kubernetes 语义检查:使用 kubeconform、kubeval 或 kubectl apply --dry-run=server 验证字段是否被当前集群 API 接受。第三类是变更检查:Helm 用 helm diff upgrade,Kustomize 或 GitOps 链路用 kubectl diff、Argo CD diff 或渲染产物 diff。
这三个步骤不能互相替代。模板能渲染,不代表字段对集群版本合法;schema 合法,不代表变更符合团队策略;策略通过,也不代表发布后应用一定健康。CI 的目标不是证明“不会出事故”,而是尽早拦住低级错误,并让高风险变更在发布前被看见。
对后端服务来说,diff 尤其重要。一次看似普通的 values 修改,可能实际改变 Deployment 的 selector、删除 Service port、替换 Secret 引用或触发全部 Pod 重建。如果发布前没有 diff,事故发生后团队只能在现场反推“到底变了什么”。如果 diff 被归档,排障可以从事实开始,而不是从记忆开始。
4. 最小可运行示例
下面用一个订单服务说明两种组织方式。假设服务镜像是 registry.example.com/backend/order-service,容器监听 8080,生产环境需要 3 个副本、严格资源限制和独立域名。示例只覆盖核心结构,真实项目还需要 RBAC、HPA、PodDisruptionBudget、NetworkPolicy、监控注解和密钥系统。
4.1 Helm Chart 的目录与 values 分层
一个最小 Chart 可以这样组织:
charts/order-service/ Chart.yaml values.yaml values-dev.yaml values-prod.yaml templates/ deployment.yaml service.yaml ingress.yaml configmap.yamlvalues.yaml 放通用默认值,默认值要安全、保守、适合本地或测试环境:
image: repository: registry.example.com/backend/order-service tag: dev
replicaCount: 1
service: port: 80 targetPort: 8080
resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi
config: springProfilesActive: dev logLevel: INFO
secret: existingSecret: order-service-secretvalues-prod.yaml 只表达生产差异:
replicaCount: 3
image: tag: "2026-07-05.1"
ingress: enabled: true host: order.example.com
config: springProfilesActive: prod logLevel: WARN
resources: requests: cpu: 500m memory: 1Gi limits: cpu: "2" memory: 2Gi发布前应先渲染并检查最终 YAML:
helm lint charts/order-servicehelm template order-service charts/order-service -f charts/order-service/values-prod.yamlhelm diff upgrade --install order-service charts/order-service -f charts/order-service/values-prod.yaml注意密钥只出现引用名 existingSecret,不出现真实密码。真实密钥由集群内 Secret 或外部密钥控制器负责同步,Chart 只负责引用。
4.2 Kustomize base 与 overlay
如果团队希望保持 YAML 原生可读,可以用 Kustomize:
k8s/order-service/ base/ deployment.yaml service.yaml kustomization.yaml overlays/ dev/ kustomization.yaml patch-deployment.yaml prod/ kustomization.yaml patch-deployment.yaml ingress.yamlbase/kustomization.yaml 只声明通用资源:
resources: - deployment.yaml - service.yamloverlays/prod/kustomization.yaml 引用 base,并表达生产差异:
resources: - ../../base - ingress.yaml
images: - name: registry.example.com/backend/order-service newTag: "2026-07-05.1"
patches: - path: patch-deployment.yaml target: kind: Deployment name: order-servicepatch-deployment.yaml 只修改生产需要覆盖的字段:
apiVersion: apps/v1kind: Deploymentmetadata: name: order-servicespec: replicas: 3 template: spec: containers: - name: order-service resources: requests: cpu: 500m memory: 1Gi limits: cpu: "2" memory: 2Gi检查时不能只看 patch 文件,而要看最终输出:
kubectl kustomize k8s/order-service/overlays/prodkubectl apply --dry-run=server -k k8s/order-service/overlays/prodkubectl diff -k k8s/order-service/overlays/prod4.3 示例迁移到真实项目
从示例迁移到真实项目时,第一件事是稳定命名和 labels。Deployment、Service、ServiceMonitor、NetworkPolicy、Ingress 的 selector 必须一致且可预测,否则某个 overlay 修改了 label 后,流量可能悄悄断开。Helm 中建议用 helper template 统一生成 app.kubernetes.io/name、app.kubernetes.io/instance、app.kubernetes.io/version;Kustomize 中建议通过 commonLabels 或显式 labels 统一管理,但要小心不要改变 selector 语义。
第二件事是明确配置热更新语义。ConfigMap 改了以后,Pod 不一定自动重启;Secret 改了以后,应用也未必重新读取。Helm 常见做法是在 Pod template annotation 中写入配置 checksum,让配置变化触发滚动更新;Kustomize 常见做法是使用 configMapGenerator 生成带 hash 的 ConfigMap 名称。无论哪种方式,都要和应用读取配置的方式一致。
第三件事是把发布证据沉淀下来。CI 至少归档这几类内容:使用的镜像 digest、values 或 overlay 输入、最终 manifest、diff 结果、dry-run 结果、发布命令输出。真正排障时,这些证据比“当时应该是这个版本”可靠得多。
5. 配置逐行拆解
多环境治理中最容易出事故的字段,往往不是最复杂的字段,而是最普通、最常改的字段。镜像、资源、副本、域名、密钥引用和 patch target 都很简单,但它们直接影响发布结果、运行稳定性和安全边界。
5.1 镜像与版本字段
image.repository 应该表达镜像仓库位置,通常不建议按环境随意改变。开发、测试、生产如果使用不同 registry,也要通过清晰的环境 values 或 overlay 表达,并在 CI 中校验生产只允许来自可信仓库。否则最常见的事故就是生产部署了测试镜像,或者同一个 tag 在不同仓库指向不同内容。
image.tag 是发布链路中最关键的字段之一。生产环境应尽量使用不可变 tag 或直接记录 digest,避免 latest 这类可漂移标签。CI 注入 tag 时,要同时记录 Git commit、构建编号和镜像 digest。Helm 场景可以通过 --set image.tag=$GIT_SHA 注入;Kustomize 场景可以用 images.newTag 覆盖。无论哪种方式,发布单里都要能追到镜像构建记录。
appVersion 和 image.tag 不要混为一谈。appVersion 是 Chart 元数据,帮助人理解应用版本;真正决定 Pod 拉取哪个镜像的是 Deployment 中的 image 字段。排障时如果只看 Chart 版本,很容易误判实际运行的制品。
5.2 副本、资源与探针字段
replicaCount 或 spec.replicas 表达容量和可用性预期。开发环境一个副本通常够用,生产环境至少要结合负载、滚动发布策略、PodDisruptionBudget 和 HPA 判断。不要只把 replica 当成“环境差异字段”,它还关系到滚动更新期间是否有足够实例承接流量。
resources.requests 决定调度和容量预留,resources.limits 决定运行上限。后端 JVM 服务尤其要注意内存限制、堆大小和容器感知参数之间的关系。limits 过低会导致 OOMKilled,requests 过低会导致节点超卖和抖动。生产 values 里应该显式写资源,而不是继承开发默认值。
livenessProbe、readinessProbe、startupProbe 不是装饰字段。readiness 决定实例是否接流量,liveness 决定是否重启,startup 保护慢启动应用不被过早杀掉。Spring Boot 服务如果启动慢,应该配置 startupProbe 或合理的 initialDelay,而不是盲目拉大 liveness 阈值。
5.3 域名、配置与密钥字段
ingress.host、Gateway route 或内部域名通常是环境差异字段,但它们也会影响证书、DNS、网关规则和回调地址。生产域名不应该通过临时命令手工注入,应该在 values 或 overlay 中可审查,并在发布前 diff 中明确出现。
ConfigMap 适合保存非敏感配置,例如日志级别、开关默认值、依赖服务地址。需要注意的是,配置值改变并不自动等于应用行为立即改变。你必须知道应用是启动时读取、定时刷新,还是通过配置中心动态订阅。模板只负责注入,不负责保证应用正确消费。
Secret 的边界要更严格。values 中可以保存 Secret 名称、key 名称、外部密钥路径或引用关系,但不应该保存真实生产密码。生产密钥泄漏到 Git 后,修复不是“删掉那一行”这么简单,而是要轮换密钥、清理历史、审计访问范围,并把 CI secret scanning 纳入门禁。
6. 后端工程场景
后端服务的多环境治理,最终要落到团队协作场景:开发能在本地复现,测试能稳定验证,平台能审查变更,运维能定位事故,业务负责人能判断风险。Helm 与 Kustomize 只是工具,真正重要的是每个场景中输入、输出和责任边界是否清楚。
6.1 本地、测试与预发环境
本地环境追求低成本和快速反馈,可以简化副本数、资源限制和外部依赖,但不应该改变应用启动契约。比如本地可以使用 mock 依赖或测试数据库,却不应该把健康检查路径、容器端口、Service selector 改成另一套逻辑。否则本地成功没有迁移价值。
测试环境追求可复现。每次测试发布应该能回答:使用哪个镜像、哪些 values 或 overlay、依赖服务地址是什么、数据库 schema 版本是什么。测试环境如果长期靠手工修配置,会让缺陷复现变得困难,也会让生产发布失去预演意义。
预发环境追求生产相似性。它不一定承载生产流量,但应尽量使用接近生产的资源、网关、密钥引用方式和发布流程。预发环境最重要的价值,是提前暴露模板渲染、准入策略、权限、网络和配置注入问题,而不是只验证业务接口能不能返回 200。
6.2 CI/CD 中的渲染校验
CI/CD 不应该直接执行 helm upgrade 或 kubectl apply,中间至少要有渲染、校验和 diff。一个简化流程可以是:构建镜像并推送;生成镜像 digest;渲染目标环境 manifest;执行 helm lint 或 kubectl kustomize;执行 schema 校验和策略校验;生成 diff;人工或自动审批;发布;归档 release 证据。
策略校验可以覆盖团队约束,例如生产环境禁止 latest tag,必须设置 resources,禁止 privileged 容器,必须引用指定命名空间的 Secret,Ingress host 必须属于受控域名。工具可以是 OPA Gatekeeper、Kyverno、Conftest 或平台自研规则,关键是规则要在发布前暴露,而不是等集群拒绝或事故发生后再补。
CI 还要区分“构建时变量”和“运行时配置”。镜像 tag、Git SHA、构建时间属于构建产物元数据;数据库连接、业务开关、密钥引用属于运行配置。两者混在一起,会导致同一个镜像在不同环境行为不可解释,或者一次配置改动被迫重新构建镜像。
6.3 团队协作与审查责任
开发同学需要负责应用契约:端口、健康检查、配置读取方式、优雅停机、日志输出和指标暴露。平台同学需要负责模板边界:Chart 或 base 的结构、默认值、资源策略、权限模型、发布门禁。测试同学需要负责环境输入可复现:测试用例对应哪个镜像和配置。运维或 SRE 需要负责观测与恢复:告警、容量、回滚和事故证据。
PR 审查时不要只看 YAML 是否语法正确,而要看变更属于哪类风险。改镜像 tag 要关联构建记录;改资源要关联容量依据;改 Secret 引用要关联密钥变更单;改 selector 要格外谨慎,因为它可能让 Service 找不到 Pod;改 ConfigMap 要确认是否会触发滚动更新。
团队越大,越需要把“可以改什么、谁来审、怎么验证”写成约定。否则 Helm 和 Kustomize 只是把手工差异换成了 Git 差异,并没有真正治理。
7. 常见错误与排障
排障时不要从“我猜是哪一行 values 错了”开始,而要从证据链开始。先确定当前集群实际运行的对象,再反推它来自哪次发布、哪份 manifest、哪组 values 或 overlay、哪个镜像 digest。证据链越完整,定位越快。
7.1 Helm values 覆盖顺序错误
典型现象是:生产发布后镜像 tag、replica 或配置值不是预期值。第一步看发布命令和 CI 日志,确认 -f 文件顺序和 --set 参数;第二步用 helm get values RELEASE -n NAMESPACE --all 查看 release 实际 values;第三步用 helm get manifest 查看最终 YAML;第四步对比 PR 中期望的 values。
常见根因包括:values-prod.yaml 被较后的文件覆盖,CI 的 --set image.tag 注入了旧变量,布尔值或数字被 --set 类型转换,或者某个模板使用了错误的 values 路径。修复时不要只改命令,要把 values 分层规则写进 CI 模板,并在发布前输出合并后的关键字段摘要。
预防方式是减少覆盖层数,并约定优先级。例如默认 values 只放通用安全值,环境 values 只放环境差异,CI 只注入构建产物字段。超过三层覆盖时,就应该警惕后续维护成本。
7.2 Kustomize patch 没有命中
典型现象是:overlay 里明明写了 patch,但最终 Deployment 没变,或者改到了不该改的对象。第一步执行 kubectl kustomize overlays/prod 查看最终输出;第二步检查 patch target 的 kind、name、namespace 是否和 base 资源一致;第三步确认 patch 类型和字段路径是否适合当前对象结构。
Kustomize patch 的风险在于“文件存在不代表生效”。资源被 namePrefix 改名、namespace 不一致、patch target 写错、数组字段匹配失败,都可能导致 patch 未按预期工作。尤其是 containers 这类数组字段,如果 name 不匹配,就可能出现覆盖失败或结构异常。
预防方式是把最终输出纳入 CI,并对关键字段做断言。例如生产 overlay 渲染后必须有 replicas: 3,镜像 tag 必须等于当前构建号,resources 不能为空,Ingress host 必须是生产域名。只看 patch 文件不能证明最终状态。
7.3 发布后应用不健康
如果发布成功但 Pod 不健康,先区分是 Kubernetes 对象问题还是应用运行问题。对象层看 kubectl describe pod、事件、镜像拉取、资源限制、挂载、探针失败原因;应用层看容器日志、启动参数、配置读取、依赖连接、数据库迁移和 JVM 内存。不要一上来就回滚,也不要一上来就改 values。
Helm 场景要同时看 release 和 Kubernetes 状态:helm status 只能告诉你发布状态,不等于业务健康。Kustomize 或 GitOps 场景也一样,同步成功只代表对象已应用,不代表服务可用。真正的发布完成标准应该包括 rollout 完成、readiness 通过、关键接口探测通过、错误率和延迟没有异常。
如果回滚,必须明确回滚对象范围。Helm rollback 可以回到上一版 manifest,但如果本次发布同时执行了数据库迁移、外部配置切换或密钥轮换,单纯回滚 manifest 可能不够。事故记录中要写清楚“回滚了什么,没有回滚什么”。
8. 生产化边界
生产化不是把 dev values 改成 prod values,而是给模板、环境差异、密钥、发布和回滚补上边界。边界越清楚,事故时越少靠经验猜。
8.1 values 分层与默认值策略
默认 values 应该安全、可运行、低风险。它可以让新人快速启动服务,但不能假装自己就是生产配置。生产环境必须显式覆盖关键字段,例如镜像 tag、resources、replica、ingress、密钥引用、探针阈值。不要让生产继承“为了本地方便”的默认值。
环境 values 应该短而明确。它只描述环境差异,不复制整个对象。一个 values-prod.yaml 如果和 values-dev.yaml 大部分内容相同,说明抽象边界可能错了。重复越多,漂移越容易发生。
对关键字段可以建立 schema。Helm 支持 values.schema.json,可以限制字段类型、必填项、枚举值和格式。比如生产必须设置 resources.requests,image.tag 不能是 latest,ingress.host 必须符合域名格式。schema 不能替代业务审查,但能拦住大量低级错误。
8.2 密钥、权限与合规边界
密钥边界要从一开始就设计好。普通 values、ConfigMap、overlay patch 都不应该保存真实生产密钥。推荐做法是让模板只引用 Secret 名称,真实密钥由 External Secrets、Vault、云 KMS、Sealed Secrets 或 SOPS 管理,并通过 RBAC 控制谁能读取。
权限边界同样重要。Chart 不应该默认创建过大的 ClusterRole,ServiceAccount 也不应该复用 default。生产模板要明确每个服务需要哪些 API 权限,不需要访问 Kubernetes API 的普通后端服务,通常不应拥有额外权限。
合规边界包括审计和轮换。谁改了密钥引用,什么时候改的,是否触发轮换,是否影响旧 Pod,是否需要重启应用,这些都应该能追踪。密钥泄漏后的处理流程,也要比普通配置错误更严格。
8.3 回滚、审计与证据归档
生产发布必须归档输入和输出。输入包括 Chart 版本、values 文件、overlay 目录、CI 注入变量、镜像 digest;输出包括最终 manifest、diff、dry-run 结果、release revision、rollout 状态。没有这些证据,回滚和复盘都会变成猜测。
回滚策略要提前演练。Helm 场景要确认哪些 revision 可回滚,回滚是否会触发 Pod 重建,是否需要同步回滚 ConfigMap 或 Secret;Kustomize/GitOps 场景要确认回退 Git commit 后控制器如何同步,是否存在手工漂移。
审计不只是满足流程,也是降低事故恢复时间。一个清晰的发布记录可以快速回答:本次变更是否只改了镜像,是否改了资源,是否改了密钥引用,是否改了网络入口。越快回答这些问题,越快定位事故边界。
9. 什么时候用,什么时候不用
工具选型不要从偏好开始,而要从问题形态开始。你要管理的是一个可复用发布包,还是一组原生 YAML 的环境差异?你需要 release 历史和依赖管理,还是需要清晰 patch 和 GitOps 同步?答案不同,工具主线就不同。
9.1 优先选择 Helm 的场景
当服务需要作为一个可复用发布单元交付时,优先考虑 Helm。例如多个团队复用同一套服务模板,需要通过 values 打开或关闭 Ingress、HPA、ServiceMonitor、PDB;或者服务依赖第三方组件,需要管理子 Chart;或者发布系统需要清晰的 release revision、rollback 和 diff。
Helm 也适合平台团队提供标准化 Chart。平台把通用能力封装进模板,业务团队只填写 values。这样可以减少重复 YAML,也能把资源限制、labels、探针、监控注解、Pod 安全上下文等规范沉淀下来。
不适合 Helm 的情况是:模板条件越来越多,values 层级越来越深,评审者必须运行渲染才能理解任何字段。此时要么拆 Chart,要么把部分环境覆盖交给 Kustomize 或 GitOps,而不是继续在模板里堆逻辑。
9.2 优先选择 Kustomize 的场景
当团队希望保留 Kubernetes 原生 YAML,并且环境差异主要是少数字段覆盖时,优先考虑 Kustomize。它适合 GitOps 场景,也适合需要让开发、平台、测试都能直接读懂最终对象结构的团队。
Kustomize 对“基础对象稳定、环境差异明确”的服务很友好。例如 base 中定义 Deployment 和 Service,dev overlay 降低资源和副本,prod overlay 增加副本、资源、Ingress 和 NetworkPolicy。评审时可以直接看 patch 改了哪些字段。
不适合 Kustomize 的情况是:需要复杂参数化、依赖管理、发布包版本、Chart 仓库分发或大量可选组件。patch 如果膨胀到几十个文件,也会让最终状态难以理解。此时应考虑 Helm 作为主线,或重新拆分 base。
9.3 混用与反模式
Helm 和 Kustomize 可以混用,但要有清晰边界。一种常见方式是 Helm 负责生成标准服务 manifest,Kustomize 负责在 GitOps 仓库中对不同集群做最后一层覆盖。另一种方式是平台提供 Helm Chart,业务仓库只维护 values,环境仓库负责保存最终发布输入。
混用的反模式是两边都在改同一个字段。比如 Helm values 设置 replica,Kustomize overlay 又 patch replica;Helm 模板生成 Ingress,overlay 又替换 host;CI 再用命令行覆盖 image tag。这样会让优先级难以解释,事故时也很难追踪真实来源。
最终判断标准很简单:团队能否在发布前看见最终 manifest,能否在事故时追到输入来源,能否在 PR 中看懂环境差异,能否把密钥和敏感配置排除在普通模板之外。如果做不到,说明问题不在工具命令,而在治理边界还没设计好。
10. 下一篇衔接
本篇解决的是 Helm 与 Kustomize 在多环境治理中的职责边界:Chart、values、templates、release、base、overlay、patch 如何协作,环境差异如何分层,密钥为什么不能混入普通配置,CI 为什么必须做渲染、校验和 diff。
下一篇会进入“容器日志、监控与排障证据链”。如果说本篇关注的是发布前和发布时的期望状态,那么下一篇关注的就是发布后的实际状态:日志如何定位应用问题,指标如何发现资源和流量问题,事件如何连接 Kubernetes 对象变化,trace 如何串起一次请求的上下游。
读完本篇后,你可以用三个问题自测:第一,能否说清一个生产 values 改动最终影响了哪些 Kubernetes 字段;第二,能否从 release 或 Git commit 追到当前集群对象的来源;第三,能否解释一个 Secret 引用、ConfigMap 更新或 Kustomize patch 失败时应该看哪些证据。如果能回答,就可以进入下一篇。