伺服器端套用

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

Kubernetes 支援多個套用者協作管理單一物件的欄位。

伺服器端套用提供一種選用機制,讓您的叢集控制平面追蹤物件欄位的變更。在特定資源的層級,伺服器端套用記錄並追蹤關於該物件欄位控制權的資訊。

伺服器端套用協助使用者和控制器透過宣告式組態管理其資源。用戶端可以透過提交其完整指定的意圖,以宣告方式建立和修改物件

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

與用戶端套用的比較說明伺服器端套用與原始的用戶端 kubectl apply 實作有何不同。

欄位管理

Kubernetes API 伺服器追蹤所有新建立物件的受管欄位

當嘗試套用物件時,具有不同值且由另一個管理器擁有的欄位將導致衝突。 這樣做是為了表示該操作可能會撤銷另一個協作者的變更。 可以強制寫入具有受管欄位的物件,在這種情況下,任何衝突欄位的值都將被覆寫,並且所有權將被轉移。

每當欄位的值發生變更時,所有權都會從目前的管理器移至進行變更的管理器。

套用檢查是否有任何其他欄位管理器也擁有該欄位。 如果該欄位未被任何其他欄位管理器擁有,則該欄位將設定為其預設值(如果有的話),否則將從物件中刪除。 相同的規則適用於欄位,這些欄位是列表、關聯式列表或映射。

對於使用者來說,在伺服器端套用的意義上管理欄位,意味著使用者依賴並期望欄位的值不會變更。 最後一次聲明欄位值的用戶將被記錄為目前的欄位管理器。 這可以透過使用 HTTP POST建立)、PUT更新)或非套用 PATCH修補)顯式變更欄位管理器詳細資訊來完成。 您也可以透過在伺服器端套用操作中包含該欄位的值來宣告和記錄欄位管理器。

伺服器端套用 修補 請求要求用戶端提供其身分作為欄位管理器。 當使用伺服器端套用時,嘗試變更由不同管理器控制的欄位將導致請求被拒絕,除非用戶端強制覆寫。 有關覆寫的詳細資訊,請參閱衝突

當兩個或多個套用者將欄位設定為相同的值時,他們將共享該欄位的所有權。 任何後續嘗試變更任何套用者共享欄位的值的行為都會導致衝突。 共享欄位擁有者可以透過發出不包含該欄位的伺服器端套用 修補 請求來放棄欄位的所有權。

欄位管理詳細資訊儲存在 managedFields 欄位中,該欄位是物件 metadata 的一部分。

如果您從資訊清單中移除欄位並套用該資訊清單,伺服器端套用會檢查是否有任何其他欄位管理器也擁有該欄位。 如果該欄位未被任何其他欄位管理器擁有,則會從即時物件中刪除該欄位,或將其重設為預設值(如果有的話)。 相同的規則適用於關聯式列表或映射項目。

與 (舊版) 由 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 查詢參數在修改請求中指定。當您對資源執行 Apply 操作時,fieldManager 參數是必要的。對於其他更新,API 伺服器會從 "User-Agent:" HTTP 標頭(如果存在)推斷欄位管理器的身分。

當您使用 kubectl 工具執行伺服器端套用(Server-Side Apply)操作時,kubectl 預設會將管理器身分設定為 "kubectl"

序列化

在協定層級,Kubernetes 將伺服器端套用(Server-Side Apply)訊息主體表示為 YAML,媒體類型為 application/apply-patch+yaml

序列化方式與 Kubernetes 物件相同,但客戶端不需要發送完整的物件。

以下是一個伺服器端套用(Server-Side Apply)訊息主體的範例(完整指定的意圖)

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

(如果將其作為 patch 請求的主體發送到有效的 v1/configmaps 資源,並使用適當的請求 Content-Type,這將會進行無變更更新)。

欄位管理範圍內的操作

考量欄位管理的 Kubernetes API 操作如下:

  1. 伺服器端套用(Server-Side Apply)(HTTP PATCH,內容類型為 application/apply-patch+yaml
  2. 取代現有物件(Kubernetes 的 update;HTTP 層級的 PUT

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

除非您指定強制覆寫,否則遇到欄位層級衝突的 apply 操作總是會失敗;相反地,如果您使用 update 進行變更,而該變更會影響受管理的欄位,則衝突永遠不會導致操作失敗。

所有伺服器端套用(Server-Side Apply)的 patch 請求都必須透過提供 fieldManager 查詢參數來識別自身,而 update 操作的查詢參數是選用的。最後,當使用 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 的管理器作為 update 執行的。更新請求成功並變更了 data 欄位中的值,這導致該欄位的管理權限變更為 kube-controller-manager

如果此更新改為嘗試使用伺服器端套用(Server-Side Apply),則請求將會因為所有權衝突而失敗。

合併策略

使用伺服器端套用(Server-Side Apply)實作的合併策略,提供通常更穩定的物件生命週期。伺服器端套用嘗試根據管理欄位的參與者來合併欄位,而不是根據值來否決。這樣一來,多個參與者可以更新同一個物件,而不會造成意外的干擾。

當使用者將完整指定的意圖物件發送到伺服器端套用(Server-Side Apply)端點時,伺服器會將其與即時物件合併,如果請求主體和即時物件中都指定了值,則優先採用請求主體中的值。如果套用配置中存在的項目集合不是同一使用者上次套用項目的超集合,則會移除每個未被任何其他應用程式管理器的遺失項目。有關物件綱要如何在合併時用於做出決策的更多資訊,請參閱 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適用於結構;否則與 //+mapType 的用法和 OpenAPI 註解相同。

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

atomic 列表類型是遞迴的。

(在 Kubernetes 的 Go 程式碼中,這些標記指定為註解,程式碼作者不需要將它們作為欄位標籤重複)。

自訂資源和伺服器端套用(Server-Side Apply)

預設情況下,伺服器端套用(Server-Side Apply)將自訂資源視為非結構化資料。所有索引鍵都被視為與結構欄位相同,並且所有列表都被視為 atomic。

如果 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,因此另一個管理器可以隨後修改或刪除這些欄位,而不會發生衝突。

在控制器中使用伺服器端套用(Server-Side Apply)

作為控制器的開發人員,您可以使用伺服器端套用(Server-Side Apply)來簡化控制器的更新邏輯。與讀取-修改-寫入和/或 patch 的主要差異如下:

  • 套用的物件必須包含控制器關心的所有欄位。
  • 沒有辦法移除控制器先前未套用過的欄位(控制器仍然可以針對這些用例發送 patchupdate)。
  • 物件不需要預先讀取;resourceVersion 不需要指定。

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

轉移所有權

除了 衝突解決 提供的並行控制之外,伺服器端套用(Server-Side Apply)還提供從使用者到控制器執行協調欄位所有權轉移的方法。

最好透過範例來說明。讓我們看看如何在啟用 Deployment 的自動水平擴展時,使用 HorizontalPodAutoscaler 資源及其隨附的控制器,安全地將 replicas 欄位的所有權從使用者轉移到控制器。

假設使用者已定義 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

使用者已使用伺服器端套用(Server-Side Apply)建立 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

首先,使用者定義一個新的 Manifest,其中僅包含 replicas 欄位

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

使用者使用私有欄位管理器名稱套用該 Manifest。在此範例中,使用者選擇了 handover-to-hpa

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

如果套用操作導致與 HPA 控制器發生衝突,則不執行任何操作。衝突表示控制器在流程中比有時更早聲稱了該欄位。

此時,使用者可以從其 Manifest 中移除 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 欄位設定為新值時,臨時欄位管理器將不再擁有任何欄位,並將自動刪除。無需進一步清理。

在管理器之間轉移所有權

欄位管理器可以透過在其各自的套用配置中將欄位設定為相同的值,從而在彼此之間轉移欄位的所有權,從而使他們共享該欄位的所有權。一旦管理器共享欄位的所有權,其中一個管理器可以從其套用配置中移除該欄位,以放棄所有權並完成向另一個欄位管理器的轉移。

與用戶端套用(Client-Side Apply)的比較

伺服器端套用(Server-Side Apply)旨在取代 kubectl apply 子命令的原始用戶端實作,並作為 控制器 實施變更的簡單有效機制。

kubectl 管理的 last-applied 註解相比,伺服器端套用(Server-Side Apply)使用更宣告式的方法,它追蹤物件的欄位管理,而不是使用者上次套用的狀態。這表示,作為使用伺服器端套用(Server-Side Apply)的副作用,有關哪個欄位管理器管理物件中每個欄位的資訊也變得可用。

伺服器端套用(Server-Side Apply)實作的衝突偵測和解決方案的結果是,應用程式始終在其本地狀態中具有最新的欄位值。如果沒有,則下次套用時會發生衝突。解決衝突的三個選項中的任何一個都會導致套用的配置成為伺服器欄位上物件的最新子集。

這與用戶端套用(Client-Side Apply)不同,在用戶端套用中,已被其他使用者覆寫的過時值會保留在應用程式的本地配置中。這些值僅在使用者更新該特定欄位時才會變得準確(如果有的話),並且應用程式無法知道他們的下一次套用是否會覆寫其他使用者的變更。

另一個差異是,使用用戶端套用(Client-Side Apply)的應用程式無法變更他們正在使用的 API 版本,但伺服器端套用(Server-Side Apply)支援此用例。

用戶端套用和伺服器端套用之間的遷移

從用戶端套用升級到伺服器端套用

使用 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 實作

支援伺服器端套用(Server-Side Apply)的資源的 PATCH 動詞可以接受非官方的 application/apply-patch+yaml 內容類型。伺服器端套用(Server-Side Apply)的使用者可以將部分指定的物件作為 YAML 發送到資源 URI 的 PATCH 請求主體中。在套用配置時,您應始終包含對您要定義的結果(例如所需狀態)重要的所有欄位。

所有 JSON 訊息都是有效的 YAML。某些客戶端使用也是有效 JSON 的 YAML 請求主體來指定伺服器端套用(Server-Side Apply)請求。

存取控制和權限

由於伺服器端套用(Server-Side Apply)是一種 PATCH 類型,因此主體(例如 Kubernetes RBAC 的角色)需要 patch 權限才能編輯現有資源,並且還需要 create 動詞權限才能使用伺服器端套用(Server-Side Apply)建立新資源。

清除 managedFields

可以透過使用 patch(JSON Merge Patch、Strategic Merge Patch、JSON Patch)或 update(HTTP PUT)覆寫物件中的所有 managedFields;換句話說,透過 apply 以外的每個寫入操作。這可以透過使用空條目覆寫 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 的資訊。

上次修改時間:2024 年 11 月 25 日下午 4:35 PST:SSA:修復合併策略標記的表格 (3f677e9bac)