伺服器端應用

特性狀態: Kubernetes v1.22 [stable] (預設啟用:true)

Kubernetes 支援多個應用者協同管理單個物件的欄位。

伺服器端應用 (Server-Side Apply) 提供了一種可選機制,用於叢集控制平面跟蹤物件欄位的更改。在特定資源層面,伺服器端應用記錄並跟蹤對該物件欄位的控制資訊。

伺服器端應用幫助使用者和控制器透過宣告式配置管理其資源。客戶端可以透過提交其_完全指定的意圖_來宣告式地建立和修改物件

完全指定的意圖是一個部分物件,只包含使用者認為應該存在的欄位和值。該意圖要麼建立一個新物件(對未指定的欄位使用預設值),要麼由 API 伺服器與現有物件合併

與客戶端應用對比解釋了伺服器端應用與原始客戶端 kubectl apply 實現的區別。

欄位管理

Kubernetes API 伺服器跟蹤所有新建立物件的_已管理欄位_。

嘗試應用物件時,如果欄位具有不同的值且由其他管理者擁有,則會導致衝突。這是為了表明該操作可能會撤消其他協作者的更改。強制寫入具有已管理欄位的物件時,任何衝突欄位的值都將被覆蓋,並且所有權將轉移。

每當欄位的值發生變化時,所有權將從當前管理者轉移到進行更改的管理者。

應用會檢查是否有其他欄位管理者也擁有該欄位。如果該欄位沒有被任何其他欄位管理者擁有,則該欄位會被設定為其預設值(如果有),否則將從物件中刪除。同樣的規則也適用於列表、關聯列表或對映型別的欄位。

對於使用者來說,以伺服器端應用的方式管理欄位,意味著使用者依賴並期望該欄位的值不會改變。最後對欄位值進行斷言的使用者將被記錄為當前欄位管理者。這可以透過使用 HTTP `POST` (**建立**)、`PUT` (**更新**) 或非應用 `PATCH` (**修補**) 顯式更改欄位管理者詳細資訊來完成。你還可以透過在伺服器端應用操作中為該欄位包含一個值來宣告和記錄欄位管理者。

伺服器端應用**補丁 (patch)** 請求要求客戶端提供其作為欄位管理者的身份。使用伺服器端應用時,嘗試更改由不同管理者控制的欄位會導致請求被拒絕,除非客戶端強制覆蓋。有關覆蓋的詳細資訊,請參閱衝突

當兩個或多個應用者將欄位設定為相同值時,它們共享該欄位的所有權。任何後續嘗試更改共享欄位的值,無論是由哪個應用者執行,都會導致衝突。共享欄位所有者可以透過發出不包含該欄位的伺服器端應用**補丁**請求來放棄對欄位的所有權。

欄位管理詳細資訊儲存在物件metadata中的 managedFields 欄位中。

如果你從清單中移除某個欄位並應用該清單,伺服器端應用會檢查是否有其他欄位管理者也擁有該欄位。如果該欄位未被任何其他欄位管理者擁有,它將從活動物件中刪除,或者(如果有預設值)重置為預設值。同樣的規則適用於關聯列表或對映項。

與 `kubectl` 管理的(舊版)kubectl.kubernetes.io/last-applied-configuration 註釋相比,伺服器端應用採用了一種更宣告式的方法,它跟蹤使用者(或客戶端)的欄位管理,而不是使用者上次應用的狀態。使用伺服器端應用的一個副作用是,可以獲得關於物件中每個欄位由哪個欄位管理者管理的資訊。

示例

使用伺服器端應用建立的簡單物件示例如下:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cm
  namespace: default
  labels:
    test-label: test
  managedFields:
  - manager: kubectl
    operation: Apply # note capitalization: "Apply" (or "Update")
    apiVersion: v1
    time: "2010-10-10T0:00:00Z"
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:test-label: {}
      f:data:
        f:key: {}
data:
  key: some value

該 ConfigMap 示例物件在 .metadata.managedFields 中包含一個欄位管理記錄。該欄位管理記錄由管理實體本身的基本資訊,以及被管理的欄位和相關操作(ApplyUpdate)的詳細資訊組成。如果最後更改該欄位的請求是伺服器端應用**補丁**,則 operation 的值為 Apply;否則為 Update

還有另一種可能的結果。客戶端可能會提交一個無效的請求體。如果完全指定的意圖無法生成一個有效的物件,則請求會失敗。

然而,可以透過**更新**或不使用伺服器端應用的**補丁**操作來更改 .metadata.managedFields。強烈不鼓勵這樣做,但如果 .metadata.managedFields 出現不一致狀態(在正常操作中不應該發生),例如,這可能是一個合理的嘗試選項。

managedFields 的格式在 Kubernetes API 參考中有所描述

衝突

_衝突_是一種特殊的狀態錯誤,當 Apply 操作嘗試更改另一個管理者也聲稱管理的欄位時發生。這可以防止應用者無意中覆蓋其他使用者設定的值。發生這種情況時,應用者有 3 種選項來解決衝突:

  • **覆蓋值,成為唯一管理者:** 如果覆蓋值是故意的(或者如果應用者是像控制器這樣的自動化程序),應用者應該將 `force` 查詢引數設定為 true(對於 `kubectl apply`,使用 --force-conflicts 命令列引數),然後再次發出請求。這會強制操作成功,更改欄位的值,並從 managedFields 中所有其他管理者的條目中刪除該欄位。

  • **不覆蓋值,放棄管理宣告:** 如果應用者不再關心該欄位的值,應用者可以將其從資源的本地模型中刪除,然後發出一個不包含該特定欄位的新請求。這會使值保持不變,並導致該欄位從應用者在 managedFields 中的條目中刪除。

  • **不覆蓋值,成為共享管理者:** 如果應用者仍然關心某個欄位的值,但不想覆蓋它,他們可以在資源的本地模型中更改該欄位的值,使其與伺服器上物件的值匹配,然後發出一個考慮了該本地更新的新請求。這樣做會使值保持不變,並導致該欄位的管理由應用者與所有其他已聲稱管理它的欄位管理者共享。

欄位管理者

管理者識別修改物件的不同工作流(在衝突時特別有用!),可以透過修改請求中的fieldManager查詢引數來指定。當你對資源進行應用時,fieldManager 引數是必需的。對於其他更新,API 伺服器會從“User-Agent:”HTTP 頭(如果存在)推斷出欄位管理者身份。

當你使用 kubectl 工具執行伺服器端應用操作時,kubectl 預設將管理者身份設定為 "kubectl"

序列化

在協議層面,Kubernetes 將伺服器端應用訊息體表示為 YAML,媒體型別為 application/apply-patch+yaml

序列化與 Kubernetes 物件相同,不同之處在於客戶端無需傳送完整的物件。

以下是伺服器端應用訊息體(完全指定的意圖)的示例

{
  "apiVersion": "v1",
  "kind": "ConfigMap"
}

(如果它作為**補丁**請求的正文傳送到有效的 v1/configmaps 資源,並且具有適當的請求 Content-Type,這將是一個無更改的更新)。

欄位管理範圍內的操作

Kubernetes API 中考慮欄位管理的操作有:

  1. 伺服器端應用(HTTP `PATCH`,內容型別為 application/apply-patch+yaml
  2. 替換現有物件(Kubernetes 中的**更新**;HTTP 層面的 PUT

兩種操作都會更新 .metadata.managedFields,但行為略有不同。

除非你指定強制覆蓋,否則遇到欄位級別衝突的應用操作總是會失敗;相比之下,如果你使用**更新**進行更改,且會影響已管理欄位,衝突絕不會導致操作失敗。

所有伺服器端應用**補丁**請求都要求透過提供 fieldManager 查詢引數來標識自身,而該查詢引數對於**更新**操作是可選的。最後,在使用 Apply 操作時,你不能在提交的請求正文中定義 managedFields

一個具有多個管理者的物件示例如下:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cm
  namespace: default
  labels:
    test-label: test
  managedFields:
  - manager: kubectl
    operation: Apply
    time: '2019-03-30T15:00:00.000Z'
    apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:labels:
          f:test-label: {}
  - manager: kube-controller-manager
    operation: Update
    apiVersion: v1
    time: '2019-03-30T16:00:00.000Z'
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        f:key: {}
data:
  key: new value

在此示例中,第二個操作由名為 kube-controller-manager 的管理者作為**更新**執行。更新請求成功並更改了資料欄位中的值,導致該欄位的管理權更改為 kube-controller-manager

如果此更新嘗試使用伺服器端應用,則該請求將由於所有權衝突而失敗。

合併策略

透過伺服器端應用實現的合併策略提供了通常更穩定的物件生命週期。伺服器端應用試圖根據管理欄位的角色進行合併,而不是根據值進行否決。這樣,多個角色可以更新同一個物件而不會引起意外干擾。

當用戶向伺服器端應用端點發送一個_完全指定的意圖_物件時,伺服器會將其與活動物件合併,如果兩者都指定了該值,則優先使用請求體中的值。如果應用配置中存在的專案集不是同一使用者上次應用的專案集的超集,則所有未被其他應用者管理的缺失專案都將被刪除。有關物件模式如何用於在合併時做出決策的更多資訊,請參閱 sigs.k8s.io/structured-merge-diff

Kubernetes API(以及為 Kubernetes 實現該 API 的 Go 程式碼)允許定義_合併策略標記_。這些標記描述了 Kubernetes 物件中欄位支援的合併策略。對於CustomResourceDefinition,可以在定義自定義資源時設定這些標記。

Golang 標記OpenAPI 擴充套件可能的值描述
//+listTypex-kubernetes-list-typeatomic/set/map適用於列表。set 適用於只包含標量元素的列表。這些元素必須是唯一的。map 只適用於巢狀型別的列表。鍵值(參見 listMapKey)在列表中必須是唯一的。atomic 可以應用於任何列表。如果配置為 atomic,則在合併期間整個列表將被替換。在任何給定時間點,單個管理者擁有該列表。如果為 setmap,不同的管理者可以單獨管理條目。
//+listMapKeyx-kubernetes-list-map-keys欄位名稱列表,例如 ["port", "protocol"]僅適用於 +listType=map。欄位名稱列表,其值唯一標識列表中的條目。雖然可以有多個鍵,但 listMapKey 是單數,因為鍵需要在 Go 型別中單獨指定。鍵欄位必須是標量。
//+mapTypex-kubernetes-map-typeatomic/granular適用於對映。atomic 表示對映只能由單個管理者完全替換。granular 表示對映支援不同的管理者更新單個欄位。
//+structTypex-kubernetes-map-typeatomic/granular適用於結構體;其他用法和 OpenAPI 註釋與 //+mapType 相同。

如果 listType 缺失,API 伺服器會將 patchStrategy=merge 標記解釋為 listType=map,並將相應的 patchMergeKey 標記解釋為 listMapKey

atomic 列表型別是遞迴的。

(在 Kubernetes 的 Go 程式碼中,這些標記以註釋形式指定,程式碼作者無需在欄位標籤中重複它們)。

自定義資源和伺服器端應用

預設情況下,伺服器端應用將自定義資源視為非結構化資料。所有鍵都與結構體欄位相同對待,所有列表都視為原子性。

如果 CustomResourceDefinition 定義了一個包含前一合併策略部分中定義的註釋的模式,則在合併此型別物件時將使用這些註釋。

拓撲變更相容性

在極少數情況下,CustomResourceDefinition (CRD) 或內建資源的作者可能希望更改其資源中欄位的特定拓撲,而無需增加其 API 版本。透過升級叢集或更新 CRD 來更改型別拓撲,在更新現有物件時會產生不同的後果。有兩種型別的更改:欄位從 map/set/granular 變為 atomic,以及反向更改。

listTypemapTypestructTypemap/set/granular 變為 atomic 時,現有物件的整個列表、對映或結構體將最終由擁有這些型別元素的參與者擁有。這意味著對這些物件的任何進一步更改都將導致衝突。

listTypemapTypestructTypeatomic 變為 map/set/granular 時,API 伺服器無法推斷這些欄位的新所有權。因此,當這些欄位的物件更新時,不會產生衝突。因此,不建議將型別從 atomic 更改為 map/set/granular

以自定義資源為例

---
apiVersion: example.com/v1
kind: Foo
metadata:
  name: foo-sample
  managedFields:
  - manager: "manager-one"
    operation: Apply
    apiVersion: example.com/v1
    fieldsType: FieldsV1
    fieldsV1:
      f:spec:
        f:data: {}
spec:
  data:
    key1: val1
    key2: val2

spec.dataatomic 更改為 granular 之前,manager-one 擁有欄位 spec.data,以及其中所有欄位(key1key2)。當 CRD 更改使 spec.data 變為 granular 時,manager-one 繼續擁有頂級欄位 spec.data(這意味著沒有其他管理者可以在沒有衝突的情況下刪除名為 data 的對映),但它不再擁有 key1key2,因此其他管理者可以在沒有衝突的情況下修改或刪除這些欄位。

在控制器中使用伺服器端應用

作為控制器開發人員,你可以使用伺服器端應用來簡化控制器的更新邏輯。與讀-修改-寫和/或補丁的主要區別如下:

  • 應用的物件必須包含控制器關心的所有欄位。
  • 無法刪除控制器之前未應用的欄位(控制器仍可針對這些用例傳送**補丁**或**更新**)。
  • 無需事先讀取物件;無需指定 resourceVersion

強烈建議控制器始終對其擁有和管理的物件強制衝突,因為它們可能無法解決或處理這些衝突。

轉移所有權

除了衝突解決提供的併發控制之外,伺服器端應用還提供了將欄位所有權從使用者協調地轉移給控制器的方法。

最好的解釋是舉例說明。讓我們看看如何安全地將 replicas 欄位的所有權從使用者轉移到控制器,同時為 Deployment 啟用自動水平擴縮,使用 HorizontalPodAutoscaler 資源及其附帶的控制器。

假設使用者已經定義了一個 Deployment,並將 replicas 設定為所需值:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2

使用者已使用伺服器端應用建立 Deployment,如下所示:

kubectl apply -f https://k8s.io/examples/application/ssa/nginx-deployment.yaml --server-side

然後,後來為 Deployment 啟用了自動擴縮;例如:

kubectl autoscale deployment nginx-deployment --cpu-percent=50 --min=1 --max=10

現在,使用者希望從其配置中刪除 replicas,這樣就不會意外地與 HorizontalPodAutoscaler (HPA) 及其控制器發生衝突。然而,這裡存在一個競爭條件:HPA 可能需要一些時間才能感覺到需要調整 .spec.replicas;如果使用者在 HPA 寫入欄位併成為其所有者之前刪除了 .spec.replicas,那麼 API 伺服器會將 .spec.replicas 設定為 1(Deployment 的預設副本數)。這不是使用者想要發生的情況,即使是暫時的——這很可能會降低正在執行的工作負載的效能。

有兩種解決方案:

  • (基本)將 replicas 留在配置中;當 HPA 最終寫入該欄位時,系統會提示使用者發生衝突。此時,從配置中刪除是安全的。

  • (更高階)但是,如果使用者不想等待,例如,因為他們想讓叢集對其同事保持清晰可讀,那麼他們可以採取以下步驟來安全地從其配置中刪除 replicas

首先,使用者定義一個只包含 replicas 欄位的新清單:

# Save this file as 'nginx-deployment-replicas-only.yaml'.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3

使用者使用私有欄位管理器名稱應用該清單。在此示例中,使用者選擇了 handover-to-hpa

kubectl apply -f nginx-deployment-replicas-only.yaml \
  --server-side --field-manager=handover-to-hpa \
  --validate=false

如果應用導致與 HPA 控制器發生衝突,則不執行任何操作。衝突表明控制器在程序中比有時更早地聲明瞭該欄位。

此時使用者可以從清單中移除 replicas 欄位

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2

請注意,每當 HPA 控制器將 replicas 欄位設定為新值時,臨時欄位管理者將不再擁有任何欄位並會自動刪除。無需進一步清理。

管理者之間轉移所有權

欄位管理者可以透過在各自的應用配置中將欄位設定為相同的值來互相轉移欄位的所有權,從而使它們共享該欄位的所有權。一旦管理者共享該欄位的所有權,其中一個可以從其應用配置中刪除該欄位以放棄所有權,並完成向另一個欄位管理者的轉移。

與客戶端應用對比

伺服器端應用旨在替代原始客戶端 kubectl apply 子命令的實現,並作為控制器執行其更改的簡單有效機制。

kubectl 管理的 last-applied 註釋相比,伺服器端應用採用了一種更宣告式的方法,它跟蹤物件的欄位管理,而不是使用者上次應用的狀態。這意味著作為使用伺服器端應用的副作用,關於物件中每個欄位由哪個欄位管理者管理的資訊也變得可用。

伺服器端應用實現的衝突檢測和解決的結果是,應用者在其本地狀態中始終擁有最新的欄位值。如果不是,他們下次應用時就會遇到衝突。解決衝突的三種選項中的任何一種都會導致應用配置成為伺服器物件欄位的最新子集。

這與客戶端應用不同,在客戶端應用中,已被其他使用者覆蓋的過時值會留在應用者的本地配置中。這些值只有在使用者更新該特定欄位時(如果更新的話)才會變得準確,而且應用者無法知道他們下次應用是否會覆蓋其他使用者的更改。

另一個區別是,使用客戶端應用的應用者無法更改他們正在使用的 API 版本,但伺服器端應用支援此用例。

客戶端應用與伺服器端應用之間的遷移

從客戶端應用升級到伺服器端應用

使用 kubectl apply 管理資源的客戶端應用使用者可以透過以下標誌開始使用伺服器端應用。

kubectl apply --server-side [--dry-run=server]

預設情況下,物件的欄位管理從客戶端應用轉移到 kubectl 伺服器端應用,而不會遇到衝突。

此行為適用於使用 kubectl 欄位管理器的伺服器端應用。作為例外,您可以透過指定不同的非預設欄位管理器來選擇退出此行為,如以下示例所示。kubectl 伺服器端應用的預設欄位管理器是 kubectl

kubectl apply --server-side --field-manager=my-manager [--dry-run=server]

從伺服器端應用降級到客戶端應用

如果您使用 kubectl apply --server-side 管理資源,您可以直接使用 kubectl apply 降級到客戶端應用。

降級有效,因為如果您使用 kubectl apply,kubectl 伺服器端應用會保持 last-applied-configuration 註釋是最新的。

此行為適用於使用 kubectl 欄位管理器的伺服器端應用。作為例外,您可以透過指定不同的非預設欄位管理器來選擇退出此行為,如以下示例所示。kubectl 伺服器端應用的預設欄位管理器是 kubectl

kubectl apply --server-side --field-manager=my-manager [--dry-run=server]

API 實現

PATCH 動詞(對於支援伺服器端應用的物件)接受非官方的 application/apply-patch+yaml 內容型別。伺服器端應用的使用者可以將部分指定的物件作為 YAML 作為 PATCH 請求的正文傳送到資源的 URI。應用配置時,應始終包含所有對結果(例如所需狀態)至關重要的欄位。

所有 JSON 訊息都是有效的 YAML。因此,除了使用 YAML 請求體進行伺服器端應用請求外,您還可以使用 JSON 請求體,因為它們也是有效的 YAML。無論哪種情況,HTTP 請求都使用媒體型別 application/apply-patch+yaml

訪問控制和許可權

由於伺服器端應用是一種 PATCH 型別,因此主體(例如 Kubernetes RBAC 的 Role)需要**補丁**許可權才能編輯現有資源,還需要**建立**動詞許可權才能使用伺服器端應用建立新資源。

清除 managedFields

可以透過使用**補丁**(JSON 合併補丁、戰略性合併補丁、JSON 補丁)或透過**更新**(HTTP PUT)覆蓋所有 managedFields 來將其從物件中剝離;換句話說,透過除**應用**之外的所有寫入操作。這可以透過用空條目覆蓋 managedFields 欄位來完成。兩個示例如下:

PATCH /api/v1/namespaces/default/configmaps/example-cm
Accept: application/json
Content-Type: application/merge-patch+json

{
  "metadata": {
    "managedFields": [
      {}
    ]
  }
}
PATCH /api/v1/namespaces/default/configmaps/example-cm
Accept: application/json
Content-Type: application/json-patch+json
If-Match: 1234567890123456789

[{"op": "replace", "path": "/metadata/managedFields", "value": [{}]}]

這將用一個包含單個空條目的列表覆蓋 managedFields,然後導致 managedFields 完全從物件中剝離。請注意,將 managedFields 設定為空列表不會重置該欄位。這是故意的,因此不瞭解該欄位的客戶端永遠不會剝離 managedFields

在重置操作與對 managedFields 以外的其他欄位的更改結合使用的情況下,這將導致 managedFields 首先被重置,然後處理其他更改。因此,應用者將擁有在同一請求中更新的任何欄位的所有權。

下一步

您可以在 Kubernetes API 參考中關於metadata頂級欄位的 managedFields 部分中閱讀更多資訊。

最後修改時間:2025 年 4 月 11 日下午 12:07 PST:修復伺服器端應用的語法錯誤 (0f89a2b194)