前言
經常操作 Kubernetes 集群的同學肯定對 finalizers
字段不陌生,每當刪除 namespace 或 pod 等一些 Kubernetes 資源時,有時資源狀態會卡在 Terminating
,很長時間無法刪除,甚至有時增加 --force
flag 之後還是無法正常刪除。這時就需要 edit
該資源,將 finalizers
字段設置為 [],之後 Kubernetes 資源就正常刪除了。
這是一個比較常見的操作,但是當有人問 finalizers
字段的作用是什麼的時候,我是懵逼的,我甚至不知道這個熟悉又陌生的單詞怎麼讀!那麼這篇文章就來探索一下 finalizers
這個字段到底是做什麼的,在實踐中應該怎麼應用這個字段。(另外,這個單詞讀作 ['faɪnəlaɪzər])
Finalizers
Finalizers 字段屬於 Kubernetes GC 垃圾收集器,是一種刪除攔截機制,能夠讓控制器實現異步的刪除前(Pre-delete)回調。其存在於任何一個資源對象的 Meta 中,在 k8s 源碼中聲明為 []string
,該 Slice 的內容為需要執行的攔截器名稱。
對帶有 Finalizer 的對象的第一個刪除請求會為其 metadata.deletionTimestamp
設置一個值,但不會真的刪除對象。一旦此值被設置,finalizers 列表中的值就只能被移除。
當 metadata.deletionTimestamp
字段被設置時,負責監測該對象的各個控制器會通過輪詢對該對象的更新請求來執行它們所要處理的所有 Finalizer。 當所有 Finalizer 都被執行過,資源被刪除。
metadata.deletionGracePeriodSeconds
的取值控制對更新的輪詢週期。
每個控制器要負責將其 Finalizer 從列表中去除。
每執行完一個就從 finalizers
中移除一個,直到 finalizers
為空,之後其宿主資源才會被真正的刪除。
// DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource is expected to be deleted (no longer visible
// from resource lists, and not reachable by name) after the time in this field, once the
// finalizers list is empty. As long as the finalizers list contains items, deletion is blocked.
// Once the deletionTimestamp is set, this value may not be unset or be set further into the
// future, although it may be shortened or the resource may be deleted prior to this time.
// For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react
// by sending a graceful termination signal to the containers in the pod. After that 30 seconds,
// the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup,
// remove the pod from the API. In the presence of network partitions, this object may still
// exist after this timestamp, until an administrator or automated process can determine the
// resource is fully terminated.
// If not set, graceful deletion of the object has not been requested.
//
// Populated by the system when a graceful deletion is requested.
// Read-only.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"`
// Number of seconds allowed for this object to gracefully terminate before
// it will be removed from the system. Only set when deletionTimestamp is also set.
// May only be shortened.
// Read-only.
// +optional
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"`
// Must be empty before the object is deleted from the registry. Each entry
// is an identifier for the responsible component that will remove the entry
// from the list. If the deletionTimestamp of the object is non-nil, entries
// in this list can only be removed.
// Finalizers may be processed and removed in any order. Order is NOT enforced
// because it introduces significant risk of stuck finalizers.
// finalizers is a shared field, any actor with permission can reorder it.
// If the finalizer list is processed in order, then this can lead to a situation
// in which the component responsible for the first finalizer in the list is
// waiting for a signal (field value, external system, or other) produced by a
// component responsible for a finalizer later in the list, resulting in a deadlock.
// Without enforced ordering finalizers are free to order amongst themselves and
// are not vulnerable to ordering changes in the list.
// +optional
// +patchStrategy=merge
Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"`
在 Operator 中的應用
知道了 Finalizers 是什麼,那麼當然也要知道怎麼用 Finalizers 了。在實際開發 Operator 時,刪除前(Pre-delete)回調是一個比較常見的功能,用於處理一些在資源刪除前需要處理的邏輯,如:關聯資源釋放、釋放資源通知、相關數據清理,甚至是阻止資源刪除。一般 Finalizers 的處理也是會 Reconcile
中實現的,下面就使用 chaosblade-operator 中的源碼,簡單介紹一些 Finalizers 的使用方式。
首先要了解的是 ChaosBlade-Operator 的工作原理:每個實驗都會以 CR 的形式部署到 k8s 集群中,之後由 chaosblade-operator
來操作以 DaemonSet 形式部署 chaosblade-tool
對具體資源進行混沌實驗。停止實驗只需刪除對應 CR 即可,在刪除 CR 時,首先會執行一遍實驗恢復邏輯,之後才會將 CR 刪除。但如果恢復實驗失敗,則會將 CR 的 Phase
設置為 Destroying
,而在 Reconcile
中觀測到 Phase
狀態為 Destroying
或者 metadata.deletionTimestamp
不為空時,就會不會移除 finalizers
中的攔截器名稱,阻止該 CR 被刪除。
這樣設計的目的是為了在實驗恢復失敗後,讓用戶去主動查看實驗恢復失敗原因,如果是一些意外原因導致的實驗恢復失敗,及時去處理。在確認原因後,可使用 CLI 工具增加 --force-remove
進去強制刪除,項目維護者在 Issue#368 中也就這個設計給出瞭解答。
// pkg/controller/chaosblade/controller.go 部分源碼
...
const chaosbladeFinalizer = "finalizer.chaosblade.io"
...
func (r *ReconcileChaosBlade) Reconcile(request reconcile.Request) (reconcile.Result, error) {
reqLogger := logrus.WithField("Request.Name", request.Name)
forget := reconcile.Result{}
// Fetch the RC instance
cb := &v1alpha1.ChaosBlade{}
err := r.client.Get(context.TODO(), request.NamespacedName, cb)
if err != nil {
return forget, nil
}
if len(cb.Spec.Experiments) == 0 {
return forget, nil
}
// Destroyed->delete
// Remove the Finalizer if the CR object status is destroyed to delete it
if cb.Status.Phase == v1alpha1.ClusterPhaseDestroyed {
cb.SetFinalizers(remove(cb.GetFinalizers(), chaosbladeFinalizer))
err := r.client.Update(context.TODO(), cb)
if err != nil {
reqLogger.WithError(err).Errorln("remove chaosblade finalizer failed at destroyed phase")
}
return forget, nil
}
if cb.Status.Phase == v1alpha1.ClusterPhaseDestroying || cb.GetDeletionTimestamp() != nil {
err := r.finalizeChaosBlade(reqLogger, cb)
if err != nil {
reqLogger.WithError(err).Errorln("finalize chaosblade failed at destroying phase")
}
return forget, nil
}
...
return forget, nil
}
如果 Phase
狀態為 Destroyed
,則從 Finalizers 中移除 finalizer.chaosblade.io
,之後正常刪除 CR。
結語
在實際工作中,像 Finalizers 這樣的東西太多了,很多平時掛在嘴邊的東西,深究起來我們可能對其並不瞭解,甚至原本的理解就是錯誤的。在今後的文章中,除了各種實踐乾貨,筆者還會將更多的精力投注到基本原理、底層實現、源碼剖析中,更聚焦於技術本身,在不重複造輪子的基礎上,學習和了解更多產品背後的代碼設計和實現原理。最後在分享一句弗蘭西斯·培根的話:
“人生如同道路。最近的捷徑通常是最壞的路。”