Skip to content

Added hpa update predicate to reconcile a scaledobject only if hpa spec,label or annotation is changed#5282

Merged
zroubalik merged 11 commits into
kedacore:mainfrom
deefreak:redundant-scaledobject-reconciles-by-HPA-status-updates
Dec 15, 2023
Merged

Added hpa update predicate to reconcile a scaledobject only if hpa spec,label or annotation is changed#5282
zroubalik merged 11 commits into
kedacore:mainfrom
deefreak:redundant-scaledobject-reconciles-by-HPA-status-updates

Conversation

@deefreak

Copy link
Copy Markdown
Contributor

This change fixes #5281 .
We have verified that scaledobject reconcilation is triggered only when hpa spec/label changes or hpa is deleted.
I raise this PR to get early feedback and automated tests will be added soon.

@deefreak deefreak requested a review from a team as a code owner December 12, 2023 12:09
@github-actions

Copy link
Copy Markdown

Thank you for your contribution! 🙏 We will review your PR as soon as possible.

While you are waiting, make sure to:

Learn more about:

@zroubalik zroubalik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add check for annotations here as well:

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

Comment thread controllers/keda/scaledobject_controller.go Outdated
Comment thread controllers/keda/util/predicate.go Outdated
Comment thread controllers/keda/util/predicate.go Outdated
@deefreak

deefreak commented Dec 13, 2023

Copy link
Copy Markdown
Contributor Author

We should probably add check for annotations here as well:

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

I am thinking that as scaledobject controller is not adding any new annotation to the HPA and HPA controller might add some annotation to it, so it should be fine to remove predicate.AnnotationChangedPredicate{} and leave this piece of code as it is.

We are now on autoscaling/v2 where currently no annotation is being added by hpa controller but i saw in autoscaling/v1 that the current metrics were part of the annotations and they kept changing.
So, it would be better to not use this predicate as there may be changes in the future in HPA controller updating annotations.

…ec,label or ownerRefernce is changed

Signed-off-by: deefreak <[email protected]>
…ec,label or annotation is changed

Signed-off-by: deefreak <[email protected]>
…ec,label or annotation is changed

Signed-off-by: deefreak <[email protected]>
@deefreak deefreak force-pushed the redundant-scaledobject-reconciles-by-HPA-status-updates branch from c1011c6 to 00193f8 Compare December 13, 2023 08:28
@JorTurFer

JorTurFer commented Dec 13, 2023

Copy link
Copy Markdown
Member

We should probably add check for annotations here as well:

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

I am thinking that as scaledobject controller is not adding any new annotation to the HPA and HPA controller might add some annotation to it, so it should be fine to remove predicate.AnnotationChangedPredicate{} and leave this piece of code as it is.

We are now on autoscaling/v2 where currently no annotation is being added by hpa controller but i saw in autoscaling/v1 that the current metrics were part of the annotations and they kept changing. So, it would be better to not use this predicate as there may be changes in the future in HPA controller updating annotations.

We are propagating the ScaledObject labels/annotations so I'd say that we have to keep the predicate:

ObjectMeta: metav1.ObjectMeta{
Name: getHPAName(scaledObject),
Namespace: scaledObject.Namespace,
Labels: labels,
Annotations: scaledObject.Annotations,
},

@deefreak

Copy link
Copy Markdown
Contributor Author

We should probably add check for annotations here as well:

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

I am thinking that as scaledobject controller is not adding any new annotation to the HPA and HPA controller might add some annotation to it, so it should be fine to remove predicate.AnnotationChangedPredicate{} and leave this piece of code as it is.
We are now on autoscaling/v2 where currently no annotation is being added by hpa controller but i saw in autoscaling/v1 that the current metrics were part of the annotations and they kept changing. So, it would be better to not use this predicate as there may be changes in the future in HPA controller updating annotations.

We are propagating the ScaledObject labels/annotations so I'd say that we have to keep the predicate:

ObjectMeta: metav1.ObjectMeta{
Name: getHPAName(scaledObject),
Namespace: scaledObject.Namespace,
Labels: labels,
Annotations: scaledObject.Annotations,
},

Sorry, my bad , missed this. Also, confirmed that currently hpa controller is not adding any annotations to the hpa object. So, it should be fine to add the annotation changed predicate . I will also then make changes to the

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

to add check for annotations as well.
I hope this is fine @JorTurFer

@zroubalik

Copy link
Copy Markdown
Member

We should probably add check for annotations here as well:

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

I am thinking that as scaledobject controller is not adding any new annotation to the HPA and HPA controller might add some annotation to it, so it should be fine to remove predicate.AnnotationChangedPredicate{} and leave this piece of code as it is.
We are now on autoscaling/v2 where currently no annotation is being added by hpa controller but i saw in autoscaling/v1 that the current metrics were part of the annotations and they kept changing. So, it would be better to not use this predicate as there may be changes in the future in HPA controller updating annotations.

We are propagating the ScaledObject labels/annotations so I'd say that we have to keep the predicate:

ObjectMeta: metav1.ObjectMeta{
Name: getHPAName(scaledObject),
Namespace: scaledObject.Namespace,
Labels: labels,
Annotations: scaledObject.Annotations,
},

Sorry, my bad , missed this. Also, confirmed that currently hpa controller is not adding any annotations to the hpa object. So, it should be fine to add the annotation changed predicate . I will also then make changes to the

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

to add check for annotations as well. I hope this is fine @JorTurFer

yes please

…ec,label or annotation is changed

Signed-off-by: deefreak <[email protected]>
@deefreak

deefreak commented Dec 13, 2023

Copy link
Copy Markdown
Contributor Author

@zroubalik I have done all the commented changes. Have added unit tests to test the HPASpecChangedPredicate as well.

@zroubalik zroubalik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, I wonder whether we can test that updates to status don't trigger a reconcile loop?

Comment thread controllers/keda/scaledobject_controller_test.go
@zroubalik

zroubalik commented Dec 13, 2023

Copy link
Copy Markdown
Member

/run-e2e
Update: You can check the progress here

@deefreak

Copy link
Copy Markdown
Contributor Author

Looking good, I wonder whether we can test that updates to status don't trigger a reconcile loop?

As there will be no change in scaledobjects, it will be really hard to test through a unit test. I mean there will be nothing to compare for before and after reconcile. Although i have compared it on a cluster with more than 100+ scaledobjects and there were no reconcilations for hpa status updates(.status.currentMetrics) after applying this fix.

@zroubalik

Copy link
Copy Markdown
Member

Looking good, I wonder whether we can test that updates to status don't trigger a reconcile loop?

As there will be no change in scaledobjects, it will be really hard to test through a unit test. I mean there will be nothing to compare for before and after reconcile. Although i have compared it on a cluster with more than 100+ scaledobjects and there were no reconcilations for hpa status updates(.status.currentMetrics) after applying this fix.

yeah, agree

@deefreak

deefreak commented Dec 13, 2023

Copy link
Copy Markdown
Contributor Author

We should probably add check for annotations here as well:

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

I am thinking that as scaledobject controller is not adding any new annotation to the HPA and HPA controller might add some annotation to it, so it should be fine to remove predicate.AnnotationChangedPredicate{} and leave this piece of code as it is.
We are now on autoscaling/v2 where currently no annotation is being added by hpa controller but i saw in autoscaling/v1 that the current metrics were part of the annotations and they kept changing. So, it would be better to not use this predicate as there may be changes in the future in HPA controller updating annotations.

We are propagating the ScaledObject labels/annotations so I'd say that we have to keep the predicate:

ObjectMeta: metav1.ObjectMeta{
Name: getHPAName(scaledObject),
Namespace: scaledObject.Namespace,
Labels: labels,
Annotations: scaledObject.Annotations,
},

Sorry, my bad , missed this. Also, confirmed that currently hpa controller is not adding any annotations to the hpa object. So, it should be fine to add the annotation changed predicate . I will also then make changes to the

func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error {
hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr)
if err != nil {
logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject))
return err
}
// DeepDerivative ignores extra entries in arrays which makes removing the last trigger not update things, so trigger and update any time the metrics count is different.
if len(hpa.Spec.Metrics) != len(foundHpa.Spec.Metrics) || !equality.Semantic.DeepDerivative(hpa.Spec, foundHpa.Spec) {
logger.V(1).Info("Found difference in the HPA spec accordint to ScaledObject", "currentHPA", foundHpa.Spec, "newHPA", hpa.Spec)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.Spec = hpa.Spec
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
if !equality.Semantic.DeepDerivative(hpa.ObjectMeta.Labels, foundHpa.ObjectMeta.Labels) {
logger.V(1).Info("Found difference in the HPA labels accordint to ScaledObject", "currentHPA", foundHpa.ObjectMeta.Labels, "newHPA", hpa.ObjectMeta.Labels)
if err = r.Client.Update(ctx, hpa); err != nil {
foundHpa.ObjectMeta.Labels = hpa.ObjectMeta.Labels
logger.Error(err, "Failed to update HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
return err
}
logger.Info("Updated HPA according to ScaledObject", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name)
}
return nil

to add check for annotations as well. I hope this is fine @JorTurFer

yes please

I added the check for annotations as similar to the labels.
Wrote a test for reconciliation of scaledobject when annotation is changed, noticed that equality.Semantic.DeepDerative(a1,a2 interface{}) returns true if a1 == nil .

when I was testing, the hpa created through scaledobject has no annotations, thus annotation was nil and therefore adding an annotation to the hpa was not reconciling again.

So, we should add this condition, verified that this is working
if (hpa.ObjectMeta.Annotations == nil && foundHpa.ObjectMeta.Annotations != nil) ||
!equality.Semantic.DeepDerivative(hpa.ObjectMeta.Annotations, foundHpa.ObjectMeta.Annotations)

@zroubalik @JorTurFer what do you think?

@zroubalik

Copy link
Copy Markdown
Member

Good catch, please do that!

…ec,label or annotation is changed

Signed-off-by: deefreak <[email protected]>
@deefreak

Copy link
Copy Markdown
Contributor Author

Good catch, please do that!

Done and added tests for label and annotation changes as well.

deefreak and others added 4 commits December 14, 2023 13:47
…ec,label or annotation is changed

Signed-off-by: deefreak <[email protected]>
…s-by-HPA-status-updates' into redundant-scaledobject-reconciles-by-HPA-status-updates
@zroubalik

zroubalik commented Dec 14, 2023

Copy link
Copy Markdown
Member

/run-e2e
Update: You can check the progress here

@deefreak

Copy link
Copy Markdown
Contributor Author

@zroubalik I see this run-e2e is failing . Any idea how to fix this?

@JorTurFer

JorTurFer commented Dec 15, 2023

Copy link
Copy Markdown
Member

/run-e2e
Update: You can check the progress here

@JorTurFer

Copy link
Copy Markdown
Member

@zroubalik I see this run-e2e is failing . Any idea how to fix this?

I think that it was an unrelated issue

@deefreak

Copy link
Copy Markdown
Contributor Author

Given checks have passed, can it be approved now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ScaledObject reconciliation delay due to redundant reconciles triggered by HPA status updates

3 participants