kubernetes K8s控制器更新状态和条件

qxsslcnc  于 2023-06-05  发布在  Kubernetes
关注(0)|答案(1)|浏览(340)

bounty将在6天内到期。回答此问题可获得+100声望奖励。PJEM正在寻找一个从一个有信誉的来源的答案:我需要一个方法来做它根据最佳实践

我有k8s控制器,需要安装一些资源,并相应地更新状态和条件
协调中的流程如下所示:
1.安装资源,不要等待
1.调用函数checkAvailability,如果就绪/待安装/错误,则相应地更新状态
我有两个主要问题:
1.这是我第一次使用状态和条件,这是正确的方法还是我错过了什么
1.有时当我更新r.Status().Update时,我会得到错误:Operation cannot be fulfilled on eds.core.vtw.bmw.com “resouce01”: the object has been modified; please apply your changes to the latest version and try again , so I’ve added the check conditionChanged`这解决了问题,但不确定其是否正确,因为我更新了状态一次,如果它没有吟唱,我就不碰它,这样用户就可以看到一段时间前的状态就绪,并且协调不会更新就绪条件的日期和时间,因为它在已经“就绪”时跳过它
我使用以下

func (r *ebdReconciler) checkHealth(ctx context.Context, req ctrl.Request, ebd ebdmanv1alpha1.ebd) (bool, error) {
    vfmReady, err := r.mbr.IsReady(ctx, req.Name, req.Namespace)
    condition := metav1.Condition{
        Type:               ebdmanv1alpha1.KubernetesvfmHealthy,
        Observebdneration: ebd.Generation,
        LastTransitionTime: metav1.Now(),
    }
    if err != nil {
        // There was an error checking readiness - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ReasonError
        condition.Message = fmt.Sprintf("Failed to check  vfm readiness: %v", err)
    } else if vfmReady {
        // The vfm is ready - Set status to true
        condition.Status = metav1.ConditionTrue
        condition.Reason = ebdmanv1alpha1.ReasonReady
        condition.Message = "vfm custom resource is ready"
    } else {
        // The vfm is not ready - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ResourceProgressing
        condition.Message = "vfm custom resource is not ready"
    }
    // Get the latest version of the ebd
    latestebd := ebdmanv1alpha1.ebd{}
    if err := r.Get(ctx, req.NamespacedName, &latestebd); err != nil {
        return vfmReady, err
    }

    oldConditions := latestebd.Status.Conditions
    meta.SetStatusCondition(&latestebd.Status.Conditions, condition)

    if !conditionChanged(&oldConditions, &latestebd.Status.Conditions, ebdmanv1alpha1.KubernetesvfmHealthy) {
        return vfmReady, nil
    }

    if err := r.Status().Update(ctx, &latestebd); err != nil {
        r.Log.Error(err, "failed to update vfm status")
        return vfmReady, err
    }
    return vfmReady, nil
}

func conditionChanged(oldConditions, newConditions *[]metav1.Condition, conditionType string) bool {
    newCondition := meta.FindStatusCondition(*newConditions, conditionType)
    oldCondition := meta.FindStatusCondition(*oldConditions, conditionType)
    if oldCondition == nil && newCondition == nil {
        return false
    }
    if oldCondition == nil || newCondition == nil {
        return true
    }
    return oldCondition.Status != newCondition.Status || oldCondition.Reason != newCondition.Reason || oldCondition.Message != newCondition.Message
}
suzh9iv8

suzh9iv81#

回答您的问题:
1.这是我第一次使用状态和条件,这是正确的方式还是我错过了什么?
您管理Kubernetes资源的状态和条件的方法通常是好的。Kubernetes API对象中的status subresource通常用于表示系统的当前状态,它可以包含条件。
条件是字段的集合,它们以比truefalse更详细的方式描述对象的状态。每个条件通常有typestatusreasonmessagelastTransitionTime。您的代码根据vfm自定义资源是否就绪来正确设置这些字段。
值得注意的是,条件应该是水平的-这意味着它们应该被设置为它们的当前观察值,而不管它们的先前值如何。还应该为组件当前状态的所有重要或用户有意义的方面设置它们(truefalseunknown)。这使得条件成为一种很好的机制来指示“ transient ”,如ProgressingDegraded,这些 transient 可能会随着时间的推移或基于外部状态而改变。
1.有时当我更新r.Status().Update时,我会得到错误:Operation cannot be fulfilled on eds.core.vtw.bmw.com “resource01”: the object has been modified; please apply your changes to the latest version and try again .
发生此错误的原因是,在控制器处理同一对象时,另一个客户端更新了该对象。这可能是另一个控制器,甚至是同一个控制器的另一个示例(如果您运行多个控制器)。
处理此问题的一种可能方法是使用重试机制,在发生此错误时重新尝试状态更新。在本例中,您实现了conditionChanged检查,以便仅在条件发生更改时尝试状态更新。这是避免不必要的更新的好方法,但它不能完全防止错误,因为另一个客户端仍然可以在您的Get调用和Status().Update调用之间更新对象。
您还可以考虑使用Patch而不是Update来修改状态,这可以降低与其他更新冲突的风险。打补丁允许对对象进行部分更新,因此您不太可能遇到冲突。
关于时间问题,您可以考虑仅在状态实际发生变化时更新LastTransitionTime,而不是每次完成健康检查时都更新。这意味着LastTransitionTime反映的是状态上次更改的时间,而不是上次执行检查的时间。
需要记住的一点是,频繁更新状态子资源,即使状态没有改变,也会导致不必要的API服务器负载。您应该努力仅在状态发生更改时才更新状态。
考虑到这些点,checkHealth函数的可能更新版本可能是:

func (r *ebdReconciler) checkHealth(ctx context.Context, req ctrl.Request, ebd ebdmanv1alpha1.ebd) (bool, error) {
    vfmReady, err := r.mbr.IsReady(ctx, req.Name, req.Namespace)
    condition := metav1.Condition{
        Type:   ebdmanv1alpha1.KubernetesvfmHealthy,
        Status: metav1.ConditionUnknown, // start with unknown status
    }

    latestebd := ebdmanv1alpha1.ebd{}
    if err := r.Get(ctx, req.NamespacedName, &latestebd); err != nil {
        return vfmReady, err
    }
    oldCondition := meta.FindStatusCondition(latestebd.Status.Conditions, ebdmanv1alpha1.KubernetesvfmHealthy)

    if err != nil {
        // There was an error checking readiness - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ReasonError
        condition.Message = fmt.Sprintf("Failed to check  vfm readiness: %v", err)
    } else if vfmReady {
        // The vfm is ready - Set status to true
        condition.Status = metav1.ConditionTrue
        condition.Reason = ebdmanv1alpha1.ReasonReady
        condition.Message = "vfm custom resource is ready"
    } else {
        // The vfm is not ready - Set status to false
        condition.Status = metav1.ConditionFalse
        condition.Reason = ebdmanv1alpha1.ResourceProgressing
        condition.Message = "vfm custom resource is not ready"
    }

    // Only update the LastTransitionTime if the status has changed
    if oldCondition == nil || oldCondition.Status != condition.Status {
        condition.LastTransitionTime = metav1.Now()
    } else {
        condition.LastTransitionTime = oldCondition.LastTransitionTime
    }

    meta.SetStatusCondition(&latestebd.Status.Conditions, condition)

    if !conditionChanged(&latestebd.Status.Conditions, oldCondition, ebdmanv1alpha1.KubernetesvfmHealthy) {
        return vfmReady, nil
    }

    // Retry on conflict
    retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
        // Retrieve the latest version of ebd before attempting update
        // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver
        if getErr := r.Get(ctx, req.NamespacedName, &latestebd); getErr != nil {
            return getErr
        }
        if updateErr := r.Status().Update(ctx, &latestebd); updateErr != nil {
            return updateErr
        }
        return nil
    })

    if retryErr != nil {
        r.Log.Error(retryErr, "Failed to update vfm status after retries")
        return vfmReady, retryErr
    }

    return vfmReady, nil
}

func conditionChanged(oldConditions, newConditions *[]metav1.Condition, conditionType string) bool {
    newCondition := meta.FindStatusCondition(*newConditions, conditionType)
    oldCondition := meta.FindStatusCondition(*oldConditions, conditionType)
    if oldCondition == nil && newCondition == nil {
        return false
    }
    if oldCondition == nil || newCondition == nil {
        return true
    }
    return oldCondition.Status != newCondition.Status || oldCondition.Reason != newCondition.Reason || oldCondition.Message != newCondition.Message
}

在此更新版本中:

  • LastTransitionTime字段仅在条件的状态更改时更新。这将确保LastTransitionTime准确地反映状态上次更改的时间,而不是checkHealth函数上次运行的时间。这将提供一个更准确的时间轴,说明资源的状态实际发生更改的时间,而不是协调循环运行的时间。
  • 使用retry.RetryOnConflict添加了重试机制,以便在发生冲突错误时重新尝试状态更新。请注意,您需要为此导入" k8s.io/client-go/util/retry " package

这是处理Operation cannot be fulfilled...错误的常见模式。
这些更改应该有助于解决您在更新Kubernetes资源的状态和条件时所面临的问题。
请记住,您可能偶尔仍会遇到冲突错误,特别是当有其他客户端更新同一对象时。在这些情况下,RetryOnConflict函数将使用对象的最新版本重试更新。

相关问题