fix: clean up taints on rule deletion for nodes that stopped matching the selector - #349
Conversation
cleanupTaintsForRule only considered nodes that currently match the rule's spec.nodeSelector. A node that the rule had tainted and that was later relabelled out of the selector was skipped, the finalizer was then removed, and the rule object was deleted. The taint was left behind with no owner, so nothing in the system could ever remove it again. For NoSchedule that keeps the node from accepting new workloads indefinitely, and for NoExecute it also keeps evicting pods without a toleration, on a node no rule governs any more. Cleanup now also covers nodes recorded in the rule's own status, via status.nodeEvaluations and status.appliedNodes. Using status as the signal is sound on this path because deletion is a one shot terminal cleanup of a rule that is going away, unlike steady state reconciliation where the controller's own previously written status is not a reliable basis for deciding the next action. Removing the taint from every node unconditionally would not be safe. The validating webhook only rejects rules that share a taint key and effect when their selectors overlap, so two rules may legitimately share a taint with disjoint selectors. Scoping to this rule's status keeps a node managed solely by such a sibling rule untouched, which the second new test covers. Signed-off-by: tejassinghbhati <tejassinghbhati077@gmail.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: tejassinghbhati The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
✅ Deploy Preview for node-readiness-controller canceled.
|
|
|
|
Hi @tejassinghbhati. 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 Regular contributors should join the org to skip this step. Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions 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. |
|
Lets discuss about the scope of the issue in corresponding issue. |
|
Sure, that makes sense. I have written up the options and the trade offs over on #347 so the discussion stays in one place. Happy to hold this until there is a direction, and equally happy to close it if we land on leaving the behaviour as it is and documenting it instead. Either way I will follow up with the docs change so the outcome is written down somewhere an operator will actually find it. |
|
Closing this. Agreed on #347 that it is a duplicate of #235, so this should be redone against that issue once there is agreement on the approach. The branch is still on my fork if it is worth reviving. The change scopes deletion cleanup to nodes recorded in the rule status, plus two envtest specs, one reproducing the stranded taint and one checking a node the rule never managed keeps its taint. |
Description
cleanupTaintsForRuleonly considered Nodes that currently match the rule'sspec.nodeSelector:A Node that the rule had tainted, and that was later relabelled out of the selector, was skipped during cleanup.
reconcileDeletethen removed the finalizer and the rule object was deleted, leaving the taint behind with no owner. Nothing in the system references it any more, so no future reconcile can remove it and an operator has to clear it by hand. For aNoScheduletaint the Node stops accepting new workloads indefinitely; forNoExecuteit also keeps evicting Pods without a toleration, on a Node that no rule governs.Cleanup now also covers Nodes recorded in the rule's own status, through
status.nodeEvaluationsandstatus.appliedNodes.Two design points worth calling out, since both came up while looking at the related issues:
Why using status is acceptable here. The review discussion on #339 concluded that leaning on
rule.Statusto decide what to act on is fragile, which is a large part of why #342 is still open. That objection is about steady state reconciliation, where the controller repeatedly consults status it wrote itself. The deletion path is different: it runs once, it is terminal, and the rule is being torn down, so the set of Nodes the rule ever managed is exactly the set that needs cleaning. There is no ongoing loop for a stale entry to feed back into.Why not just remove the taint everywhere. The validating webhook rejects rules sharing a taint key and effect only when their selectors overlap, so two rules may legitimately share
readiness.k8s.io/x:NoSchedulewith disjoint selectors. Unconditionally stripping the key on deletion would pull the taint off Nodes owned by the surviving sibling rule. Scoping to this rule's status avoids that, because a Node managed solely by the sibling never appears in this rule's status. The second test added here locks that behaviour in.This is deliberately scoped to the deletion path and does not attempt to resolve #342, which needs the harder "used to match, no longer does" detection during normal reconciliation. I filed this separately because the deletion case is strictly worse: while the rule still exists the taint is at least recoverable by a future fix, whereas after deletion it is permanent.
Related Issue
Fixes #347
Type of Change
/kind bug
Testing
Two specs added to the existing
when a rule is deletedcontext ininternal/controller/nodereadinessrule_controller_test.go:should remove taints from nodes that no longer match the selectorreproduces the bug. The Node is managed by the rule, relabelled out of the selector, and the rule is deleted. The spec asserts the rule object is fully gone (so nothing could clean up later) and that the taint is not left behind.should not remove a taint from a node the rule never managedguards the fix against over-removal, using a Node outside the selector that carries the same taint key and effect.Verified the first test genuinely catches the regression by stashing the source change and re-running against unmodified
main:With the fix applied, the full controller suite passes: 64 of 64 specs, up from 62 before these two were added.
internal/webhookandcmd/readiness-condition-reporterpass as well. Run against envtest with Kubernetes 1.36.2 binaries.golangci-lintv2.12.1 reports 0 issues.Checklist
make testpassesmake lintpassesDoes this PR introduce a user-facing change?