TIPS之 Golang 和 Kubernetes finalizer使用方式

Golang 和 Kubernetes finalizer使用方式

Posted by 董江 on Wednesday, January 3, 2024

Golang 和 Kubernetes finalizer使用方式

Golang 垃圾回收 finalizer

Golang finalizer直白点理解,是Go语言中的析构函数. 可通过runtime.SetFinalizer对象objectfinalizer函数 进行绑定。

举个例子:

func main() {
    var i = 3
    // 这func 就是  finalizer 析构函数
    runtime.SetFinalizer(&i, func(i *int) {
        fmt.Println(i, *i, "set finalizer")
    })
    
    var k = 5
    i = k
    time.Sleep(time.Second * 5)
    
    // 解绑 finalizer 析构函数
    runtime.SetFinalizer(&i, nil)
}

从golang runtime 中可以得出: runtime SetFinalizer 说明

  1. runtime.SetFinalizer 是在一个runtime协程中处理的。如果全局设置的的finalizer过多 或者 过于复杂,会影响 GC 性能;
  2. runtime.SetFinalizer 是在对象被 GC 选中并被回收之前,SetFinalizer 都不会执行, 所以禁止在SetFinalizer中执行将内存中的内容flush到磁盘这种操作。
  3. runtime.SetFinalizer 最大的问题是延长了对象生命周期. 对于大量对象分配、删除等场景不建议设置;
  4. 指针构成的 “循环引⽤” 加上 runtime.SetFinalizer 会导致内存泄露;

使用场景

1. CGO场景下,对非golang开辟对象,进行析构传递

package main

// #include <stdio.h>
// typedef struct {
// char *msg;
// } myStruct;
// void myFunc(myStruct *strct) {
// printf("Hello %s!\n", strct->msg);
// }

import "C"
func main() {
	msg := C.myStruct{C.CString("world")}
	runtime.SetFinalizer(&msg, func(t *C.myStruct) {
		C.free(unsafe.Pointer(t.msg))
	})
	C.myFunc(msg.msg)
	runtime.KeepAlive(&msg)
}

由于垃圾回收时调用finalizer很有可能是在另一个线程中执行的, 但有些资源可能不是线程安全的, 需要确保绑定到同一个协程定时释放

2. 优雅关闭后台goroutine协程

通常显示调用方式:

func watch() {
	ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-stop:
            // do something
            return
        case <-ticker.C:
            // do something
        }
    }
}

func Close(i *interface) {
	if i != nil {
		close(i.object) //底层对象清理
	}
	// 设置对象为 nil
	i = nil
}

这种方式需要循环,并显示调用close方法;

可以通过 runtime.SetFinalizer 实现优雅关闭;

type Wrapper struct {
	obj 	*interface
	stop    chan struct{}
}

var i = 3
result := &Wrapper{&i}
runtime.SetFinalizer(result, func(w *Wrapper) {
	w.stop <- true
	w.evicterTerminated.Wait()
})

在基础库中SetFinalzer主要的使用场景是减少用户错误使用导致的资源泄露,比如 os.NewFile()net.FD() 都注册了 finalizer 来避免用户由于忘记调用 Close 导致的 fd leak.

Kubernetes finalizer机制, 理念和 golang finalizer一致

Kubernetes Finalizer 在Kubernetes资源删除过程中,异步删除/状态同步的一种控制机制

Finalizer 主要是实现多个资源对象之间可能会有依赖关系,随意删除一个对象可能会对依赖它的其他对象产生影响。 在删除其中一个之前,有一个钩子函数可执行相关资源删除关系;

使用场景

Kubernetes 的一些原生资源对象会自动被加上一些finalizers.

原生场景: PVC和PV

PVCPV分别原生自带 kubernetes.io/pvc-protectionkubernetes.io/pv-protectionfinalizers标签的。 顾名思义,其目的在于保护持久化存储不被误删,避免挂载了存储的工作负载产生问题

原生场景: namespace

Namespace也是自带一个kubernetes的finalizers标签的.

自定义Controller

kubebuilder Finalizers允许控制器实现异步预删除钩子. 为 API 类型的每个对象创建一个外部资源(例如存储桶),并且您希望在从 Kubernetes 删除对象时删除关联的外部资源,您可以使用终结器来执行此操作。

func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := r.Log.WithValues("cronjob", req.NamespacedName)

    cronJob := &batchv1.CronJob{}
    if err := r.Get(ctx, req.NamespacedName, cronJob); err != nil {
        log.Error(err, "unable to fetch CronJob")
        // we'll ignore not-found errors, since they can't be fixed by an immediate
        // requeue (we'll need to wait for a new notification), and we can get them
        // on deleted requests.
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // name of our custom finalizer
    myFinalizerName := "batch.tutorial.kubebuilder.io/finalizer"

    // examine DeletionTimestamp to determine if object is under deletion
    if cronJob.ObjectMeta.DeletionTimestamp.IsZero() {
        // The object is not being deleted, so if it does not have our finalizer,
        // then lets add the finalizer and update the object. This is equivalent
        // registering our finalizer.
        if !controllerutil.ContainsFinalizer(cronJob, myFinalizerName) {
            controllerutil.AddFinalizer(cronJob, myFinalizerName)
            if err := r.Update(ctx, cronJob); err != nil {
                return ctrl.Result{}, err
            }
        }
    } else {
        // The object is being deleted
        if controllerutil.ContainsFinalizer(cronJob, myFinalizerName) {
            // our finalizer is present, so lets handle any external dependency
            if err := r.deleteExternalResources(cronJob); err != nil {
                // if fail to delete the external dependency here, return with error
                // so that it can be retried
                return ctrl.Result{}, err
            }

            // remove our finalizer from the list and update it.
            controllerutil.RemoveFinalizer(cronJob, myFinalizerName)
            if err := r.Update(ctx, cronJob); err != nil {
                return ctrl.Result{}, err
            }
        }

        // Stop reconciliation as the item is being deleted
        return ctrl.Result{}, nil
    }

    // Your reconcile logic

    return ctrl.Result{}, nil
}

func (r *Reconciler) deleteExternalResources(cronJob *batch.CronJob) error {
    //
    // delete any external resources associated with the cronJob
    //
    // Ensure that delete implementation is idempotent and safe to invoke
    // multiple times for same object.
}

其他

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付