Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Deployment generation/observedGeneration bug #4867

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

veophi
Copy link

@veophi veophi commented Apr 24, 2024

What type of PR is this?

What this PR does / why we need it:

Which issue(s) this PR fixes:
Part of #4870

Special notes for your reviewer:

Does this PR introduce a user-facing change?:


@karmada-bot karmada-bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 24, 2024
@karmada-bot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
To complete the pull request process, please assign kevin-wangzefeng after the PR has been reviewed.
You can assign the PR to them by writing /assign @kevin-wangzefeng in a comment when ready.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@karmada-bot karmada-bot added the size/S Denotes a PR that changes 10-29 lines, ignoring generated files. label Apr 24, 2024
@codecov-commenter
Copy link

codecov-commenter commented Apr 24, 2024

Codecov Report

Attention: Patch coverage is 24.00000% with 19 lines in your changes are missing coverage. Please review.

Project coverage is 53.04%. Comparing base (6e5a602) to head (62b976f).
Report is 16 commits behind head on master.

Files Patch % Lines
pkg/controllers/status/work_status_controller.go 0.00% 9 Missing ⚠️
...esourceinterpreter/default/native/reflectstatus.go 0.00% 8 Missing ⚠️
...ourceinterpreter/default/native/aggregatestatus.go 71.42% 1 Missing and 1 partial ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4867      +/-   ##
==========================================
+ Coverage   52.98%   53.04%   +0.05%     
==========================================
  Files         250      251       +1     
  Lines       20421    20411      -10     
==========================================
+ Hits        10820    10826       +6     
+ Misses       8881     8871      -10     
+ Partials      720      714       -6     
Flag Coverage Δ
unittests 53.04% <24.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@XiShanYongYe-Chang
Copy link
Member

Hi @veophi can you help fix the lint error?

@XiShanYongYe-Chang
Copy link
Member

/assign @yike21

@veophi veophi force-pushed the bugfix/deployment-generation branch 2 times, most recently from f380479 to cf79f5b Compare April 25, 2024 02:32
@karmada-bot karmada-bot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Apr 25, 2024
@veophi
Copy link
Author

veophi commented Apr 25, 2024

Hi @veophi can you help fix the lint error?

@XiShanYongYe-Chang fixed.

@XiShanYongYe-Chang
Copy link
Member

Hi @veophi, Is this pr ready? If it is ready, you can remove the WIP info in the title. Then the work-in-progress label will be removed, indicating that the current pr is ready.

In addition, you can add the issue number of the current pr to the end of Fiexs, like this:

**Which issue(s) this PR fixes:**
Fixes #4866

and GitHub will automatically associate it with the issue. After the pr is merged, the associated issue will be closed.

@veophi veophi changed the title [WIP]: fix Deployment generation/observedGeneration logic [buggix] fix Deployment generation/observedGeneration logic Apr 25, 2024
@karmada-bot karmada-bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 25, 2024
@veophi veophi changed the title [buggix] fix Deployment generation/observedGeneration logic [bugfix] fix Deployment generation/observedGeneration logic Apr 25, 2024
@veophi veophi changed the title [bugfix] fix Deployment generation/observedGeneration logic fix: Deployment generation/observedGeneration bug Apr 25, 2024
@@ -171,6 +173,7 @@ func mergeAnnotations(workload *unstructured.Unstructured, workNamespace string,
annotations := make(map[string]string)
util.MergeAnnotation(workload, workv1alpha2.WorkNameAnnotation, names.GenerateWorkName(workload.GetKind(), workload.GetName(), workload.GetNamespace()))
util.MergeAnnotation(workload, workv1alpha2.WorkNamespaceAnnotation, workNamespace)
util.MergeAnnotation(workload, workv1alpha2.KarmadaWorkloadGenerationAnnotationKey, strconv.Itoa(int(workload.GetGeneration())))
Copy link
Member

Choose a reason for hiding this comment

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

Does int64 to int conversion lose information on 32-bit systems?

Copy link
Author

Choose a reason for hiding this comment

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

Does int64 to int conversion lose information on 32-bit systems?

@XiShanYongYe-Chang fixed.

@veophi veophi force-pushed the bugfix/deployment-generation branch from cf79f5b to fa2364f Compare April 25, 2024 03:28
Copy link
Member

@yike21 yike21 left a comment

Choose a reason for hiding this comment

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

/lgtm
There are now stricter requirements for aligning the status.observedGeneration of a federated resource (deployment) with its metadata.generation.
Sometimes you may find that the status.observedGeneration of federated resource (Deployment) is smaller than its metadata.generation, meaning that some resource in member cluster have not yet been updated by deployment-controller, which is normal.
Thanks for you work! @veophi

@karmada-bot karmada-bot added the lgtm Indicates that a PR is ready to be merged. label Apr 25, 2024
@@ -58,6 +58,10 @@ const (
// - Manifest in Work object: describes the name of ClusterResourceBinding which the manifest derived from.
ClusterResourceBindingAnnotationKey = "clusterresourcebinding.karmada.io/name"

// KarmadaWorkloadGenerationAnnotationKey records the generation of karmada workload to member workload.
// This annotation is helpful to generate observedGeneration for karmada workload.
KarmadaWorkloadGenerationAnnotationKey = "workload.karmada.io/generation"
Copy link
Member

Choose a reason for hiding this comment

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

The resource wrapped in work is not necessarily a workload resource. It may be more appropriate to name it resourcetemplate.

Copy link
Author

Choose a reason for hiding this comment

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

The resource wrapped in work is not necessarily a workload resource. It may be more appropriate to name it resourcetemplate.

resourcetemplate.karmada.io/generation?

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Author

Choose a reason for hiding this comment

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

fixed

Copy link
Member

Choose a reason for hiding this comment

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

I have a question. Does this annotation record the resource template generation of the host cluster or the workload generation of the member cluster?

@@ -80,14 +81,23 @@ func aggregateDeploymentStatus(object *unstructured.Unstructured, aggregatedStat
// which is the generation Karmada 'observed'.
// The 'observedGeneration' is mainly used by GitOps tools(like 'Argo CD') to assess the health status.
// For more details, please refer to https://argo-cd.readthedocs.io/en/stable/operator-manual/health/.
newStatus.ObservedGeneration = deploy.Generation
Copy link
Member

Choose a reason for hiding this comment

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

This comment may no longer be appropriate:

// always set 'observedGeneration' with current generation(.metadata.generation)
// which is the generation Karmada 'observed'.

Copy link
Author

Choose a reason for hiding this comment

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

fixed

if deploymentStatus.ObservedGeneration < object.GetGeneration() {
klog.Errorf("%s(%s/%s) latest generation is not observed by its controller, current status is untrustworthy, ignore reflect status",
object.GetKind(), object.GetNamespace(), object.GetName())
return nil, nil
Copy link
Member

Choose a reason for hiding this comment

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

If nil is directly returned, the upper-layer logic sets the entire status to nil when processing status collection:

So, can we avoid unnecessary updates to work by making the determination directly before starting the state collection?

klog.Infof("reflecting %s(%s/%s) status to Work(%s/%s)", observedObj.GetKind(), observedObj.GetNamespace(), observedObj.GetName(), workNamespace, workName)
return c.reflectStatus(workObject, observedObj)

Copy link
Author

Choose a reason for hiding this comment

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

make sense

If nil is directly returned, the upper-layer logic sets the entire status to nil when processing status collection:

So, can we avoid unnecessary updates to work by making the determination directly before starting the state collection?

klog.Infof("reflecting %s(%s/%s) status to Work(%s/%s)", observedObj.GetKind(), observedObj.GetNamespace(), observedObj.GetName(), workNamespace, workName)
return c.reflectStatus(workObject, observedObj)

make sense.

Copy link
Author

Choose a reason for hiding this comment

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

fixed

@karmada-bot
Copy link
Collaborator

New changes are detected. LGTM label has been removed.

@XiShanYongYe-Chang
Copy link
Member

Part of #4870
/kind feature
Hi @veophi, can you help add the release note.

@karmada-bot karmada-bot added the kind/feature Categorizes issue or PR as related to a new feature. label Apr 25, 2024
@veophi veophi force-pushed the bugfix/deployment-generation branch 4 times, most recently from c3eed4d to e4fff06 Compare April 26, 2024 01:43
@@ -251,10 +256,27 @@ func (c *WorkStatusController) syncWorkStatus(key util.QueueKey) error {
// status changes.
}

if reason, ready := c.isReflectStatusReady(observedObj); !ready {
// Just return nil and no need to reflect the status, waiting for the next correct event.
klog.Infof("Skip %s/%s/%s reflect status, reason: %s", observedObj.GetKind(), observedObj.GetNamespace(), observedObj.GetName(), reason)
Copy link
Member

Choose a reason for hiding this comment

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

just a formatting problem: klog.Infof("Skip %s(%s/%s) reflect status, reason: %s", observedObj.GetKind(), observedObj.GetNamespace(), observedObj.GetName(), reason)

Copy link
Author

Choose a reason for hiding this comment

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

done

func (c *WorkStatusController) isReflectStatusReady(observedObj *unstructured.Unstructured) (string, bool) {
observedGeneration, ok, err := unstructured.NestedInt64(observedObj.Object, "status", "observedGeneration")
if err == nil && ok {
// We compare them iff `status.observedGeneration` exists.
Copy link
Member

Choose a reason for hiding this comment

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

typo: iff -> if

Copy link
Member

@XiShanYongYe-Chang XiShanYongYe-Chang left a comment

Choose a reason for hiding this comment

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

Thanks~

@@ -251,10 +256,27 @@ func (c *WorkStatusController) syncWorkStatus(key util.QueueKey) error {
// status changes.
}

if reason, ready := c.isReflectStatusReady(observedObj); !ready {
// Just return nil and no need to reflect the status, waiting for the next correct event.
Copy link
Member

Choose a reason for hiding this comment

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

How about this comment: When the generation in the resource does not reach a consistent state, skip reflect status and wait for the next update event.

klog.Infof("reflecting %s(%s/%s) status to Work(%s/%s)", observedObj.GetKind(), observedObj.GetNamespace(), observedObj.GetName(), workNamespace, workName)
return c.reflectStatus(workObject, observedObj)
}

func (c *WorkStatusController) isReflectStatusReady(observedObj *unstructured.Unstructured) (string, bool) {
Copy link
Member

Choose a reason for hiding this comment

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

Is it enough for the current scenario to just return a bool value?

Copy link
Author

@veophi veophi Apr 26, 2024

Choose a reason for hiding this comment

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

This is for extensibility considerations.

Copy link
Member

Choose a reason for hiding this comment

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

Currently, there is only one cause, and only log output processing is performed for the cause. No other processing logic is available. After the processing logic is available, we can design how to add a reason.

@@ -68,12 +69,17 @@ func reflectDeploymentStatus(object *unstructured.Unstructured) (*runtime.RawExt
return nil, fmt.Errorf("failed to convert DeploymentStatus from map[string]interface{}: %v", err)
}

resourceTemplateGenerationInt := int64(0)
resourceTemplateGenerationStr := util.GetAnnotationValue(object.GetAnnotations(), v1alpha2.ResourceTemplateGenerationAnnotationKey)
_ = runtime.Convert_string_To_int64(&resourceTemplateGenerationStr, &resourceTemplateGenerationInt, nil)
Copy link
Member

Choose a reason for hiding this comment

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

If the annotation value is deleted or set to an invalid value due to misoperations, will an error occur?

@veophi veophi force-pushed the bugfix/deployment-generation branch 2 times, most recently from 7230b2d to 87be253 Compare April 26, 2024 02:09
Signed-off-by: veophi <vec.g.sun@gmail.com>
@veophi veophi force-pushed the bugfix/deployment-generation branch from 87be253 to 62b976f Compare April 26, 2024 02:41
if observedGeneration < observedObj.GetGeneration() {
return false
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Do you wanna express that the deployment has reached a consistent state if .status.observedGeneration is absent?

grabStatus := appsv1.DeploymentStatus{
Replicas: deploymentStatus.Replicas,
UpdatedReplicas: deploymentStatus.UpdatedReplicas,
ReadyReplicas: deploymentStatus.ReadyReplicas,
AvailableReplicas: deploymentStatus.AvailableReplicas,
UnavailableReplicas: deploymentStatus.UnavailableReplicas,
ObservedGeneration: resourceTemplateGenerationInt,
Copy link
Member

Choose a reason for hiding this comment

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

I don't get why not use deploymentStatus.ObservedGeneration. This seems so confusing. According to the previous logic, when the function reflectDeploymentStatus is executed, status.observedGeneration is already equal to metadata.generation.

Copy link
Member

Choose a reason for hiding this comment

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

Users in the member cluster or the controller will modify resources. If deploymentStatus.ObservedGeneration in the member cluster is used, it cannot be consistent with generation in the resource template on the control plane.

Copy link
Member

Choose a reason for hiding this comment

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

I got you, but let's take a look at a specific example.
deploy-status

We assume that the reconciliation of member1 will take some time. Before the deployment of member1 reaches a consistent state, Is the deployment status of Karmada credible?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this will happen. I have talked to @veophi about this. Resources in this state will not be considered updated. After the controllers in the member clusters are processed, generation will be processed consistently, which means it may have some delay. But in my opinion, an extension is better than an advance. And, this should be a low probability event.

I don't know if I described it clearly. Let's see if the author has anything to add. @veophi

Copy link
Member

Choose a reason for hiding this comment

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

I have a question: there doesn't seem to be a way to reflect the actual .status.observedGeneration of deployment in member clusters. If status.ObservedGeneration changes in a member cluster, a judgment is made at the time of AggregationStatus: only status.ObservedGeneration greater than or equal to the .metadata.generation in the resource template are counted.

Copy link
Author

@veophi veophi Apr 26, 2024

Choose a reason for hiding this comment

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

I got you, but let's take a look at a specific example. deploy-status

We assume that the reconciliation of member1 will take some time. Before the deployment of member1 reaches a consistent state, Is the deployment status of Karmada credible?

@whitewindmills Karmada Deployment will keep the old status instead of aggregating inconsistent status of member1. Why do you think the deployment status of Karmada is credible?

Copy link
Member

Choose a reason for hiding this comment

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

I have a question: there doesn't seem to be a way to reflect the actual .status.observedGeneration of deployment in member clusters.

I think the value of .status.observedGeneration in the member cluster is not available for the control plane, because the resources of karmada control plane and member cluster, their changes may not be consistent.

will operation retention correct it? I find that retention just retain replicas for deployment.

You're right, deployment retention only deals with the replicas, the other fields will be overwritten by the control plane. The deployment controller in the member cluster processes the .status.observedGeneration field, and the final generation is consistent in the member cluster (unless an error occurs).

Copy link
Member

Choose a reason for hiding this comment

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

Why do you think the deployment status of Karmada is credible?

No, we do not have to guarantee it's trustworthy. What I mean is that I hope karmada can collect the state in real time instead of waiting until it reaches a consistent state.

Copy link
Member

Choose a reason for hiding this comment

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

What I mean is that I hope karmada can collect the state in real time instead of waiting until it reaches a consistent state.

My main objection is that this PR undermines this behavior.

Copy link
Member

@yike21 yike21 Apr 26, 2024

Choose a reason for hiding this comment

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

You're right, deployment retention only deals with the replicas, the other fields will be overwritten by the control plane. The deployment controller in the member cluster processes the .status.observedGeneration field, and the final generation is consistent in the member cluster (unless an error occurs).

Thanks, according to c.ObjectWatcher.NeedsUpdate, if users change .spec of deployment in some member cluster, code here will overwrite resource in member clusters.

No, we do not have to guarantee it's trustworthy. What I mean is that I hope karmada can collect the state in real time instead of waiting until it reaches a consistent state.

We can see that overwrite is ahead of reflectStatus. Users changed spec of deployment in member cluster, then karmada-control-plane will correct it. and then we get status of deployment in member cluster by reflectStatus.

Maybe just reflect .status.observedGeneration, because .metadata.generation will be overwritten finally.

@@ -251,10 +251,27 @@ func (c *WorkStatusController) syncWorkStatus(key util.QueueKey) error {
// status changes.
}

if !c.isConsistentGenerationState(observedObj) {
Copy link
Member

Choose a reason for hiding this comment

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

Obviously skipping state collection is not a better approach, even if the collected state is not reliable until a consistent state is reached, it is better than no state at all.
For users, it is obviously a more friendly way for Karmada to provide a continuous state change before reaching a consistent state.

Previous: 0 -> 1 -> 2 -> 3 -> 4
Now: 0 -> 4

Copy link
Author

Choose a reason for hiding this comment

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

1.Skiping reflect status doesn't mean do not aggregate statuses.

2.Keeping the old status is better than aggregating untrustworthy statuses.

Copy link
Member

Choose a reason for hiding this comment

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

1.Skiping reflect status doesn't mean do not aggregate statuses.

If status is not reflected, where does the aggregated data come from?

Keeping the old status is better than aggregating untrustworthy statuses.

it's not untrustworthy status but the real status. And the old status is also untrustworthy.

Copy link
Author

Choose a reason for hiding this comment

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

@whitewindmills Perhaps this check condition can be deleted, but we should keep status.observedGeneration < generation for Karmada deployment if its member status is inconsistent.

Copy link
Member

Choose a reason for hiding this comment

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

What about that reflect .status.observedGeneration to express the real status, and keep status.observedGeneration < generation condition by AggregateStatus operaton of deployment, not in thework_status_controller.go?

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @yike21
The work status controller just triggers status collection, but how status is collected is defined by resource interpreter. In addition, not all resources have the .status.observedGeneration, it's not appropriate to check it for all kinds of resources.

@XiShanYongYe-Chang
Copy link
Member

I see that there is a lot of discussion about this pr and the issues are quite in-depth. This means that the PR solution is very important to Karmada. Can we organize a meeting or discussion group to discuss this issue?

@veophi @whitewindmills @yike21

@RainbowMango
Copy link
Member

By the way, I updated the PR description(Removed Fixes indicator) as it tries to fix the #4866, but it just focuses on Deployment.

@XiShanYongYe-Chang
Copy link
Member

/assign

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature Categorizes issue or PR as related to a new feature. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants