- 序言
- JSON Patch
- JSON Merge Patch
- JSON Patch 与 JSON Merge Patch 对比
- Strategic Merge Patch
- 总结
- 参考
序言
在更新 k8s 资源的时候,除了 update 这种方式,k8s 也提供了 patch 来进行资源的更新。
通过 kubectl patch 来更新的时候,也提供了不同的更新方式
这里的三种方式对应的就是 JSON Patch、JSON Merge Patch 以及 k8s 自定义的 Strategic Merge Patch。
今天我们来看下这三种方式分别都是怎么工作的,让大家能有一个大概的认识,在选择方案的时候,能有所帮助。
因为 Json Patch 和 Json Merge Patch 大家在其他的很多地方也会用到,因此使用场景会更多,所以会优先讲解这两种方式,我也建议大家先搞懂这两种方式。
Strategic Merge Patch 只有在更新 k8s 的标准资源的时候才会有(截止今天,不支持自定义资源通过这种方式进行更新),因此放在最后讲解。
Json Patch
Json Patch 是一种比较好理解的方式,当你更新 json 文档的时候,你可以通过直接指定 'op' 'path' 'value' 来完成,比如如下 patch 数据。op 代表了执行的操作类型(目前在 RFC 文档中指定了有六种,分别是 add、remove 、replace、move、copy、test,具体的可以参见 Json Patch RFC 文档),path 指定了你要更新的 key 值,value 代表了被更新后的值。
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
我们来看个示例。
比如原来的 json 数据如下:
{
"title": "Goodbye!",
"author": {
"givenName": "John",
"familyName": "Doe"
},
"tags": [
"example",
"sample"
],
"content": "This will be unchanged"
通过 JSON Patch 格式来 patch 以下数据
[
{ "op": "replace", "path": "/title", "value": "Hello!"},
{ "op": "remove", "path": "/author/familyName"},
{ "op": "add", "path": "/phoneNumber", "value": "+01-123-456-7890"},
{ "op": "replace", "path": "/tags", "value": ["example"]}
]
就会最终变成以下信息
{
"title": "Hello!",
"author": {
"givenName": "John"
},
"tags": [
"example"
],
"content": "This will be unchanged",
"phoneNumber": "+01-123-456-7890"
}
我们可以对比下两者之间的差别
可以明显看出,在 JSON Patch 格式数据中对应的 remove、replace 以及 add 都在对应源 json 文件的位置产生了变化。
JSON Merge Patch
如果说 Json Patch 是一系列操作的集合,那么 Json Merge Patch 就是一系列差异的集合。差异就是指 原始 Json 文件 和 目标 Json 文件 的不同。
如果是删除某个字段,则需要将字段置为 null,如果是修改某个字段,则需要将新的 value 值写在 对应的 key 上。
比如为了达到和上面示例类似的效果,通过 Json Merge Patch 在执行操作的时候,对应的数据如下。
{
"title": "Hello!",
"author": {
"familyName": null
},
"phoneNumber": "+01-123-456-7890",
"tags": [
"example"
]
}
可以看到这里修改了 title 的值,去掉了 author 中的 familyName,增加了 phoneNumber 以及删除了 tags 中的 sample。通过这种方式也达到了相同的结果。详细内容亦可参考 JSON Merge Patch RFC 文档。
JSON Patch 与 JSON Merge Patch 对比
就我的使用体验而言,他们在常规的使用情况下不分伯仲,但在一些特殊场景场景,都会存在一些难以解决的问题。
Json Patch 因为是操作的集合,在并发的这种场景下,有可能就会造成 某个数组中的值,被增加了多次这种问题,反观 Json Merge Patch,因为它提供的数组是完整的数组,因为不会有该问题。
当然 Json Merge Patch 也不是万能的,只要观察示例中使用的文件,就不难发现 Json Merge Patch 存在一些致命的限制。
- 删除某个键值,需要在 patch 文件中将对应的值置成 null,但是如果我们某个值,确实是需要置成 null,那就几乎无解了
- 数组需要提供全量的,在很多场景下,这个是有些让人难以接受,比如只有一个很细小的值,也是需要 提供全量的数组
- 不会报错,无论什么情况,都会 patch 成功。在真实使用场景下,还需要做额外的校验
当然在实际场景中,我们可以根据自己的使用场景灵活的来确定使用哪种方式。
Strategic Merge Patch
这种方式是更新 k8s 资源时,提供的一种特殊的新的类型,并不是一个标准的协议。
这种方式其实是基于 JSON Merge Patch 的理念来实现的,但是又可以避免 Json Merge Patch 中的坑。比如上面提到的修改数组需要提供全量数组的问题,示例如下。
当前集群有一个 deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: patch-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: patch-demo-ctr
image: nginx
tolerations:
- effect: NoSchedule
key: dedicated
value: test-team
我们想给这个 deployment 增加一个容器,这个时候我们创建一个 patch 文件
spec:
template:
spec:
containers:
- name: patch-demo-ctr-2
image: redis
执行命令,进行 patch 操作,
kubectl patch deployment patch-demo --patch "$(cat patch-file-containers.yaml)"
这个时候,再去查看对应的 deployment 就会包括 patch 文件中的容器
containers:
- image: redis
imagePullPolicy: Always
name: patch-demo-ctr-2
...
- image: nginx
imagePullPolicy: Always
name: patch-demo-ctr
...
我们在上面的操作中所做的 patch 称为策略性合并 patch(Strategic Merge Patch)。 在这种情况下,patch 中的列表与现有列表合并。但是当你在列表中使用策略性合并 patch 时,在某些情况下,列表是替换的,而不是合并的。
patch 策略由 Kubernetes 源代码中字段标记中的 patchStrategy 键的值指定。 例如,PodSpec 结构体的 Containers 字段的 patchStrategy 为 merge,因此这里的操作是一个合并的操作
type PodSpec struct {
...
Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" ...`
如果我们替换 deployment 中另外一个列表数据,你就会发现不同。
template:
spec:
tolerations:
- effect: NoSchedule
key: disktype
value: ssd
执行命令,将该文件内容 patch 到 deployment 中。
kubectl patch deployment patch-demo --patch "$(cat patch-file-containers.yaml)"
we will get that in deployment
...
tolerations:
- effect: NoSchedule
key: disktype
value: ssd
...
请注意,PodSpec 中的 tolerations 列表被替换,而不是合并。这是因为 PodSpec 的 tolerations 的字段标签中没有 patchStrategy 键。所以策略合并 patch 操作使用默认的 patch 策略,也就是 replace。
type PodSpec struct {
...
Tolerations []Toleration `json:"tolerations,omitempty" protobuf:"bytes,22,opt,name=tolerations"`
总结
标题虽然是 k8s patch 的几种方式,但是主要还是分析了下 JSON Patch 和 JSON Merge Patch 的不同,因为 k8s 的 Strategy Merge Patch 几乎就是 JSON Merge Patch 的一个变种,搞懂了前两种方式,这种方式也就很容易弄清楚了。
参考
json patch
json merge patch
Json Patch and Json Merge Patch — Quick Example in Java
JSON Patch and JSON Merge Patch
Update API Objects in Place Using kubectl patch