fix(triggers): gate node_not_ready on sustained NotReady, not transition - #478
Merged
Conversation
The node_not_ready matcher fired on every Ready True->False transition, so spot/preemptible reclaim, autoscaler scale-down, and graceful-shutdown drains each raised a HIGH issue for a node that was deleted seconds later. On a churny dev cluster that produced thousands of meaningless findings (346 distinct nodes in 7 days, each firing once and never recovering). Make it level-triggered on duration instead: only fire once a node has continuously reported Ready=False for >=15m. Reclaimed nodes are deleted well before the window elapses and never fire; a genuinely stuck node still surfaces. Add a 6h RateLimit keyed on the per-episode fingerprint so the now level-triggered predicate doesn't re-fire on every kubelet heartbeat.
There was a problem hiding this comment.
Code Review
This pull request updates the nodeNotReadyMatcher to trigger only when a Node has been in a NotReady state for a sustained period (at least 15 minutes) instead of immediately on transition, which helps reduce noise from expected churn. Associated tests have been updated to verify this duration-based logic. The feedback suggests using time.RFC3339Nano instead of time.RFC3339 to parse Kubernetes timestamps, as the latter can fail when fractional seconds are present.
RamanKharchee
previously approved these changes
Jun 15, 2026
Tolerate fractional-second timestamps when measuring NotReady duration; RFC3339 fails on them and would silently suppress the alert. Add a fractional-second test case.
Contributor
|
📦 Image Tags Updated |
blue4209211
approved these changes
Jun 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
node_not_readymatcher fired on everyReady: True→Falsetransition. On clusters with spot/preemptible or autoscaled node pools, every node reclaim, scale-down, and graceful-shutdown drain flipped a node NotReady for ~30–60s right before it was deleted — each raising a HIGH issue for a node that no longer exists.Confirmed in the dev DB: 3,347
node_not_readyevents, 346 distinct nodes in 7 days, each firing once and never recovering under that name — the signature of lifecycle churn, not incidents. All on the all-spotk8s-devrunner pools.Change
Make the matcher level-triggered on duration instead of edge-triggered on transition:
Ready=Falsefor ≥15m (nodeNotReadyMinDuration). Reclaimed nodes are deleted before the window elapses → never fire. A genuinely stuck node still surfaces.RateLimit: 6hkeyed on the per-episode fingerprint (name + lastTransitionTime), so the now level-triggered predicate doesn't re-fire on every kubelet heartbeat during a long outage. Recover-then-fail-again gets a newlastTransitionTime→ new fingerprint → fires again.Performance
Neutral-to-cheaper. The rate limiter sits before the expensive evidence-fetch path in the engine, so churn events stop at the predicate (cheaper than before, which proceeded to fingerprint + 3 K8s events-table API calls per transition). Rate-limiter entries are only created for nodes that pass the 15m gate — the churny ones never reach
Allow, so no memory growth.Notes
Ready=Falseonly (unchanged). Unreachable nodes reportReady=Unknownand still won't fire — sustained-unreachable detection would be a follow-up.node_not_readybacklog onk8s-devis unaffected and can be cleared separately.Test
Rewrote the matcher tests for the duration gate (fires past threshold, silent within threshold, silent when Ready).
go build,go test ./pkg/triggers ./pkg/alerts,go vetall pass.