由于查看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()
效果:
之前一直有这样一个困扰,例如这样的一个Pod资源,可以看到像kubectl.kubernetes.io/last-applied-configuration annotation和 managedFields 这两个字段,并不是我们手写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)
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管理。
Apply一个资源对象时,如果该对象不存在,则创建它(同时写入last-applied-configuration)。如果对象已经存在,则kubectl需要根据以下三个状态:
通过patch方式进行更新(而不是将配置文件全量的发送到服务端)。patch报文的计算方法如下:
详细的patch计算示例可参考K8S文档中给出的详细示例。
由此可见,last-applied-configuration体现的是一种ownership的关系,表示哪些字段是由kubectl管理,它是kubectl apply时,计算patch报文的依据。
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的合并策略前,我们先了解一下CSA的合并策略。CSA的合并规则是基于Kubernetes的strategic merge patch方式,不同的字段类型分别有各自不同的合并策略,规则比较复杂。这也导致了CSA容易产生更多的Bug。SSA针对这个问题做了优化,相较于CSA,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方式。