Kubernetes 中的垃圾回收

K8SMeetup社群發表於2020-07-16

設想這麼一個場景:我們在 K8s 上建立了一個物件,它根據需要生成副本集和 Pod。在檢查時,我們遺漏了容器某個屬性的設定,因此又重新編輯了 Deployment。新的 Deployment 就產生了新的副本集物件和新的 Pod。這裡就出現了一個問題,舊的副本集和 Pop 去哪了?另外,如果直接刪除 Deployment,那副本集和 Pod 又會如何?事實就是,在刪除 Deployment 後,副本集和 Pod 也會一起被刪除,要不然叢集早就亂套了。

在這個場景之下,我們可以深入思考幾個問題:在 K8s 中該如何實現級聯刪除?有幾種級聯刪除策略?在 K8s 中有沒有可能存在孤兒物件(orphan object)?這些問題其實就是典型的垃圾回收(garbage collection,GC)問題。本文將介紹 K8s 中垃圾回收的概念以及實現方法。

什麼是垃圾回收?

一般來說,垃圾回收(GC)就是從系統中刪除未使用的物件,並釋放分配給它們的計算資源。GC 存在於所有的高階程式語言中,較低階的程式語言通過系統庫實現 GC。

GC 最常見的演算法之一是 mark-and-sweep,這個演算法會標記將刪除的物件,再進行刪除,如下圖所示:

OwnerRefernce

在物件導向的語言中,一些物件會引用其他物件或者直接由其他物件組成,k8s 也有類似形式,例如副本集管理一組 Pod,而 Deployment 又管理著副本集。

但與面嚮物件語言不同的是,在 K8s 物件的定義中,沒有明確所有者之間的關係,那麼系統要如何確定它們的關係呢?其實,在 K8s 中,每個從屬物件都具有 唯一資料欄位名稱 metadata.ownerReferences 用於確定關係。

從 Kubernetes v1.8 開始,K8s 對於 ReplicaSet、StatefulSet、DaemonSet、Deployment、Job、 CronJob 等建立或管理的物件,會自動為其設定 ownerReferences 的值。如果有需要,我們還可以手動設定 ownerReferences。

以下內容顯示了 core-dns Deployment 上 metadata.ownerReferences 的值。

k get deployment -n kube-system -o wide
NAME     READY  UP-TO-DATE  AVAILABLE  AGE  CONTAINERS  IMAGES
coredns  2/2    2           2          44d  coredns     k8s.gcr.io/coredns:1.6.7


k get rs -n kube-system -o json | jq ".items[0].metadata.name, .items[0].metadata.ownerReferences"
"coredns-66bff467f8"
[
 {
  "apiVersion": "apps/v1",
  "blockOwnerDeletion": true,
  "controller": true,
  "kind": "Deployment",
  "name": "coredns",
  "uid": "d8f29b78-439c-497e-9a45-7c33bd626a9f"
 }
]

k get pods coredns-66bff467f8-rsnmg -n kube-system -o json | jq ".metadata.name, .metadata.ownerReferences"
"coredns-66bff467f8-rsnmg"
[
 {
  "apiVersion": "apps/v1",
  "blockOwnerDeletion": true,
  "controller": true,
  "kind": "ReplicaSet",
  "name": "coredns-66bff467f8",
  "uid": "085d5398-1358-43e2-918e-2e03da18c7bd"
 }
]

認真觀察上述命令的輸出,其實它和其他物件 GC 之間是有些許差別的。物件關聯參考金字塔是顛倒的:

K8s 的垃圾回收策略

如前面所講,在 Kubernetes v1.8 之前,依賴物件邏輯刪除的實現是在客戶端,對於某些資源而言則是在控制器端。有時,客戶端會中途失敗,導致叢集狀態混亂,需要手動清理。後來為了解決這個問題,K8s 社群引入並實現了 Garbage Collector Controller(垃圾回收器),用更好用且更簡單的方式實現 GC。在 K8s 中,有兩大類 GC:

  • 級聯(Cascading):在級聯刪除中,所有者被刪除,那叢集中的從屬物件也會被刪除。
  • 孤兒(Orphan):這種情況下,對所有者的進行刪除只會將其從叢集中刪除,並使所有物件處於“孤兒”狀態。

級聯刪除

在級聯刪除(cascading deletion strategy)中,從屬物件(dependent object)與所有者物件(owner object)會被一起刪除。在級聯刪除中,又有兩種模式:前臺(foreground)後臺(background)

前臺級聯刪除(Foreground Cascading Deletion):在這種刪除策略中,所有者物件的刪除將會持續到其所有從屬物件都被刪除為止。當所有者被刪除時,會進入“正在刪除”(deletion in progress)狀態,此時:

  • 物件仍然可以通過 REST API 查詢到(可通過 kubectl 或 kuboard 查詢到)
  • 物件的 deletionTimestamp 欄位被設定
  • 物件的 metadata.finalizers 包含值 foregroundDeletion

一旦物件被設定為 “正在刪除” 狀態,垃圾回收器將刪除其從屬物件。當垃圾回收器已經刪除了所有的“blocking”從屬物件(ownerReference.blockOwnerDeletion=true 的物件)以後,將刪除所有者物件。

後臺級聯刪除(Background Cascading Deletion):這種刪除策略會簡單很多,它會立即刪除所有者的物件,並由垃圾回收器在後臺刪除其從屬物件。這種方式比前臺級聯刪除快的多,因為不用等待時間來刪除從屬物件。

孤兒刪除

孤兒刪除策略(orphan deletion strategy)中,會直接刪除所有者物件,並將從屬物件中的 ownerReference 後設資料設定為預設值。之後垃圾回收器會確定孤兒物件並將其刪除。

垃圾回收器如何工作?

如果物件的 OwnerReferences 後設資料中沒有任何所有者物件,那麼垃圾回收器會刪除該物件。垃圾回收器由 Scanner、Garbage Processor 和 Propagator 組成

Scanner:它會檢測 K8s 叢集中支援的所有資源,並通過控制迴圈週期性地檢測。它會掃描系統中的所有資源,並將每個物件新增到"髒佇列"(dirty queue)中。

Garbage Processor:它由在"髒佇列"上工作的 worker 組成。每個 worker 都會從"髒佇列"中取出物件,並檢查該物件裡的 OwnerReference 欄位是否為空。如果為空,那就從“髒佇列”中取出下一個物件進行處理;如果不為空,它會檢測 OwnerReference 欄位內的 owner resoure object 是否存在,如果不存在,會請求 API 伺服器刪除該物件。

Propagator :用於優化垃圾回收器,它包含以下三個元件:

  • EventQueue:負責儲存 k8s 中資源物件的事件
  • DAG(有向無環圖):負責儲存 k8s 中所有資源物件的 owner-dependent 關係
  • Worker:從 EventQueue 中取出資源物件的事件,並根據事件的型別會採取操作

在有了 Propagator 的加入之後,我們完全可以僅在 GC 開始執行的時候,讓 Scanner 掃描系統中所有的物件,然後將這些資訊傳遞給 Propagator 和“髒佇列”。只要 DAG 一建立起來之後,那麼 Scanner 其實就沒有再工作的必要了。

總體而言,K8s 中 GC 的實現是非常通用且非常有效,希望這篇文章可以幫助大家更加了解 K8s 中的 GC。

原文請點選:https://mp.weixin.qq.com/s/zhygwHbdK1h7sViHWN93hw

相關文章