伺服器端套用
Kubernetes v1.22 [stable]
(預設啟用:true)Kubernetes 支援多個套用者協作管理單一物件的欄位。
伺服器端套用提供一種選用機制,讓您的叢集控制平面追蹤物件欄位的變更。在特定資源的層級,伺服器端套用記錄並追蹤關於該物件欄位控制權的資訊。
伺服器端套用協助使用者和控制器透過宣告式組態管理其資源。用戶端可以透過提交其完整指定的意圖,以宣告方式建立和修改物件。
完整指定的意圖是一個部分物件,僅包含使用者對其有意見的欄位和值。該意圖要麼建立一個新物件(對未指定的欄位使用預設值),要麼由 API 伺服器與現有物件合併。
與用戶端套用的比較說明伺服器端套用與原始的用戶端 kubectl apply
實作有何不同。
欄位管理
Kubernetes API 伺服器追蹤所有新建立物件的受管欄位。
當嘗試套用物件時,具有不同值且由另一個管理器擁有的欄位將導致衝突。 這樣做是為了表示該操作可能會撤銷另一個協作者的變更。 可以強制寫入具有受管欄位的物件,在這種情況下,任何衝突欄位的值都將被覆寫,並且所有權將被轉移。
每當欄位的值發生變更時,所有權都會從目前的管理器移至進行變更的管理器。
套用檢查是否有任何其他欄位管理器也擁有該欄位。 如果該欄位未被任何其他欄位管理器擁有,則該欄位將設定為其預設值(如果有的話),否則將從物件中刪除。 相同的規則適用於欄位,這些欄位是列表、關聯式列表或映射。
對於使用者來說,在伺服器端套用的意義上管理欄位,意味著使用者依賴並期望欄位的值不會變更。 最後一次聲明欄位值的用戶將被記錄為目前的欄位管理器。 這可以透過使用 HTTP POST
(建立)、PUT
(更新)或非套用 PATCH
(修補)顯式變更欄位管理器詳細資訊來完成。 您也可以透過在伺服器端套用操作中包含該欄位的值來宣告和記錄欄位管理器。
伺服器端套用 修補 請求要求用戶端提供其身分作為欄位管理器。 當使用伺服器端套用時,嘗試變更由不同管理器控制的欄位將導致請求被拒絕,除非用戶端強制覆寫。 有關覆寫的詳細資訊,請參閱衝突。
當兩個或多個套用者將欄位設定為相同的值時,他們將共享該欄位的所有權。 任何後續嘗試變更任何套用者共享欄位的值的行為都會導致衝突。 共享欄位擁有者可以透過發出不包含該欄位的伺服器端套用 修補 請求來放棄欄位的所有權。
欄位管理詳細資訊儲存在 managedFields
欄位中,該欄位是物件 metadata
的一部分。
如果您從資訊清單中移除欄位並套用該資訊清單,伺服器端套用會檢查是否有任何其他欄位管理器也擁有該欄位。 如果該欄位未被任何其他欄位管理器擁有,則會從即時物件中刪除該欄位,或將其重設為預設值(如果有的話)。 相同的規則適用於關聯式列表或映射項目。
與 (舊版) 由 kubectl
管理的 kubectl.kubernetes.io/last-applied-configuration
註解相比,伺服器端套用使用更宣告式的方法,該方法追蹤使用者(或用戶端)的欄位管理,而不是使用者上次套用的狀態。 作為使用伺服器端套用的副作用,關於哪個欄位管理器管理物件中每個欄位的資訊也變得可用。
範例
使用伺服器端套用建立的物件的簡單範例可能如下所示
注意
kubectl get
預設會省略受管欄位。 當輸出格式為 json
或 yaml
時,新增 --show-managed-fields
以顯示 managedFields
。---
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
中包含單一欄位管理記錄。 欄位管理記錄包含關於管理實體本身的基本資訊,以及關於正在管理的欄位和相關操作 (Apply
或 Update
) 的詳細資訊。 如果上次變更該欄位的請求是伺服器端套用 修補,則 operation
的值為 Apply
;否則,為 Update
。
還有另一種可能的結果。 用戶端可能會提交無效的請求主體。 如果完整指定的意圖未產生有效的物件,則請求失敗。
但是,可以透過 更新 或不使用伺服器端套用的 修補 操作來變更 .metadata.managedFields
。 非常不建議這樣做,但如果例如 .metadata.managedFields
進入不一致的狀態(在正常操作中不應發生),則這可能是一個合理的嘗試選項。
managedFields
的格式在 Kubernetes API 參考中的 描述。
注意
.metadata.managedFields
欄位由 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
。
注意
無論您提交的是 JSON 資料還是 YAML 資料,請使用 application/apply-patch+yaml
作為 Content-Type
標頭值。
所有 JSON 文件都是有效的 YAML。然而,Kubernetes 存在一個錯誤,它使用的 YAML 解析器並未完全實作 YAML 規範。某些 JSON 跳脫字元可能無法被識別。
序列化方式與 Kubernetes 物件相同,但客戶端不需要發送完整的物件。
以下是一個伺服器端套用(Server-Side Apply)訊息主體的範例(完整指定的意圖)
{
"apiVersion": "v1",
"kind": "ConfigMap"
}
(如果將其作為 patch 請求的主體發送到有效的 v1/configmaps
資源,並使用適當的請求 Content-Type
,這將會進行無變更更新)。
欄位管理範圍內的操作
考量欄位管理的 Kubernetes API 操作如下:
- 伺服器端套用(Server-Side Apply)(HTTP
PATCH
,內容類型為application/apply-patch+yaml
) - 取代現有物件(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 擴充 | 可能的值 | 描述 |
---|---|---|---|
//+listType | x-kubernetes-list-type | atomic /set /map | 適用於列表。set 適用於僅包含純量元素的列表。這些元素必須是唯一的。map 適用於僅包含巢狀類型的列表。索引鍵值(請參閱 listMapKey )在列表中必須是唯一的。atomic 可以適用於任何列表。如果配置為 atomic ,則在合併期間會替換整個列表。在任何時間點,單一管理器擁有該列表。如果是 set 或 map ,不同的管理器可以分別管理條目。 |
//+listMapKey | x-kubernetes-list-map-keys | 欄位名稱列表,例如 ["port", "protocol"] | 僅在 +listType=map 時適用。欄位名稱列表,其值唯一識別列表中的條目。雖然可以有多個索引鍵,但 listMapKey 是單數,因為索引鍵需要在 Go 類型中單獨指定。索引鍵欄位必須是純量。 |
//+mapType | x-kubernetes-map-type | atomic /granular | 適用於映射。atomic 表示映射只能由單一管理器完全替換。granular 表示映射支援不同的管理器更新個別欄位。 |
//+structType | x-kubernetes-map-type | atomic /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
,以及反向變更。
當 listType
、mapType
或 structType
從 map
/set
/granular
變更為 atomic
時,現有物件的整個列表、映射或結構最終將由擁有這些類型元素的參與者所擁有。這表示對這些物件的任何進一步變更都將導致衝突。
當 listType
、mapType
或 structType
從 atomic
變更為 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.data
從 atomic
變更為 granular
之前,manager-one
擁有欄位 spec.data
以及其中的所有欄位(key1
和 key2
)。當 CRD 變更為使 spec.data
granular
時,manager-one
繼續擁有頂層欄位 spec.data
(表示沒有其他管理器可以在沒有衝突的情況下刪除名為 data
的映射),但它不再擁有 key1
和 key2
,因此另一個管理器可以隨後修改或刪除這些欄位,而不會發生衝突。
在控制器中使用伺服器端套用(Server-Side Apply)
作為控制器的開發人員,您可以使用伺服器端套用(Server-Side Apply)來簡化控制器的更新邏輯。與讀取-修改-寫入和/或 patch 的主要差異如下:
- 套用的物件必須包含控制器關心的所有欄位。
- 沒有辦法移除控制器先前未套用過的欄位(控制器仍然可以針對這些用例發送 patch 或 update)。
- 物件不需要預先讀取;
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
注意
在這種情況下,用於 SSA 的 YAML 檔案僅包含您想要變更的欄位。如果您只想使用 SSA 修改spec.replicas
欄位,則不應提供完全符合規範的 Deployment Manifest。使用者使用私有欄位管理器名稱套用該 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 伺服器端套用,而不會遇到衝突。
注意
保持 last-applied-configuration
註解為最新狀態。該註解推斷用戶端套用管理的欄位。任何未由用戶端套用管理的欄位都會引發衝突。
例如,如果您在使用用戶端套用後使用 kubectl scale
更新了 replicas 欄位,則此欄位不歸用戶端套用所有,並且會在 kubectl apply --server-side
上產生衝突。
此行為適用於使用 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
首先被重設,然後再處理其他變更。因此,應用程式取得在同一個請求中更新的任何欄位的所有權。
注意
伺服器端套用(Server-Side Apply)無法正確追蹤未接收資源物件類型的子資源的所有權。如果您正在對這類子資源使用伺服器端套用(Server-Side Apply),則變更的欄位可能無法被追蹤。下一步
您可以在 Kubernetes API 參考資料中閱讀有關 metadata
頂層欄位內的 managedFields
的資訊。