本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

使用 Finalizer 控制刪除

在 Kubernetes 中刪除物件可能具有挑戰性。您可能認為已經刪除了某個東西,卻發現它仍然存在。雖然發出 kubectl delete 命令並寄希望於最好的結果可能適用於日常操作,但瞭解 Kubernetes delete 命令的運作方式將有助於您理解為什麼某些物件在刪除後仍然存在。

在這篇文章中,我將探討:

  • 資源的哪些屬性控制刪除
  • Finalizer 和 Owner Reference 如何影響物件刪除
  • 如何使用傳播策略更改刪除順序
  • 刪除的運作方式,並附帶示例

為簡單起見,所有示例都將使用 ConfigMap 和基本的 shell 命令來演示該過程。我們將探討這些命令的運作方式,並討論在實踐中使用它們的影響和結果。

基本 delete

Kubernetes 有多種不同的命令,您可以用來建立、讀取、更新和刪除物件。為了本部落格文章的目的,我們將重點關注四個 kubectl 命令:creategetpatchdelete

以下是基本 kubectl delete 命令的示例:

kubectl create configmap mymap
configmap/mymap created
kubectl get configmap/mymap
NAME    DATA   AGE
mymap   0      12s
kubectl delete configmap/mymap
configmap "mymap" deleted
kubectl get configmap/mymap
Error from server (NotFound): configmaps "mymap" not found

$ 開頭的 shell 命令後面跟著它們的輸出。您可以看到我們從 kubectl create configmap mymap 開始,它將建立一個空的 configmap mymap。接下來,我們需要 get 這個 configmap 來證明它存在。然後我們可以刪除這個 configmap。再次嘗試 get 它會產生一個 HTTP 404 錯誤,這意味著找不到該 configmap。

基本 delete 命令的狀態圖非常簡單:

State diagram for delete

刪除的狀態圖

儘管此操作很簡單,但其他因素可能會干擾刪除,包括 finalizer 和 owner reference。

理解 Finalizer

當涉及到理解 Kubernetes 中的資源刪除時,瞭解 finalizer 的工作原理很有幫助,並且可以幫助您理解為什麼某些物件沒有被刪除。

Finalizer 是資源上的鍵,用於指示預刪除操作。它們控制資源的垃圾回收,旨在提醒控制器在刪除資源之前執行哪些清理操作。然而,它們不一定指定應該執行的程式碼;資源上的 finalizer 基本上只是一系列鍵,很像註解。與註解一樣,它們可以被操縱。

您可能遇到過的一些常見 finalizer 是:

  • kubernetes.io/pv-protection
  • kubernetes.io/pvc-protection

上述 finalizer 用於卷,以防止意外刪除。類似地,一些 finalizer 可以用於防止刪除任何資源,但不受任何控制器管理。

下面是一個自定義 ConfigMap,它沒有屬性但包含一個 finalizer:

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: mymap
  finalizers:
  - kubernetes
EOF

ConfigMap 資源控制器不明白如何處理 kubernetes finalizer 鍵。我將其稱為 ConfigMap 的“死”finalizer,因為它通常用於名稱空間。以下是嘗試刪除 ConfigMap 時發生的情況:

kubectl delete configmap/mymap &
configmap "mymap" deleted
jobs
[1]+  Running kubectl delete configmap/mymap

Kubernetes 將報告物件已被刪除,但是,它並未以傳統意義上的方式被刪除。相反,它正在刪除過程中。當我們嘗試再次 get 該物件時,我們發現該物件已被修改,包含了刪除時間戳。

kubectl get configmap/mymap -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: "2020-10-22T21:30:18Z"
  deletionGracePeriodSeconds: 0
  deletionTimestamp: "2020-10-22T21:30:34Z"
  finalizers:
  - kubernetes
  name: mymap
  namespace: default
  resourceVersion: "311456"
  selfLink: /api/v1/namespaces/default/configmaps/mymap
  uid: 93a37fed-23e3-45e8-b6ee-b2521db81638

簡而言之,發生的情況是物件被更新了,而不是被刪除了。這是因為 Kubernetes 發現該物件包含 finalizer,並阻止了從 etcd 中刪除該物件。刪除時間戳表示已請求刪除,但在我們編輯物件並移除 finalizer 之前,刪除將不會完成。

下面演示如何使用 patch 命令刪除 finalizer。如果我們要刪除一個物件,我們可以在命令列上簡單地修補它以刪除 finalizer。透過這種方式,在後臺執行的刪除操作將完成,物件將被刪除。當我們嘗試 get 該 ConfigMap 時,它將消失。

kubectl patch configmap/mymap \
    --type json \
    --patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'
configmap/mymap patched
[1]+  Done  kubectl delete configmap/mymap

kubectl get configmap/mymap -o yaml
Error from server (NotFound): configmaps "mymap" not found

這是 finalization 的狀態圖:

State diagram for finalize

終結的狀態圖

因此,如果您嘗試刪除一個帶有終結器 (finalizer) 的物件,它將一直處於終結狀態,直到控制器刪除了終結器鍵,或者使用 Kubectl 刪除了終結器。一旦該終結器列表為空,Kubernetes 就可以真正回收該物件,並將其放入佇列中以從登錄檔中刪除。

Owner Reference

所有者引用描述了物件組之間的關係。它們是資源上的屬性,指定了彼此之間的關係,因此可以刪除整個資源樹。

當存在所有者引用時,將處理 Finalizer 規則。所有者引用由名稱和 UID 組成。所有者引用連結同一名稱空間中的資源,並且它還需要一個 UID 才能使該引用生效。Pod 通常擁有對擁有副本集的 Owner Reference。因此,當刪除 Deployment 或 StatefulSet 時,子副本集和 Pod 也會在此過程中被刪除。

以下是一些所有者引用及其工作方式的示例。在第一個示例中,我們首先建立一個父物件,然後建立子物件。結果是一個非常簡單的 ConfigMap,其中包含對其父物件的所有者引用:

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: mymap-parent
EOF
CM_UID=$(kubectl get configmap mymap-parent -o jsonpath="{.metadata.uid}")

cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: mymap-child
  ownerReferences:
  - apiVersion: v1
    kind: ConfigMap
    name: mymap-parent
    uid: $CM_UID
EOF

當涉及所有者引用時,刪除子物件不會刪除父物件:

kubectl get configmap
NAME           DATA   AGE
mymap-child    0      12m4s
mymap-parent   0      12m4s

kubectl delete configmap/mymap-child
configmap "mymap-child" deleted

kubectl get configmap
NAME           DATA   AGE
mymap-parent   0      12m10s

在這個例子中,我們重新建立了上面提到的父子 ConfigMap。現在,當從父級(而不是子級)刪除時,如果子級對父級有 Owner Reference,當我們 get 這些 ConfigMap 時,名稱空間中沒有任何 ConfigMap。

kubectl get configmap
NAME           DATA   AGE
mymap-child    0      10m2s
mymap-parent   0      10m2s

kubectl delete configmap/mymap-parent
configmap "mymap-parent" deleted

kubectl get configmap
No resources found in default namespace.

總而言之,當子級到父級存在覆蓋所有者引用時,刪除父級會自動刪除子級。這稱為 cascade(級聯)。cascade 的預設值為 true,但是,您可以使用 kubectl delete--cascade=orphan 選項來刪除一個物件並使其子物件成為孤兒。更新:從 kubectl v1.20 開始,cascade 的預設值為 background

在下面的示例中,有一個父級和一個子級。請注意,所有者引用仍然包含在內。如果我使用 --cascade=orphan 刪除父級,父級被刪除,但子級仍然存在。

kubectl get configmap
NAME           DATA   AGE
mymap-child    0      13m8s
mymap-parent   0      13m8s

kubectl delete --cascade=orphan configmap/mymap-parent
configmap "mymap-parent" deleted

kubectl get configmap
NAME          DATA   AGE
mymap-child   0      13m21s

--cascade 選項鍊接到 API 中的傳播策略,該策略允許您更改樹中物件被刪除的順序。以下示例使用 API 訪問來構建一個帶有後臺傳播策略的自定義刪除 API 呼叫:

kubectl proxy --port=8080 &
Starting to serve on 127.0.0.1:8080

curl -X DELETE \
  localhost:8080/api/v1/namespaces/default/configmaps/mymap-parent \
  -d '{ "kind":"DeleteOptions", "apiVersion":"v1", "propagationPolicy":"Background" }' \
  -H "Content-Type: application/json"
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Success",
  "details": { ... }
}

請注意,傳播策略無法透過 kubectl 命令列指定。您必須使用自定義 API 呼叫來指定它。只需建立一個代理,以便您從客戶端訪問 API 伺服器,然後執行一個帶有 URL 的 curl 命令來執行該 delete 命令。

傳播策略有三種不同的選項:

  • Foreground(前臺):子級在父級之前刪除(後序)
  • Background(後臺):父級在子級之前刪除(前序)
  • Orphan(孤立):所有者引用被忽略

請記住,當您刪除一個物件並且已指定了 Owner Reference 時,Finalizer 將在此過程中得到遵守。這可能導致物件樹持久存在,並且最終導致部分刪除。此時,您必須檢視物件上任何現有的 Owner Reference 以及任何 Finalizer,才能瞭解正在發生什麼。

強制刪除名稱空間

有一種情況可能需要強制終結名稱空間。如果您已經刪除了一個名稱空間並清理了其中的所有物件,但該名稱空間仍然存在,則可以透過更新名稱空間子資源 finalize 來強制刪除。這會通知名稱空間控制器需要從名稱空間中移除終結器並執行任何清理操作:

cat <<EOF | curl -X PUT \
  localhost:8080/api/v1/namespaces/test/finalize \
  -H "Content-Type: application/json" \
  --data-binary @-
{
  "kind": "Namespace",
  "apiVersion": "v1",
  "metadata": {
    "name": "test"
  },
  "spec": {
    "finalizers": null
  }
}
EOF

應謹慎執行此操作,因為它可能只刪除名稱空間,而留下孤立物件在現在不存在的名稱空間中——這對於 Kubernetes 來說是一種混亂的狀態。如果發生這種情況,可以手動重新建立名稱空間,有時孤立的物件會重新出現在剛建立的名稱空間下,這將允許手動清理和恢復。

主要要點

正如這些示例所示,finalizer 會阻礙 Kubernetes 中資源的刪除,尤其是在物件之間存在父子關係時。通常,在程式碼中新增 finalizer 是有原因的,因此在手動刪除之前應始終進行調查。Owner Reference 允許您指定和刪除資源樹,儘管在此過程中 finalizer 將被遵守。最後,傳播策略可用於透過自定義 API 呼叫指定刪除順序,從而讓您控制物件的刪除方式。現在您對 Kubernetes 中刪除的工作原理有了更多瞭解,我們建議您在測試叢集上親自嘗試一下。