侧边栏壁纸
博主头像
船长博主等级

专注于云原生运维,致敬每个爱学习的你

  • 累计撰写 35 篇文章
  • 累计创建 10 个标签
  • 累计收到 9 条评论

Kubernetes Patch的多种方式

船长
2022-02-20 / 0 评论 / 0 点赞 / 1,663 阅读 / 5,913 字
温馨提示:
本文最后更新于 2022-02-20,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

序言

在更新 k8s 资源的时候,除了 update 这种方式,k8s 也提供了 patch 来进行资源的更新。

通过 kubectl patch 来更新的时候,也提供了不同的更新方式

 # Partially update a node using a strategic merge patch. Specify the patch as JSON.
  kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}'
  
  # Partially update a node using a strategic merge patch. Specify the patch as YAML.
  kubectl patch node k8s-node-1 -p $'spec:\n unschedulable: true'
  
  # Partially update a node identified by the type and name specified in "node.json" using strategic merge patch.
  kubectl patch -f node.json -p '{"spec":{"unschedulable":true}}'
  
  # Update a container's image; spec.containers[*].name is required because it's a merge key.
  kubectl patch pod valid-pod -p '{"spec":{"containers":[{"name":"kubernetes-serve-hostname","image":"new image"}]}}'
  
  # Update a container's image using a json patch with positional arrays.
  kubectl patch pod valid-pod --type='json' -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"new
image"}]'

这里的三种方式对应的就是 JSON Patch、JSON Merge Patch 以及 k8s 自定义的 Strategic Merge Patch。

接下来看下 这三种方式分别都是怎么工作的,让大家能有一个大概的认识,在选择方案的时候,能有所帮助。

因为 Json Patch 和 Json Merge Patch 大家在其他的很多地方也会用到,因此使用场景会更多,所以会优先讲解这两种方式,我也建议大家先搞懂这两种方式。

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
来源:皮革先生

0

评论区