3

想打印k8s资源YAML结果搞懂了Client-Side & Server-Side Apply - YOYOFx

 1 year ago
source link: https://www.cnblogs.com/maxzhang1985/p/17382921.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言#

由于查看k8s资源YAML时常看到沉长的YAML与手写的格式,相差甚远不利于阅读,经过探索官方文档,才理解什么是Client-Side & Server-Side Apply。

先看一下我用client-go在生成Deployment的YAML格式,核心代码如下:

k8sDeployment, _ := clientSet.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{})
k8sDeployment.Kind = "Deployment"
k8sDeployment.APIVersion = "apps/v1"
k8sDeployment.SetManagedFields(nil)  // 不显示管理字段,至于什么是管理字段? 请继续阅读后面的内容.
runtimeWorkload = k8sDeployment
yamlPrinter := printers.YAMLPrinter{}
buffers := bytes.NewBufferString("")
yamlErr := yamlPrinter.PrintObj(runtimeWorkload, buffers)
YAML := buffers.String()

效果:

176287-20230508192252085-1416311314.png

之前一直有这样一个困扰,例如这样的一个Pod资源,可以看到像kubectl.kubernetes.io/last-applied-configuration annotationmanagedFields 这两个字段,并不是我们手写YAML中存在的,他们是什么呢?

用一个例子展现:

kubectl get pods demo -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"creationTimestamp":null,"labels":{"run":"demo"},"name":"demo","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"demo","resources":{}}],"dnsPolicy":"ClusterFirst","restartPolicy":"Always"},"status":{}}
  creationTimestamp: "2022-05-28T07:28:51Z"
  labels:
    run: demo
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
        f:labels:
          .: {}
          f:run: {}
....

由这两个字段,引出本文的两位主角,Client-Side Apply(以下简称CSA)和Server-Side Apply(以下简称SSA)

  1. kubectl.kubernetes.io/last-applied-configuration是使用kubectl apply进行Client-Side Apply时,由kubectl自行填充的。
  2. managedFields 则是由kubectl apply的增强功能 Server-Side Apply 的引入而添加。

Client-Side Apply#

kubectl apply是一种声明示的K8S对象管理方式,是我们最常用的应用部署,升级方式之一。

需要特别指出的是,kubectl apply声明的仅仅是它关心的字段的状态,而不是整个对象的真实状态。apply表达的意思是:此次提交管理的字段应该和apply的配置文件一致,其它字段并不关心。

比如首次部署时,K8S会将replicas值设置为默认1,随后由HPA控制器扩容到合适的副本数。

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  # replicas: 1 不要设置replicas
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        resources: {}

当升级应用时(修改镜像版本),修改配置文件中的image字段,再次执行kubectl apply。此时kubectl apply只会影响镜像版本 ,而不会影响HPA控制器设置的副本数。在这个例子中,replicas字段不是kubectl apply管理的字段,因此更新镜像时不会被删除,避免了每次应用升级时,副本数都会被重置。

在上述例子中,为了能识别出replicas不是kubectl管理的字段,kubectl需要一个标识,用来追踪对象中哪些字段是由kubectl apply管理的,而这个标识就是last-applied-configuration。
该annotation是在kubectl apply时,由kubectl客户端自行填充——每次执行kubectl apply时(未启用SSA),kubectl会将本次apply的配置文件全量的记录在last-applied-configurationannotation中,用于追踪哪些字段由kubectl apply管理。

CSA工作工作机制:#

Apply一个资源对象时,如果该对象不存在,则创建它(同时写入last-applied-configuration)。如果对象已经存在,则kubectl需要根据以下三个状态:

  • 当前配置文件所表示的对象在集群中的真实状态。(修改对象前先Get一次)当前apply的配置。以及上次apply的配置。 (在last-applied-configuration里)。
  • 当前apply的配置。
  • 以及上次apply的配置。 (在last-applied-configuration里)

计算patch#

通过patch方式进行更新(而不是将配置文件全量的发送到服务端)。patch报文的计算方法如下:

  1. 计算需要被删除的字段。如果字段存在在last-applied-configuration中,但配置文件中没有,将删除它们。
  2. 计算需要修改或添加的字段。如果配置文件中的字段与真实状态不一致,则添加或修改它们。
  3. 对于那些last-applied-configuration中不存在的字段,不要修改它们(例如上述示例中的replicas字段)。

CSA的合并策略#

详细的patch计算示例可参考K8S文档中给出的详细示例

由此可见,last-applied-configuration体现的是一种ownership的关系,表示哪些字段是由kubectl管理,它是kubectl apply时,计算patch报文的依据。

Server-Side Apply#

Server-Side Apply 是另一种声明式的对象管理方式,和CSA的作用是基本一致的。SSA始于从1.14开始发布alpha版本,到1.16beta,到1.18beta2,终于在1.22升级为GA。
它协助用户、控制器通过声明式配置的方式管理他们的资源。 客户端可以发送完整描述的目标(A fully specified intent), 声明式地创建和修改对象。

顾名思义,SSA将对象合并的逻辑转移到了服务端(APIServer),客户端只需提交完整的配置文件,剩下的工作交给服务端处理。 在kubectl中使用SSA,只需在kubectl apply时加上--server-side参数即可,例如这样:

kubectl apply --server-side=true -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-server-side-apply
data:
  a: "a"
  b: "b"
EOF

部署成功后,查看对象会发现该对象中不再存在之前CSA中的last-applied-configuration,取而代之的是metadata.managedFields。查看上面apply创建的资源:

apiVersion: v1
data:
  a: a
  b: b
kind: ConfigMap
metadata:
  creationTimestamp: "2022-12-04T07:59:24Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        f:a: {}
        f:b: {}
    manager: kubectl
    operation: Apply
    time: "2022-12-04T07:59:24Z"
  name: test-server-side-apply
  namespace: default
  resourceVersion: "1304750"
  uid: d265df3d-b9e9-4d0f-91c2-e654f850d25a

字段管理(field management)机制追踪对象字段的变化。 当一个字段值改变时,其所有权从当前管理器(manager)转移到施加变更的管理器。 当尝试将新配置应用到一个对象时,如果字段有不同的值,且由其他管理器管理, 将会引发冲突。 冲突引发警告信号:此操作可能抹掉其他协作者的修改。 冲突可以被刻意忽略,这种情况下,值将会被改写,所有权也会发生转移。

SSA的合并策略#

在介绍SSA的合并策略前,我们先了解一下CSA的合并策略。CSA的合并规则是基于Kubernetes的strategic merge patch方式,不同的字段类型分别有各自不同的合并策略,规则比较复杂。这也导致了CSA容易产生更多的Bug。SSA针对这个问题做了优化,相较于CSA,SSA定义了更加规范和准确的合并规则。 这里抄录文档中的一段表格加以说明:

176287-20230508191907045-128911740.png

SSA的优点#

简化客户端逻辑,CSA是一个很重的客户端逻辑,里面有复杂的对象合并操作,这意味着apply这项操作和kubectl是深度绑定的,使用其他客户端或者在控制器(Controller)中难以使用apply方式来配置对象。而SSA将这些合并的逻辑转移到了服务端,提供单一的API,客户端实现方式得以简化。这让apply的能力得以整合到client-go中,让应用可以通过client-go来使用apply的能力。

更细粒度的字段所有权管理,减少错误覆盖配置的可能性

相比于last-applied-configuration,SSA使用managedFields来管理每个字段的ownership,这是一种更细粒度的字段管理方式。这使得多个管理者之间能更好的协作,且其自带冲突检测,能很大程度避免错误覆盖配置的发生。

当使用SSA时,dry-run的逻辑也放在服务端执行。相比CSA,服务端dry-run可以真实的经过validating/mutating admission webhooks的校验,从而获取最准确的返回结果。这是CSA无法实现的。

总之CSA和SSA是两种不同实现的声明示管理Kubernetes对象的方式。SSA的出现是为了解决了CSA中存在的一些挑战与问题,如apply逻辑和kubectl深度绑定、strategic merge patch复杂多bug等等。SSA发展至今已是Kubernetes中的一个关键特性,相信其最终的目标将会是完全取代CSA,成为Kubernetes中唯一的apply方式。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK