Kustomize 结构化数据内嵌 JSON/YAML 的定向替换与合并提案(22-03)深度解析
2026/9/23 19:50:20 网站建设 项目流程
  • CLI
  • 开发工具
  • 云原生

【免费下载链接】kustomize

Customization of kubernetes YAML configurations

项目地址:https://gitcode.com/gh_mirrors/ku/kustomize
点击查看免费下载

本文档基于仓库 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)提出两项关键能力:通过扩展replacementsfieldPath/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(明确不做的事):

  1. 不提供非结构化编辑(unstructured edits),这与 Kustomize 拒绝参数化(eschews parameterization)的立场一致;
  2. 不提供通过patches字段去定位/合并字符串内部值的能力——即该能力仅属于replacements与生成器合并,不向 patch 机制扩散;
  3. 不提供对内嵌数据自定义字段合并策略的能力,合并行为遵循固定语义。

Goals 则只有一个:提供一种方式,去更新 Kubernetes 对象内以 JSON/YAML 格式存在的结构化数据中的值

三、特性一:用replacements改写内嵌结构化数据的值

3.1 接口设计:扩展fieldPath/fieldPaths的语义

提案建议扩展replacementssource.fieldPathtargets.fieldPaths的取值能力。其核心思路是:

source.fieldPathtargets.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选择器与optionsFieldOptions,支持delimiterindexcreate等细化解释)。

3.3 底层实现:setValueInStructuredData的完整流程

仓库中 api/filters/replacement/replacement.go 的setValueInStructuredData函数完整实现了这一机制,其流程可以概括为:

  1. 切分路径:用SmarterPathSplitter.切分(识别\.转义);
  2. 寻找标量边界:从路径第 1 段开始递增尝试,用yaml.Lookup逐段定位,找到"能解析为结构化数据的标量节点"为止——一旦某段命中的节点是ScalarNode且其后还有剩余路径段,就尝试yaml.Unmarshal解析其值;解析成功即把该段作为"字符串字段路径",剩余段作为"结构化数据路径";
  3. 解析内嵌数据:将该标量字符串反序列化为yaml.RNode结构树;
  4. 下钻并写入:通过PathMatcher沿structuredDataPath下钻,配合FieldOptions.Createcreate: true时允许创建缺失字段)找到目标节点,调用setFieldValue写入新值(标量仅复制 Value 以保留类型自动转换能力);
  5. 回写并保持格式serializeStructuredData根据原始字符串的首字符判断格式——以{[开头按 JSON 序列化,否则回退为 YAML 序列化,从而尽量保留原有 JSON/YAML 风格与紧凑/美化格式(见 replacement.go)。

该过滤器由内置 transformer 插件ReplacementTransformerPlugin驱动(api/internal/builtins/ReplacementTransformer.go),最终通过resmapApplyFilter对全部资源生效。这也印证了提案的"沿用既有 replacements 接口、不引入新 Kind/CLI 标志"的保守设计。

四、特性二:configMapGenerator/secretGeneratormergeValues结构化合并

4.1 接口设计:GeneratorArgs新增mergeValues

提案为configMapGeneratorsecretGenerator共用的 GeneratorArgs 增加一个参数mergeValues,用于在behaviormerge时,对同为结构化格式的两个字符串字面量执行递归合并。

mergeValues是一个列表,每个元素包含两个参数:

参数含义
key用于选中要合并的字符串字面量的键名(即 ConfigMap/Secret 数据项的 key)
format指定该字符串字面量的格式,必须为YAMLJSON

该合并操作属于"覆盖 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" } } }

合并结果parameterfoobaz并存,loglevelhostname互补):

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: 8080

replacement 配置

## 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

项目地址:https://gitcode.com/gh_mirrors/ku/kustomize
点击查看免费下载
上一篇:2025最新版adblock-nocoin-list评测:拦截率提升30%的秘密
下一篇:FakeTraveler技术解析:Android位置模拟框架的设计与实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询