- CLI
- 开发工具
- 云原生
【免费下载链接】kustomize
Customization of kubernetes YAML configurations
本文档基于仓库 proposals/22-03-value-in-the-structured-data.md 展开,并结合 api/filters/replacement/replacement.go、api/types/replacement.go、api/types/generatorargs.go 等源码佐证。
Kustomize 的传统定位是"只做结构化编辑":它能够精确地改写 YAML 资源中任意字段,却无法触碰字符串字面量(string literal)内部的内容——例如 ConfigMap 的data.config.json中内嵌的一段 JSON,或prometheus.yml里的一段 YAML 配置。本提案(编号 22-03,状态 implementable)提出两项关键能力:通过扩展replacements的fieldPath/fieldPaths语义,直接定位并改写字符串内部的 JSON/YAML 子结构;以及为configMapGenerator/secretGenerator新增mergeValues参数,让behavior: merge时按 key 对结构化字符串做递归合并。读完本文,你将理解这两个特性背后的设计动机、接口约定、四个完整用户故事,以及仓库中对应的源码实现路径。
一、背景:为什么"字符串里的结构"是 Kustomize 的盲区
Kustomize 能够对 Kubernetes YAML 资源施加结构化编辑(structured edits),但当某个字段的值本身是一个多行长字符串或长单行字符串(如 JSON、YAML 等其他结构化格式数据)时,从 Kustomize 的视角看,这只是一段"任意的非结构化字符串"。
考虑下面这个 ConfigMap:
apiVersion: v1 kind: ConfigMap metadata: name: target-configmap data: config.json: |- {"config": { "id": "42", "hostname": "REPLACE_TARGET_HOSTNAME" }}想直接通过replacements把"REPLACE_TARGET_HOSTNAME"换成集群专属域名,传统方式做不到——data.config.json在 Kustomize 眼里只是一个标量字符串,无法继续下钻。用户只能整体替换整个 JSON 文件,这在一个 base 要覆盖多集群(dev/prod)时非常痛苦。
这一诉求由来已久,提案中列出了一系列历史 issue(#680、#3787、#4517),并且明确指出:该功能在大量场景下可以成为已弃用的 vars 的替代方案。
二、设计原则:在不违背"仅结构化编辑"核心原则的前提下扩展
提案的价值判断非常克制。它强调,允许用户标识出包含 JSON/YAML 数据的字符串字面量,让 Kustomize 对其中包含的数据做结构化编辑——这样既满足了内嵌数据改写的需求,又没有破坏 Kustomize 只支持结构化编辑的核心原则。
为此,提案明确了三个 Non-goals(明确不做的事):
- 不提供非结构化编辑(unstructured edits),这与 Kustomize 拒绝参数化(eschews parameterization)的立场一致;
- 不提供通过
patches字段去定位/合并字符串内部值的能力——即该能力仅属于replacements与生成器合并,不向 patch 机制扩散; - 不提供对内嵌数据自定义字段合并策略的能力,合并行为遵循固定语义。
Goals 则只有一个:提供一种方式,去更新 Kubernetes 对象内以 JSON/YAML 格式存在的结构化数据中的值。
三、特性一:用replacements改写内嵌结构化数据的值
3.1 接口设计:扩展fieldPath/fieldPaths的语义
提案建议扩展replacements中source.fieldPath与targets.fieldPaths的取值能力。其核心思路是:
当
source.fieldPath与targets.fieldPaths在命中某个 YAML 中的字符串字面量之后,仍然带有额外的路径段时,Kustomize 将把该字符串解析为结构化数据,并用这些额外路径段继续向下钻取。
也就是说,路径被分成两段:前段在 YAML 资源里定位到那个"装着结构体的字符串"字段,后段在这个字符串内部继续定位具体值。
3.2 关键语法:用\.转义键名中的点
由于字段路径默认以.作为分隔符,当字符串字面量字段的键名本身含有.(例如config.json)时,必须使用\.转义:
## replacement replacements: - source: kind: ConfigMap name: source-configmap fieldPath: data.HOSTNAME targets: - select: kind: ConfigMap name: target-configmap fieldPaths: - data.config\.json.config.hostname # `config\.json` 之后的路径指向结构化数据中的一处这里data.config\.json定位到data["config.json"]这个字符串,随后的config.hostname则在该字符串解析出的 JSON 结构内部继续下钻。
从源码看,路径切分由kyaml_utils.SmarterPathSplitter完成(见 api/filters/replacement/replacement.go),它负责正确处理\.转义。而fieldPath/fieldPaths字段本身的类型定义位于 api/types/replacement.go:SourceSelector.FieldPath为单个字符串,TargetSelector.FieldPaths为字符串数组,TargetSelector还支持select/reject选择器与options(FieldOptions,支持delimiter、index、create等细化解释)。
3.3 底层实现:setValueInStructuredData的完整流程
仓库中 api/filters/replacement/replacement.go 的setValueInStructuredData函数完整实现了这一机制,其流程可以概括为:
- 切分路径:用
SmarterPathSplitter按.切分(识别\.转义); - 寻找标量边界:从路径第 1 段开始递增尝试,用
yaml.Lookup逐段定位,找到"能解析为结构化数据的标量节点"为止——一旦某段命中的节点是ScalarNode且其后还有剩余路径段,就尝试yaml.Unmarshal解析其值;解析成功即把该段作为"字符串字段路径",剩余段作为"结构化数据路径"; - 解析内嵌数据:将该标量字符串反序列化为
yaml.RNode结构树; - 下钻并写入:通过
PathMatcher沿structuredDataPath下钻,配合FieldOptions.Create(create: true时允许创建缺失字段)找到目标节点,调用setFieldValue写入新值(标量仅复制 Value 以保留类型自动转换能力); - 回写并保持格式:
serializeStructuredData根据原始字符串的首字符判断格式——以{或[开头按 JSON 序列化,否则回退为 YAML 序列化,从而尽量保留原有 JSON/YAML 风格与紧凑/美化格式(见 replacement.go)。
该过滤器由内置 transformer 插件ReplacementTransformerPlugin驱动(api/internal/builtins/ReplacementTransformer.go),最终通过resmap的ApplyFilter对全部资源生效。这也印证了提案的"沿用既有 replacements 接口、不引入新 Kind/CLI 标志"的保守设计。
四、特性二:configMapGenerator/secretGenerator的mergeValues结构化合并
4.1 接口设计:GeneratorArgs新增mergeValues
提案为configMapGenerator和secretGenerator共用的 GeneratorArgs 增加一个参数mergeValues,用于在behavior为merge时,对同为结构化格式的两个字符串字面量执行递归合并。
mergeValues是一个列表,每个元素包含两个参数:
| 参数 | 含义 |
|---|---|
key | 用于选中要合并的字符串字面量的键名(即 ConfigMap/Secret 数据项的 key) |
format | 指定该字符串字面量的格式,必须为YAML或JSON |
该合并操作属于"覆盖 base ConfigMap 值"(Overriding Base ConfigMap Values)能力的一部分:在合并两个 ConfigMap/Secret 时,对具有相同 key的字符串字面量执行结构化合并。从类型定义上看,GeneratorArgs中的Behavior字段取值必须是create/replace/merge三者之一,本特性要求behavior: merge才能生效(api/types/generatorargs.go)。
4.2 配置示例
configMapGenerator: - name: demo-settings behavior: merge # 本功能要求 `behavior: merge`。 mergeValues: - key: config.json # 要合并的目标 key。 format: json # 结构化数据格式必须是 YAML/JSON。 literals: - config.json: |- { "config": { "hostname": "REPLACE_TARGET_HOSTNAME", "value": { "foo": "bar" } } }合并语义与 Kustomize 一贯的"递归合并"一致:同名字段深度合并,不同名字段互补保留(详见下文 Story 2 的输入输出对照)。
五、四个用户故事:从需求到完整输入输出
Story 1:替换 ConfigMap 中 JSON 字符串内的值
场景:多集群管理中,dev/prod 集群需要不同的config.json内容。传统做法只能整体替换整个 JSON 文件;本特性允许仅覆盖差异点。
源与目标资源:
## source apiVersion: v1 kind: ConfigMap metadata: name: source-configmap data: HOSTNAME: www.example.com --- apiVersion: v1 kind: ConfigMap metadata: name: target-configmap data: config.json: |- {"config": { "id": "42", "hostname": "REPLACE_TARGET_HOSTNAME" }}replacement 配置:
## replacement replacements: - source: kind: ConfigMap name: source-configmap fieldPath: data.HOSTNAME targets: - select: kind: ConfigMap name: target-configmap fieldPaths: - data.config\.json.config.hostname期望结果:
## expected apiVersion: v1 kind: ConfigMap metadata: name: source-configmap data: HOSTNAME: www.example.com --- apiVersion: v1 kind: ConfigMap metadata: name: target-configmap data: config.json: '{"config":{"hostname":"www.example.com","id":"42"}}'注意结果中config.json被序列化为紧凑 JSON(键顺序也发生了重排),这正是serializeStructuredData按 JSON 格式回写的行为。
Story 2:用configMapGenerator合并两份 JSON 配置
场景:许多应用以 JSON 文件承载配置,运行在 Kubernetes 上时通过 ConfigMap 挂载。若configMapGenerator能对data中的 JSON 做合并,JSON 文件的维护将变得简单。
base 侧(base/kustomization.yaml):
configMapGenerator: - name: demo literals: - config.json: |- { "config": { "loglevel": debug, "parameter": { "foo": "bar" } } }overlay 侧(overlay/kustomization.yaml):
resources: - ../base configMapGenerator: - name: demo behavior: merge mergeValues: - key: config.json # 要合并的目标 key。 format: json # 结构化数据格式必须是 YAML/JSON。 literals: - config.json: |- { "config": { "hostname": "www.example.com", "parameter": { "baz": "qux" } } }合并结果(parameter下foo与baz并存,loglevel与hostname互补):
apiVersion: v1 data: config.json: |- { "config": { "loglevel": debug, "hostname": "www.example.com", "parameter": { "foo": "bar", "baz": "qux" } } } kind: ConfigMap metadata: name: demo-xxxxxxxxxx # 名称后缀哈希Story 3:替换 ConfigMap 中 YAML 字符串内的值
场景:Prometheus、AlertManager 等云原生应用使用 YAML 格式的配置文件,且需要覆盖的值通常位于嵌套 YAML 结构中。若能在 YAML 内部做覆盖,就无需复制整个 YAML 文件。
源与目标资源:
## source apiVersion: v1 kind: ConfigMap metadata: name: environment-config data: env: dev --- apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config data: prometheus.yml: |- global: external_labels: prometheus_env: TARGET_ENVIROMENT scrape_configs: - job_name: "prometheus" static_configs: - targets: ["localhost:9090"]replacement 配置:
## replacement replacements: - source: kind: ConfigMap name: environment-config fieldPath: data.env targets: - select: kind: ConfigMap name: prometheus-config fieldPaths: - data.prometheus\.yml.global.external_labels.prometheus_env期望结果:
## expected apiVersion: v1 kind: ConfigMap metadata: name: environment-config data: env: dev --- apiVersion: v1 kind: ConfigMap metadata: name: prometheus-config data: prometheus.yml: |- global: external_labels: prometheus_env: dev scrape_configs: - job_name: "prometheus" static_configs: - targets: ["localhost:9090"]注意这里data.prometheus\.yml后面的global.external_labels.prometheus_env是在 YAML 结构内部下钻;由于原始值以 YAML 块标量(|-)形式存在且首字符不是{/[,回写时走 YAML 序列化路径,块式格式得以保留。
Story 4:替换 Annotations 中 JSON 字符串内的值
场景:部分集群上的应用需要在 Kubernetes 资源的Annotations中写入 JSON 格式配置(例如云厂商 Ingress/BackendConfig 注解、AWS Load Balancer Controller 的注解等),此时也需要覆盖其中的值。
源与目标资源:
## source apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: debug-backend-config spec: securityPolicy: name: "debug-security-policy" --- apiVersion: v1 kind: Service metadata: name: appA-svc annotations: cloud-provider/backend-config: '{"ports": {"appA":"gke-default-backend-config"}}' spec: ports: - name: appA port: 1234 protocol: TCP targetPort: 8080replacement 配置:
## replacement replacements: - source: kind: BackendConfig name: debug-backend-config fieldPath: metadata.name targets: - select: kind: Service name: appA-svc fieldPaths: - metadata.annotations.cloud-provider/backend-config.ports.appA期望结果:
## expected apiVersion: cloud.google.com/v1 kind: BackendConfig metadata: name: debug-backend-config spec: securityPolicy: name: "debug-security-policy" --- apiVersion: v1 kind: Service metadata: name: appA-svc annotations: cloud-provider/backend-config: '{"ports": {"appA":"debug-backend-config"}}' spec: ports: - name: appA port: 1234 protocol: TCP targetPort: 8080这一故事把"结构内下钻"扩展到了 annotations 场景:cloud-provider/backend-config键名中的/无需转义(.才是分隔符),其后的ports.appA在 JSON 内下钻。这正是提案中"从 BackendConfig 的metadata.name取值、写入 Service 注解 JSON 中ports.appA字段"的经典用法,对应了 GKE Ingress 按端口绑定独立 BackendConfig、以及 AWS Load Balancer Controllerlisten-ports注解等真实需求。
六、风险与后续规划
提案模板中保留了 Risks、Dependencies、Scalability 等章节(当前正文未填充细节,符合仓库中 mini enhancement proposal 的轻量流程,见 proposals/README.md 中对 Option 2 的描述)。值得注意的约束包括:
- 依赖控制:Kustomize 严格管控 Go 依赖以保证能合入
kubectl,不能直接依赖 kubectl 或 apimachinery 代码——因此本特性在实现上优先复用kyaml自身的 YAML 解析与PathMatcher能力,而不是引入新的解析库; - 格式保持:回写时需识别 JSON/YAML 两种格式并尽量保留原始风格,这是实现中最容易产生行为差异的部分(已由
serializeStructuredData处理); - 进阶路径:若该特性后续进入
kubectl kustomize,按 KEP 流程需要经历 Alpha(可能以开关门控)→ Beta(与kubectl kustomize完全对齐)→ GA(一般等待至少两个 kubectl 发布周期)的阶段。
七、总结
本提案用最小的接口改动(扩展fieldPath/fieldPaths语义 +GeneratorArgs新增mergeValues)解决了 Kustomize 长期无法触碰"字符串内结构"的痛点,且严格守住了"仅结构化编辑"的核心原则。四个用户故事覆盖了 JSON/YAML 内嵌数据在 ConfigMap data、Prometheus 配置、云厂商 annotations 等典型场景的定向覆盖与递归合并,仓库源码(api/filters/replacement/replacement.go、api/types/replacement.go、api/types/generatorargs.go)也已给出可实现的完整路径。对于在多集群、多环境场景下维护内嵌配置的用户而言,这套能力意味着"不必再整文件替换、只改差异点"。
如需继续深入,可参考仓库内的相关实现与测试:replacementtransformer_test.go、ReplacementTransformer_test.go,以及另一份相关提案 21-11-transformer-annotations.md。
- CLI
- 开发工具
- 云原生
【免费下载链接】kustomize
Customization of kubernetes YAML configurations
相关推荐
性能对比分析:ANE Transformers vs 传统Transformer在苹果设备上的差异
性能对比分析:ANE Transformers vs 传统Transformer在苹果设备上的差异 ANE Transformers是针对苹果神经引擎(Appl
Karmada 中 sigs.k8s.io/yaml 深度解析:YAML 与 Go 结构体互转的 JSON 桥接实现
Karmada 中 sigs.k8s.io/yaml 深度解析:YAML 与 Go 结构体互转的 JSON 桥接实现 本文以 Karmada 仓库中 vendo
云原生多集群集群管理微服务Sudachi 模拟器上手指南:从源码编译到运行 Switch 游戏的完整流程
Sudachi 模拟器上手指南:从源码编译到运行 Switch 游戏的完整流程 Sudachi 是一款用 C++ 编写的 Nintendo Switch 开源模
桌面应用移动开发虚拟化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考