Kustomize 多环境配置管理实战:base 与 overlay 的组织之道
同一个应用要部署到 dev、staging、prod 三套环境,清单大同小异:镜像标签不同、副本数不同、几个环境变量和资源配额不同。如果每个环境复制一份完整 YAML,改一个字段就要同步改三处,漏一处就是一次事故。Kustomize 的解法是分层:把公共部分写成一份 base,把每个环境的差异写成 overlay(补丁层),构建时叠加生成最终清单。它不使用模板语法,不在 YAML 里挖占位符,所有文件单独拿出来都是合法清单,这是它和 Helm 最大的气质区别。本文讲清 base/overlay 的组织方式、四类高频补丁、components 复用,以及日常维护里真正会踩的坑。
前置条件
- 已有一组能正常部署的 Kubernetes 清单(Deployment、Service 等),准备按环境拆分。
- 本地装好 kustomize(独立二进制,或直接用 kubectl 内置的 -k,版本以独立版为准)。
- 清单已纳入 Git 管理,后续所有环境差异都走代码评审而不是手工改集群。
先定分工:Kustomize 与 Helm 各管什么
两者不是二选一,而是分工不同。Helm 强在“打包与分发”:把清单连同参数(values)打成 Chart,带版本、能回滚,适合把应用交付给别人用。Kustomize 强在“同一套清单的环境适配”:无模板、无参数语言,用声明式补丁表达差异,适合团队内部维护多环境。常见组合是上游用 Helm 发布基础组件,自家业务的环境差异用 overlay 叠加。判断标准很简单:差异是“少量字段覆盖”用 Kustomize,是“整段逻辑随参数变化”才考虑模板。
第一步:组织 base
base 只放所有环境共享的东西:工作负载、Service、公共的 ConfigMap,并用一个 kustomization.yaml 把它们组织起来。
apps/shop/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── configmap.yaml
│ └── kustomization.yaml
└── overlays/
├── dev/
│ └── kustomization.yaml
├── staging/
│ └── kustomization.yaml
└── prod/
├── kustomization.yaml
└── patch-replicas.yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
labels:
- pairs:
app.kubernetes.io/name: shop
includeSelectors: true
组织上有三条经验:一是 base 保持“可直接部署”的完整度,单独 build base 就应是一套能跑的清单,不留半成品;二是公共标签、命名空间这类横切字段统一在 kustomization 里处理,别散落在每个资源文件里重复写;三是尽早用 labels 代替已不推荐的 commonLabels——后者会改动 selector,容易让已部署服务在升级时出问题,这也是新版把标签拆成 pairs 与 includeSelectors 两个维度的原因。
第二步:overlay 的四类常用补丁
环境差异来来去去就四类,逐个看写法。
1. 改镜像
# overlays/prod/kustomization.yaml(节选)
images:
- name: registry.example.com/shop/web
newTag: "1.8.2"
images 按镜像名匹配替换标签或仓库地址,是 CI 里最常自动改写的一段:流水线构建出新镜像后,只需要改对应 overlay 的 newTag 并提交 Git,GitOps 工具就会把新版本同步到集群。
2. 改副本数
replicas:
- name: web
count: 4
replicas 按工作负载名匹配。注意它只对 Deployment、StatefulSet 等支持副本的资源有效,名字必须与 base 中资源的 metadata.name 一致(在 namePrefix 改名之前的名字)。
3. 改配置差异
小差异用字段级补丁:strategic merge patch 直接写一段“目标资源的局部样子”即可,适合改 resources 配额、环境变量这类结构化字段。
# overlays/prod/patch-resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
template:
spec:
containers:
- name: web
resources:
limits:
cpu: "2"
memory: 2Gi
# 在 kustomization 里挂上补丁
patches:
- path: patch-resources.yaml
大段配置差异更适合在 overlay 里用同名 ConfigMap 覆盖,或用 configMapGenerator 按环境生成。原则:差异越结构化越用补丁,越像“另一份文件”越用覆盖,别写出比原文件还长的补丁。
4. 命名空间与名前缀
namespace: shop-prod
namePrefix: prod-
namespace 一键把整套资源改到目标命名空间;namePrefix/nameSuffix 给所有资源名加统一前后缀,适合在同一命名空间里并行部署多套临时环境。要注意 namePrefix 会连带改写资源间引用(Kustomize 会自动处理它能识别的引用,如 Service 名、ConfigMap 引用),但手写在字符串里的名字不会被改,这类隐式引用是多环境并存时最常见的故障源。
第三步:用 components 复用横切能力
有些能力是“哪个环境需要就插哪个”的,比如统一加日志 sidecar、注入监控注解、加一套 NetworkPolicy。逐个复制进每个 overlay,改一处要同步多处。这正是 components(Kustomize 4.1 引入)的用途:声明为 kind: Component 的目录可被任意 kustomization 引用,像插件一样叠加资源与补丁。
# components/log-sidecar/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- path: add-sidecar.yaml
# overlays/prod/kustomization.yaml(节选)
components:
- ../../components/log-sidecar
使用边界要划清:components 适合“可选的横切功能”,base/overlay 适合“环境的主干与差异”。把环境差异塞进 components、或把横切功能复制进每个 overlay,都会让目录越长越乱。
第四步:本地验证与接入交付链路
overlay 写好先在本地构建出来逐行看,不要直接往集群上套。确认无误后,交付侧有三种接入方式:kubectl 直接 apply -k;Argo CD 在 Application 里把 path 指向 overlay 目录即可,它原生识别 kustomization.yaml;Flux 的 kustomize-controller 同理。
# 构建并检查最终清单
kustomize build overlays/prod > /tmp/prod.yaml
kubectl apply --dry-run=server -f /tmp/prod.yaml
kubectl diff -k overlays/prod
# 实际部署
kubectl apply -k overlays/prod
版本差异是长期维护的隐形战场:字段一直在演进,老字段 bases 早已移除,patchesStrategicMerge 收敛到 patches,commonLabels 换成了 labels。应对只有两条:在 CI 里固定 kustomize 版本,把每个 overlay 的 kustomize build 设为必过检查;升级时先全量构建对比输出再合并,别等部署时才发现字段被弃用。
验证
kustomize build对 base 与每个 overlay 都能成功输出,且输出里镜像标签、副本数、命名空间与预期逐项对得上。kubectl diff -k overlays/prod的变更范围只包含这个环境的预期差异,没有误伤其他资源。- 部署后
kubectl -n shop-prod get deploy,svc,cm状态正常,应用读到的是本环境的配置。
常见问题
补丁打了却没生效,也不报错。九成是匹配不上:strategic merge patch 靠 apiVersion、kind、name 三者定位,name 写的是改名之后的名字、或资源实际在另一个 group/version 下,都会静默落空。先 kustomize build 看输出确认补丁有没有进去,再逐项核对这三个字段。带 target 选择器的 patches 同理,检查 target 的 name 与 version 是否与 base 中一致。
strategic merge 与 JSON6902 该选哪个。改内置资源的常规字段,用 strategic merge,可读性好;要按数组下标精确增删元素、或处理没有合并键的 CRD,strategic merge 会力不从心,改用 JSON6902(op/path/value 的写法精确但啰嗦)。一个实用原则:能用 strategic merge 说清的不要上 JSON6902,上了 JSON6902 的补丁要在旁边写注释说明动的是哪个元素。
把密码直接写进 overlay。这是敏感配置的边界问题:Kustomize 不管加密,进 Git 的 Secret 只是 base64 编码,不是加密。对策是敏感值不进 Kustomize 管辖——用 Sealed Secrets、SOPS 或 External Secrets 在交付侧注入,overlay 里只保留非敏感配置与密文引用。审查清单加一条:含 password、token 的明文文件不允许进仓库。
升级 kustomize/kubectl 后构建突然失败。基本都是字段弃用。先看报错指向的字段,对照新版把 bases、patchesStrategicMerge、commonLabels 换成 resources、patches、labels 的新写法,改完重新全量构建各 overlay 对比输出,确认只是写法迁移没有语义变化。
小结
Kustomize 的功夫在组织不在语法:base 保持完整可部署,overlay 只写四类差异(镜像、副本、配置、命名空间与命名),横切功能沉到 components 复用。维护纪律同样重要:版本固定在 CI、构建检查必过、敏感配置不进 Git。做到这几条,多环境配置就从“复制三份改三遍”变成“公共部分改一处、差异一目了然”。







