前言
经常操作 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 删除对应资源 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/