Skip to content

fix: consistent dry-run behavior between NodeController and RuleContr… - #422

Open
Saloni3494 wants to merge 1 commit into
kubernetes-sigs:mainfrom
Saloni3494:issue-54
Open

fix: consistent dry-run behavior between NodeController and RuleContr…#422
Saloni3494 wants to merge 1 commit into
kubernetes-sigs:mainfrom
Saloni3494:issue-54

Conversation

@Saloni3494

Copy link
Copy Markdown

What type of PR is this?

/kind bug

What this PR does / why we need it:
This PR fixes inconsistent Dry-Run evaluation behavior between the RuleController and NodeController.

Previously: When DryRun was set to true, NodeController would completely skip rule evaluation for any node updates (continue). This resulted in stale DryRunResults being reported in the Rule's status when a Node's conditions transitioned, as the results would only update when the Rule itself was modified.
Now: NodeController catches the dry-run rules, safely evaluates them against all applicable nodes (via processDryRun), and persists the refreshed DryRunResults summary to the NodeReadinessRule status, giving real-time visibility into what actions would occur without actually applying the taints.

An E2E test verifying this live-update behavior has also been added.

Which issue(s) this PR fixes:
Fixes #54

Special notes for your reviewer:
The update to the rule's status is done safely using RetryOnConflict within the NodeController loop.

Does this PR introduce a user-facing change?:

Fixed a bug where `DryRunResults` on `NodeReadinessRules` would not dynamically update in response to Node condition changes.

@netlify

netlify Bot commented Aug 18, 2026

Copy link
Copy Markdown

Deploy Preview for node-readiness-controller canceled.

Name Link
🔨 Latest commit 06c1732
🔍 Latest deploy log https://app.netlify.com/projects/node-readiness-controller/deploys/6a83cca92906210008eef656

@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Saloni3494
Once this PR has been reviewed and has the lgtm label, please assign sergeykanzhelev for approval. For more information see the Code Review Process.

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

Details 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

@kubernetes-prow kubernetes-prow Bot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Aug 18, 2026
@kubernetes-prow

Copy link
Copy Markdown

Hi @Saloni3494. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kubernetes-prow kubernetes-prow Bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Aug 18, 2026
@ajaysundark

Copy link
Copy Markdown
Contributor

Hi @Saloni3494, Thanks for your PR.

I see you have created multiple large PRs against the repo without the discussion context. If you are interested in working on this / other issues, I'd welcome you to bring this to our slack channel / biweekly sync to align on the directions first.

I also recommend you to review the kubernetes contribution policy guidance here: https://github.com/kubernetes/community/blob/main/contributors/guide/pull-requests.md#ai-guidance. We love to have you work with us, but it'd help us if you engage with us and work on these PRs than one-shotting them!

@ajaysundark
ajaysundark self-requested a review August 18, 2026 06:25
@Saloni3494

Copy link
Copy Markdown
Author

Thank you for the feedback, Ajay! I really appreciate you taking the time to review my PRs and share the contribution guidelines.

I understand your point about aligning on the direction before working on larger changes. I’ll review the Kubernetes contribution policy and will make sure to engage in the Slack channel/bisweekly syncs before taking up similar issues going forward.

I’d definitely like to continue contributing to this project and work more closely with the community. I’ll also revisit my current PRs based on the discussion and guidance from the maintainers.

Thanks again for pointing me in the right direction!

@yindia yindia left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Left a few comments. The bigger thing is direction.

There are two shapes in #54. This takes the minimal one, keep processDryRun separate and call it from the NodeReconciler. The other, which AvineshTripathi and farazmd each landed on independently, unifies them, let evaluateRuleForNode handle both modes, gate only the taint write on dryRun, and update stats by delta.

That second shape also gets you nodeEvaluations for dry-run rules, drops the duplicated aggregation logic, and is O(1) per node event instead of a full node scan.

Nothing was ever decided there though. Could you drop a note on #54 with which shape you'd like to take? Would rather agree that up front than have you rework this twice.

patch := client.MergeFrom(latestRule.DeepCopy())
latestRule.Status.DryRunResults = rule.Status.DryRunResults
latestRule.Status.ObservedGeneration = rule.Status.ObservedGeneration
return r.Status().Patch(ctx, latestRule, patch)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This fires on every node event regardless of whether DryRunResults actually changed, so a no-op merge patch still costs a full API round trip per node event per dry-run rule.

Could we compare against latestRule.Status.DryRunResults first and skip the patch when they're equal? We already do this kind of no-op-write suppression in the readiness condition reporter for the same scale reason (documented here https://node-readiness-controller.sigs.k8s.io/user-guide/concepts.html#optimizing-node-status-writes).

"node", node.Name, "rule", rule.Name)

nodeList := &corev1.NodeList{}
if err := r.List(ctx, nodeList); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This List runs inside the per-rule loop, so N matching dry-run rules means N cluster-wide lists per node event. It's a cache read, but controller-runtime still deep-copies every Node, and processDryRun then walks all of them. During a rolling upgrade that's O(nodes²).

Two fixes:

  • move the List out of the rule loop so it runs once per node event
  • pass the rule's nodeSelector via client.MatchingLabels so we only copy matching nodes

continue
}

err := retry.RetryOnConflict(retry.DefaultRetry, func() error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This patches the live rule with state read from the cache. If someone flips dryRun: false while a node reconcile is in flight, we can end up writing dryRunResults back onto a now-enforcing rule and reverting observedGeneration. Status is a subresource and we use GenerationChangedPredicate, so RuleReconciler never re-runs to fix it.

Can we re-check latestRule.Spec.DryRun inside the retry closure, and skip the ObservedGeneration write here?

Comment thread test/e2e/e2e_test.go
cmd := exec.Command("kubectl", "get", "nodereadinessrule", "dryrun-test-rule", "-o", "jsonpath={.status.dryRunResults.taintsToAdd}")
output, _ := utils.Run(cmd)
return output
}, 30*time.Second, 2*time.Second).Should(Equal("1"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unless I'm misreading the marshalling, taintsToAdd is a *int32 with omitempty, which only drops nil, and processDryRun always sets the pointer. So a zero count still serializes as "taintsToAdd": 0 and jsonpath returns "0" here, not empty.

Suggested change
}, 30*time.Second, 2*time.Second).Should(Equal("1"))
}, 30*time.Second, 2*time.Second).Should(Equal("0"))

Optional, but summary might be a stronger signal than a count dropping to zero, it goes from "would add 1 taints" to "No changes needed"

@Saloni3494

Copy link
Copy Markdown
Author

@yindia Thanks for the detailed review and for laying out the two architectural options so clearly.

I agree that the second approach, unifying the logic under evaluateRuleForNode to handle both modes and updating the stats by delta, is the stronger direction. The $O(1)$ scaling per node event and the removal of duplicated aggregation logic make a lot more sense than doing full node scans.

Before I start rewriting the PR, I wanted to check if we have consensus to officially proceed with this unified shape? I'm ready to refactor the code to match this direction once everyone is aligned.

@yindia

yindia commented Aug 20, 2026

Copy link
Copy Markdown

Before you start the rewrite, let's move this to Slack or the issue thread so the maintainers can chime in. Once everyone agrees on the approach, go ahead with the refactor.

@Saloni3494

Copy link
Copy Markdown
Author

@yindia Sounds good! I've moved the architectural discussion over to Issue #54 for the maintainers to weigh in. I'll hold off on the refactor until we reach a consensus there.

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

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. 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.

Inconsistent Dry-Run Behavior Between Controllers

3 participants