kustomize JSON Patch(JSON 补丁)完全指南:用 RFC 6902 精准改造 Ingress 等任意资源
【免费下载链接】kustomizeCustomization of kubernetes YAML configurations项目地址: https://gitcode.com/gh_mirrors/ku/kustomize
JSON Patch(RFC 6902 的完整演示为主线,结合仓库源码讲解 patch 的语法、target 定位机制、JSON/YAML 两种写法,以及它与其他 patch 方式的取舍,读完你可以立刻在自己的 kustomization 项目中落地这套打补丁方案。
一、JSON Patch 是什么,kustomize 为什么要支持它
kustomize 的职责是「Customization of kubernetes YAML configurations」——在不改原始资源的前提下,通过叠加规则生成最终 YAML。修改资源的方式有很多种,JSON Patch 是其中语法最精确、最贴近标准的一种:
- Strategic Merge Patch(SMP):把想修改的字段局部重写一份,kustomize 按 Kubernetes 特有规则与原资源合并(详见 patchstrategicmerge 过滤器)。它擅长「改字段」,但在「往列表中间插一项」「按精确路径改一个值」等场景下不够直观。
- JSON Patch:由一组显式的操作指令组成,每条指令形如
{"op": "replace", "path": "/spec/rules/0/host", "value": "foo.bar.io"},操作对象、路径、新值一目了然,完全遵循 RFC 6902 标准。
kustomize 的 JSON Patch 支持体现在两个层面:
- 字段层面:
kustomization.yaml中声明patches(新推荐)或patchesJson6902(已弃用,见 kustomization.go),每个 patch 带一个target选择器; - 实现层面:内置插件 PatchJson6902Transformer 与底层过滤器 patchjson6902.Filter 负责解析和应用。
二、完整演示:用 JSON Patch 改造一个 Ingress
下面完整复现 examples/jsonpatch.md 的官方演示:先造一个包含三个路由路径的 Ingress,再用一个 JSON Patch 文件同时完成「改值 × 2 + 指定位置插入 × 1」。
2.1 准备资源文件
建一个临时工作目录,写入 Ingress:
DEMO_HOME=$(mktemp -d)cat <<EOF >$DEMO_HOME/ingress.yaml apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: my-ingress spec: rules: - host: foo.bar.com http: paths: - path: / backend: serviceName: homepage servicePort: 8888 - path: /api backend: serviceName: my-api servicePort: 7701 - path: /test backend: serviceName: hello servicePort: 7702 EOF注意:示例资源使用的是networking.k8s.io/v1beta1版本的 Ingress,这是该演示文档编写时的 API 版本,当前 Kubernetes 已演进到networking.k8s.io/v1,实际使用时请把apiVersion与target中的version同步替换为你集群支持的版本。
2.2 定义三项目标修改
我们要做的修改是:
- 把
host的值从foo.bar.com改为foo.bar.io; - 把
'/'路径的servicePort从8888改为80; - 在
paths列表的指定位置(/test之前)插入一条全新的/healthz服务路径,而不是追加到列表末尾或开头。
第三条正是 JSON Patch 相对普通字段覆盖的差异化优势:路径定位用数组下标(/paths/1)精确控制插入点。
2.3 编写 JSON Patch 文件
cat <<EOF >$DEMO_HOME/ingress_patch.json [ {"op": "replace", "path": "/spec/rules/0/host", "value": "foo.bar.io"}, {"op": "replace", "path": "/spec/rules/0/http/paths/0/backend/servicePort", "value": 80}, {"op": "add", "path": "/spec/rules/0/http/paths/1", "value": { "path": "/healthz", "backend": {"servicePort":7700} }} ] EOF该文件是一个 JSON 数组,数组中每个元素是一条 RFC 6902 操作指令:
| 字段 | 含义 | 本例取值 |
|---|---|---|
op | 操作类型:add/remove/replace/move/copy/test | replace、add |
path | 用 JSON Pointer 语法(/分隔,数组用数字下标)定位目标字段 | /spec/rules/0/host等 |
value | 操作涉及的数值/对象 | "foo.bar.io"、80、{...} |
add在数组路径上的语义是「在指定下标处插入」,因此"/spec/rules/0/http/paths/1"会把新路径插到原下标 1(即/api路径)之前,其余元素自动后移。关于 add 在对象、数组及-通配下标上的行为,见 RFC 6902 第 4.1 节。
2.4 编写 kustomization 并挂接 patch
先声明引用 Ingress:
cat <<EOF >$DEMO_HOME/kustomization.yaml resources: - ingress.yaml EOF再追加patches字段,用target把 patch 指向 Ingress 对象:
cat <<EOF >>$DEMO_HOME/kustomization.yaml patches: - path: ingress_patch.json target: group: networking.k8s.io version: v1beta1 kind: Ingress name: my-ingress EOF2.5 运行并校验输出
预期输出($DEMO_HOME/out_expected.yaml):
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: my-ingress spec: rules: - host: foo.bar.io http: paths: - backend: serviceName: homepage servicePort: 80 path: / - backend: servicePort: 7700 path: /healthz - backend: serviceName: my-api servicePort: 7701 path: /api - backend: serviceName: hello servicePort: 7702 path: /test运行构建并与预期比对:
kustomize build $DEMO_HOME >$DEMO_HOME/out_actual.yaml diff $DEMO_HOME/out_actual.yaml $DEMO_HOME/out_expected.yamldiff无输出即说明补丁生效,三处修改全部命中:host 变为foo.bar.io、/端口变为 80、/healthz被插到/api之前。同时可以看到 patch 后的字段排序(如backend与path的顺序)可能相对原文件发生变化,这与底层实现有关(见第四节)。
三、同样一套补丁,也可以写成 YAML
JSON Patch 的规则不变,但文件本身可以用 YAML 语法书写(YAML 是 JSON 的超集,写起来更省标点)。追加一条「add」操作到列表末尾(/spec/rules/0/http/paths/-中的-表示列表末尾,是 RFC 6902 规定的特殊下标):
cat <<EOF >$DEMO_HOME/ingress_patch.yaml - op: add path: /spec/rules/0/http/paths/- value: path: '/canada' backend: serviceName: hoser servicePort: 7703 EOF把它加进 kustomization 的 patch 列表:
cat <<EOF >>$DEMO_HOME/kustomization.yaml - path: ingress_patch.yaml target: group: networking.k8s.io version: v1beta1 kind: Ingress name: my-ingress EOF预期输出末尾追加了/canada路径:
- backend: serviceName: hello servicePort: 7702 path: /test - backend: serviceName: hoser servicePort: 7703 path: /canada验证:
kustomize build $DEMO_HOME | tail -n 8 |\ diff $DEMO_HOME/out_expected.yaml -tail -n 8截取输出末尾 8 行再与期望片段比对,同样无输出即通过。
3.1 中文版演示的另一种写法
仓库还提供了一份更精简的中文演示 examples/zh/jsonpatch.md,它演示的是:
patchesJson6902(旧字段)写法,patch 中target与path的顺序可以交换;- 用
replace+add两种操作组合,把 host 改为foo.bar.io、把 servicePort 改为8080、追加/test路径; - 用
grep验证输出,例如kustomize build $DEMO_HOME | grep "host: foo.bar.io"。
两份演示互相印证:同一个功能既可以写在新字段patches下,也可以写在旧字段patchesJson6902下。
四、底层原理:patch 是如何被解析和应用的
4.1 字段定义:Patch 与 Selector
kustomization.yaml中的patches字段类型定义在 api/types/kustomization.go:每个元素都是types.Patch,其结构见 api/types/patch.go:
path:patch 文件的相对路径;patch:直接内联的 patch 内容(二选一,与path互斥);target:指向要应用 patch 的资源选择器;options:可选,allowNameChange/allowKindChange两个开关,定义见 api/types/patchargs.go。
target是types.Selector,定义在 api/types/selector.go,支持以下条件,多个条件取交集(AND 关系):
| 字段 | 说明 |
|---|---|
group | API 组 |
version | API 版本 |
kind | 资源类型 |
name | 资源名,支持正则 |
namespace | 命名空间 |
labelSelector | 标签选择器表达式 |
annotationSelector | 注解选择器表达式 |
从源码可以推断:name等字段会被编译为正则表达式(见 selector.go 的NewSelectorRegex与anchorRegex),非空字段以^(?:pattern)$锚定匹配。这意味着name: my-ingress精确匹配,而name: foo.*可以一次命中多个资源。
4.2 解析流程:从文件到 jsonpatch.Patch
内置插件 PatchJson6902Transformer 的Config方法负责解析,核心逻辑:
- 校验
target.name不能为空、path与内联jsonOp不能同时为空; - 若给了
path,用 Loader 读取文件内容; - 若内容不是以
[开头,说明是 YAML 格式,先yaml.YAMLToJSON转成 JSON——这就是为什么 patch 文件可以用 YAML 书写; - 交给
jsonpatch.DecodePatch解码为操作列表; - 空 patch(解码后长度为 0)直接报错。
4.3 应用流程:目标选择与逐资源打补丁
Transform方法(PatchJson6902Transformer.go)先按target从资源映射中选出所有匹配资源,再对每个资源调用过滤器。最底层的过滤器在 api/filters/patchjson6902/patchjson6902.go:
- 把目标资源的 YAML 序列化为 JSON;
- 调用
jsonpatch库的Apply应用全部操作; - 再把结果反序列化回 YAML。
该文件源码注释也坦诚指出一个重要事实:这种「YAML → JSON → patch → YAML」的往返方式不保证字段顺序完全保持,这正是 2.5 节输出中字段顺序可能重排的根本原因。若对字段顺序敏感,应优先考虑 Strategic Merge Patch 或直接检查生成结果。
过滤器的行为有完整单测覆盖:api/filters/patchjson6902/patchjson6902_test.go 用 4 个用例验证了 JSON/YAML 两种格式下单操作与多操作(replace + 多个 add)的等效性;example_test.go 还展示了对多个资源(Foo、Bar)同时应用同一个 YAML patch 的管道式用法(kio.Pipeline),与 kustomize 的批处理机制同源。
五、一次 patch 打多个资源:target 选择器进阶
patches字段天然支持「一个 patch 作用于多个资源」,因为target是一个选择器而非单一路径。完整演示见 examples/patchMultipleObjects.md,核心要点:
name支持正则,name: foo.*可命中多个同类型资源;kind: Deployment只按类型筛;- 组合条件更精确,例如同时要求
kind: Deployment且labelSelector: app=hello; - 该演示还展示了用 labelSelector 只 patch 其中一个 Deployment(
labelSelector: key=value时仅 deploy2 被修改)。
需要注意:示例中的patches条目既可以放 Strategic Merge Patch 文件,也可以放 JSON Patch 文件(kustomization.go 注明每个 patch 可以是两者之一),kustomize 依据内容自动识别。若想严格指定 JSON Patch 语义,可使用旧字段patchesJson6902。
六、新旧字段迁移:patchesJson6902 已弃用
从 kustomization.go 可以看到patchesJson6902已被标记为Deprecated,注释明确指出:使用patches字段即可,它是 JSON Patch 功能的超集。运行时若检测到旧字段,kustomize 会给出警告(kustomization.go),并在内部把patchesJson6902条目合并进patches(kustomization.go)。
迁移方法很简单,把:
patchesJson6902: - target: group: networking.k8s.io version: v1beta1 kind: Ingress name: my-ingress path: ingress_patch.json改成:
patches: - path: ingress_patch.json target: group: networking.k8s.io version: v1beta1 kind: Ingress name: my-ingress七、JSON Patch vs Strategic Merge Patch:如何选择
| 维度 | JSON Patch(本文) | Strategic Merge Patch |
|---|---|---|
| 语法 | RFC 6902 操作数组,精确到路径 | 部分重写目标对象,按 Kubernetes 合并规则 |
| 列表操作 | 支持按下标/-精确插入、删除、移动 | 主要靠合并策略,行为受 openapi schema 影响 |
| 适用场景 | 改某个精确字段、列表指定位置插入、跨多个同型资源批量修改 | 覆盖字段、注入 sidecar 等常规合并 |
| 字段顺序 | 经 JSON 往返,不保证完全保持 | 一般保持结构顺序 |
| 声明位置 | patches(或旧patchesJson6902) | patches(或旧patchesStrategicMerge) |
实际项目中二者可混用(都挂在patches下),按「精确路径 vs 局部重写」的诉求灵活选择。
八、快速上手小结
- 准备要修改的资源(本文为
ingress.yaml),resources引用它; - 写 patch 文件(
.json或.yaml均可),内容为 RFC 6902 操作数组; - 在
kustomization.yaml的patches下挂接path+target; kustomize build生成结果,用diff或grep验证;- 需要批量时把
target.name写成正则,或叠加labelSelector/annotationSelector。
更完整的配套示例可继续阅读 examples/jsonpatch.md(英文原版)、examples/zh/jsonpatch.md(中文精简版)与 examples/patchMultipleObjects.md(多资源补丁)。
【免费下载链接】kustomizeCustomization of kubernetes YAML configurations项目地址: https://gitcode.com/gh_mirrors/ku/kustomize
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考