У меня есть контроллер k8s, который должен установить некоторые ресурсы и соответствующим образом обновить статус и состояние.
Поток согласования выглядит следующим образом:
checkAvailability и соответственно обновите статус, если готов/ожидает установки/ошибкаУ меня два основных вопроса:
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
}


На ваши вопросы:
Это первый раз, когда я использую статус и условия, это правильно или я что-то упускаю?
Ваш подход к управлению состоянием и состоянием ресурсов Kubernetes в целом неплох. Подресурс статуса в объекте API Kubernetes обычно используется для представления текущего состояния системы и может включать условия.
Условие — это набор полей, описывающих состояние объекта более подробно, чем просто true или false. Каждое условие обычно имеет type, status, reason, message и lastTransitionTime. Ваш код правильно устанавливает эти поля в зависимости от того, готов ли пользовательский ресурс vfm или нет.
Следует отметить, что условия должны быть выровнены, то есть они должны быть установлены на их текущее наблюдаемое значение независимо от их предыдущего значения. Они также должны быть установлены (true, false или unknown) для всех значимых или значимых для пользователя аспектов текущего состояния компонента. Это делает условия хорошим механизмом для обозначения «переходных состояний», таких как Progressing или Degraded, которые, как ожидается, изменятся со временем или в зависимости от внешнего состояния.
Иногда, когда я делаю обновление 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 oldCondition != nil && condition.Status == oldCondition.Status && condition.Reason == oldCondition.Reason && condition.Message == oldCondition.Message {
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
}
В этой обновленной версии:
Поле LastTransitionTime обновляется только при изменении статуса условия. Это гарантирует, что LastTransitionTime точно отражает время последнего изменения статуса, а не время последнего запуска функции checkHealth. Это должно обеспечить более точную временную шкалу фактического изменения состояния ресурса, а не время запуска цикла согласования.
Добавлен механизм повторных попыток с использованием retry.RetryOnConflict для повторной попытки обновления статуса при возникновении конфликтной ошибки. Обратите внимание, что для этого вам потребуется импортировать пакет "k8s.io/client-go/util/retry".
Это распространенный шаблон для работы с ошибкой Operation cannot be fulfilled....
Эти изменения должны помочь решить проблемы, с которыми вы сталкивались при обновлении статуса и условий ваших ресурсов Kubernetes.
Помните, что иногда вы все равно можете получать конфликтные ошибки, особенно если другие клиенты обновляют тот же объект. В этих случаях функция RetryOnConflict повторит попытку обновления с использованием последней версии объекта.
@PJEM Я вижу: ошибка связана с несоответствием типов. Функция conditionChanged ожидает указатель на фрагмент metav1.Condition в качестве второго аргумента, но я передавал указатель на один metav1.Condition (т. е. oldCondition). Я обновил ответ, напрямую сравнивая oldCondition и condition в функции checkHealth без необходимости вызывать conditionChanged.
Большое спасибо, теперь все работает. Я отлаживаю код и проблема в том, что когда статус и состояние wasn't changed (например, состояние готовности и все еще готово) код входит в retry и r.Status().Update(ctx... весь смысл в том, чтобы избежать этого, если статус условия не был изменен, не это ?
спасибо вам 1+, когда я пробую ваш код, я получаю ошибку
Cannot use 'oldCondition' (type *metav1.Condition) as the type *[]v1.Conditionвы знаете, возможно, как мне это сделать?