想打印k8s資源YAML結果搞懂了Client-Side & Server-Side Apply

前言

由於查看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 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定義了更加規範和準確的合併規則。 這裏抄錄文檔中的一段表格加以說明:

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方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章