-
Notifications
You must be signed in to change notification settings - Fork 820
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
base: master
Are you sure you want to change the base?
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: 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 |
Codecov ReportAttention: Patch coverage is
❗ 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Hi @veophi can you help fix the lint error? |
/assign @yike21 |
f380479
to
cf79f5b
Compare
@XiShanYongYe-Chang fixed. |
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
and GitHub will automatically associate it with the issue. After the pr is merged, the associated issue will be closed. |
pkg/controllers/binding/common.go
Outdated
@@ -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()))) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
cf79f5b
to
fa2364f
Compare
There was a problem hiding this 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
@@ -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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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'.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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:
Status: statusRaw, |
So, can we avoid unnecessary updates to work by making the determination directly before starting the state collection?
karmada/pkg/controllers/status/work_status_controller.go
Lines 254 to 255 in 4105790
klog.Infof("reflecting %s(%s/%s) status to Work(%s/%s)", observedObj.GetKind(), observedObj.GetNamespace(), observedObj.GetName(), workNamespace, workName) | |
return c.reflectStatus(workObject, observedObj) |
There was a problem hiding this comment.
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:
Status: statusRaw, So, can we avoid unnecessary updates to work by making the determination directly before starting the state collection?
karmada/pkg/controllers/status/work_status_controller.go
Lines 254 to 255 in 4105790
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
fa2364f
to
68e7d2f
Compare
New changes are detected. LGTM label has been removed. |
c3eed4d
to
e4fff06
Compare
@@ -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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: iff -> if
There was a problem hiding this 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. |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
7230b2d
to
87be253
Compare
Signed-off-by: veophi <vec.g.sun@gmail.com>
87be253
to
62b976f
Compare
if observedGeneration < observedObj.GetGeneration() { | ||
return false | ||
} | ||
} |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
We assume that the reconciliation of
member1
will take some time. Before the deployment ofmember1
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?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
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? |
By the way, I updated the PR description(Removed |
/assign |
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?: