前言
經常操作 Kubernetes 集群的同學肯定對 finalizers
字段不陌生,每當刪除 namespace 或 pod 等一些 Kubernetes 資源時,有時資源狀態會卡在 Terminating
,很長時間無法刪除,甚至有時增加 --force
flag 之后還是無法正常刪除。這時就需要 edit
該資源,將 finalizers
字段設置為 [],之后 Kubernetes 資源就正常刪除了。
Finalizers
Finalizers 字段屬於 Kubernetes GC 垃圾收集器,是一種刪除攔截機制,能夠讓控制器實現異步的刪除前(Pre-delete)回調。比如你給 API 類型中的每個對象都創建了對應的外部資源,你希望在 k8s 刪除對應資源時同時刪除關聯的外部資源,那么可以通過 Finalizers 來實現。
Finalizers存在於任何一個資源對象的 Meta中,在 k8s 源碼中聲明為 []string
,該 Slice 的內容為需要執行的攔截器名稱。當 Finalizers 字段存在時,相關資源不允許被強制刪除。存在 Finalizers 字段的的資源對象接收的第一個刪除請求設置 metadata.deletionTimestamp
字段的值, 但不刪除具體資源,在該字段設置后, finalizer
列表中的對象只能被刪除,不能做其他操作。
當 metadata.deletionTimestamp
字段被設置時,負責監測該對象的各個控制器會通過輪詢對該對象的更新請求來執行它們所要處理的所有 Finalizer。當所有 Finalizer 都被執行過,資源被刪除。
metadata.deletionGracePeriodSeconds 的取值控制對更新的輪詢周期。
每個控制器要負責將其 Finalizer 從列表中去除。
每執行完一個就從 finalizers
中移除一個,直到 finalizers
為空,之后其宿主資源才會被真正的刪除。
Operator finalizers 使用
介紹了 Finalizers 概念,那么我們來看看在 Operator 中如何使用,在 Operator Controller 中,最重要的邏輯就是 Reconcile 方法,finalizers 也是在 Reconcile 中實現的。要注意的是,設置了 Finalizers 會導致 k8s 的 delete 動作轉為設置 metadata.deletionTimestamp
字段,如果你通過 kubectl get
命令看到資源存在這個字段,則表示資源正在刪除(deleting)。
有以下幾點需要理解:
- 如果資源對象未被刪除且未設置 finalizers,則添加 finalizer並更新 k8s 資源對象;
- 如果正在刪除資源對象並且 finalizers 仍然存在於 finalizers 列表中,則執行 pre-delete hook並刪除 finalizers ,更新資源對象;
- 由於以上兩點,需要確保 pre-delete hook是冪等的。
kuberbuilder 示例
我們來看一個 kubebuilder 官方示例:
func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() log := r.Log.WithValues("cronjob", req.NamespacedName) var cronJob batch.CronJob if err := r.Get(ctx, req.NamespacedName, &cronJob); err != nil { log.Error(err, "unable to fetch CronJob") return ctrl.Result{}, ignoreNotFound(err) } // 聲明 finalizer 字段,類型為字符串 myFinalizerName := "storage.finalizers.tutorial.kubebuilder.io" // 通過檢查 DeletionTimestamp 字段是否為0 判斷資源是否被刪除 if cronJob.ObjectMeta.DeletionTimestamp.IsZero() { // 如果為0 ,則資源未被刪除,我們需要檢測是否存在 finalizer,如果不存在,則添加,並更新到資源對象中 if !containsString(cronJob.ObjectMeta.Finalizers, myFinalizerName) { cronJob.ObjectMeta.Finalizers = append(cronJob.ObjectMeta.Finalizers, myFinalizerName) if err := r.Update(context.Background(), cronJob); err != nil { return ctrl.Result{}, err } } } else { // 如果不為 0 ,則對象處於刪除中 if containsString(cronJob.ObjectMeta.Finalizers, myFinalizerName) { // 如果存在 finalizer 且與上述聲明的 finalizer 匹配,那么執行對應 hook 邏輯 if err := r.deleteExternalResources(cronJob); err != nil { // 如果刪除失敗,則直接返回對應 err,controller 會自動執行重試邏輯 return ctrl.Result{}, err } // 如果對應 hook 執行成功,那么清空 finalizers, k8s 刪除對應資源(因為存在deletionTimestamp字段,k8s會自動回收資源) cronJob.ObjectMeta.Finalizers = removeString(cronJob.ObjectMeta.Finalizers, myFinalizerName) if err := r.Update(context.Background(), cronJob); err != nil { return ctrl.Result{}, err } } return ctrl.Result{}, err } } func (r *Reconciler) deleteExternalResources(cronJob *batch.CronJob) error { // // 刪除 crobJob關聯的外部資源邏輯 // // 需要確保實現是冪等的 } func containsString(slice []string, s string) bool { for _, item := range slice { if item == s { return true } } return false } func removeString(slice []string, s string) (result []string) { for _, item := range slice { if item == s { continue } result = append(result, item) } return }
總結
在開發 Operator 時,pre-delete hook 是一個很常見的需求,目前只發現了 Finalizers 適合實現這個功能,需要好好掌握。
參考:https://cloud.tencent.com/developer/article/1703237
參考:https://zdyxry.github.io/2019/09/13/Kubernetes-%E5%AE%9E%E6%88%98-Operator-Finalizers/