Kubernetes 實戰-Operator Finalizers 實現


原文鏈接:https://zdyxry.github.io/2019/09/13/Kubernetes-%E5%AE%9E%E6%88%98-Operator-Finalizers/

Finalizers

Finalizers 允許 Operator 控制器實現異步的 pre-delete hook。比如你給 API 類型中的每個對象都創建了對應的外部資源,你希望在 k8s 刪除對應資源時同時刪除關聯的外部資源,那么可以通過 Finalizers 來實現。

Finalizers 是由字符串組成的列表,當 Finalizers 字段存在時,相關資源不允許被強制刪除。存在 Finalizers 字段的的資源對象接收的第一個刪除請求設置 metadata.deletionTimestamp 字段的值, 但不刪除具體資源,在該字段設置后, finalizer 列表中的對象只能被刪除,不能做其他操作。

當 metadata.deletionTimestamp 字段非空時,controller watch 對象並執行對應 finalizers 的動作,當所有動作執行完后,需要清空 finalizers ,之后 k8s 會刪除真正想要刪除的資源。

Operator finalizers 使用

介紹了 Finalizers 概念,那么我們來看看在 Operator 中如何使用,在 Operator Controller 中,最重要的邏輯就是 Reconcile 方法,finalizers 也是在 Reconcile 中實現的。要注意的是,設置了 Finalizers 會導致 k8s 的 delete 動作轉為設置 metadata.deletionTimestamp 字段,如果你通過 kubectl get 命令看到資源存在這個字段,則表示資源正在刪除(deleting)。

有以下幾點需要理解:

  1. 如果資源對象未被刪除且未設置 finalizers,則添加 finalizer並更新 k8s 資源對象;
  2. 如果正在刪除資源對象並且 finalizers 仍然存在於 finalizers 列表中,則執行 pre-delete hook並刪除 finalizers ,更新資源對象;
  3. 由於以上兩點,需要確保 pre-delete hook是冪等的。

kuberbuilder 示例

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
}

cluster-api-provider-vsphere 實現

看完了示例,我們來招一個具體項目看看,cluster-api-provider-vsphere 是 cluster-api 相關項目,用於提供 vsphere 相關資源創建的 Operator,采用 kubebuilder 來實現的。

vspheremachine_controller.go 中實現了 Reconcile 方法:

// Reconcile ensures the back-end state reflects the Kubernetes resource state intent.
func (r *VSphereMachineReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, reterr error) {
    ...
    // Always close the context when exiting this function so we can persist any VSphereMachine changes.
    defer func() {
        if err := machineContext.Patch(); err != nil && reterr == nil {
            reterr = err
        }
    }()

    // Handle deleted machines
    if !vsphereMachine.ObjectMeta.DeletionTimestamp.IsZero() {
        return r.reconcileDelete(machineContext)
    }

    // Handle non-deleted machines
    return r.reconcileNormal(machineContext)
}

在 Reconcile 中檢測了 DeletionTimestamp 是否為0 ,如果不為0 ,則表示資源處於正在刪除中,那么來看下 reconcileDelete 實現:

func (r *VSphereMachineReconciler) reconcileDelete(ctx *context.MachineContext) (reconcile.Result, error) {
    ctx.Logger.Info("Handling deleted VSphereMachine")

    var vmService services.VirtualMachineService = &govmomi.VMService{}

    // 執行刪除虛擬機邏輯
    vm, err := vmService.DestroyVM(ctx)
    if err != nil {
        // 如果刪除失敗,則直接返回錯誤,controller 會自動重試
        return reconcile.Result{}, errors.Wrapf(err, "failed to destroy VM")
    }

    // 重新調度刪除虛擬機邏輯,直到虛擬機狀態處於 notfound 狀態
    if vm.State != infrav1.VirtualMachineStateNotFound {
        ctx.Logger.V(6).Info("requeuing operation until vm state is reconciled", "expected-vm-state", infrav1.VirtualMachineStateNotFound, "actual-vm-state", vm.State)
        return reconcile.Result{RequeueAfter: config.DefaultRequeue}, nil
    }

    // pre-delete hook執行成功,也就是上面的刪除虛擬機邏輯執行成功,則清空 Finalizers
    ctx.VSphereMachine.Finalizers = clusterutilv1.Filter(ctx.VSphereMachine.Finalizers, infrav1.MachineFinalizer)

    return reconcile.Result{}, nil
}

可以看到整體邏輯與示例的使用是一致的,主要通過這種方式來達到 pre-delete hook 的效果。

k8s-initializer-finalizer-practice

在搜索相關資料的時候,看到有人在 SO 上問了如何使用的問題,其中有個回答中附上了一個練習項目,項目很小,很適合了解 Finalizers 概念。

相關邏輯如下:

}else{
    customdeployment:=obj.(*crdv1alpha1.CustomDeployment).DeepCopy()
    fmt.Println("Event..............................")
    if customdeployment.DeletionTimestamp != nil{
        // check if it has finalizer
        if customdeployment.GetFinalizers()!=nil{
            finalizers:=customdeployment.GetFinalizers()

            // check if first finalizer match with deletepod.crd.emruz.com
            if finalizers[0]=="deletepods.crd.emruz.com"{
                //
                _,err:=myutil.PatchCustomDeployment(c.clientset,customdeployment, func(deployment *crdv1alpha1.CustomDeployment) *crdv1alpha1.CustomDeployment {
                    // delete pods under this deployment
                    err:=myutil.DeletePods(c.kubeclient,c.podLabel)
                    if err!=nil{
                        fmt.Println("Failed to remove all pods. Reason: ",err)
                        return nil
                    }
                    // pods sucessfully removed. remove the finalizer
                    customdeployment.ObjectMeta=myutil.RemoveFinalizer(customdeployment.ObjectMeta)
                    return customdeployment
                })
                if err!=nil{
                    return err
                }
            }
           }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM