-
Notifications
You must be signed in to change notification settings - Fork 56
feat: Add selector and bootstrap observability metrics #286
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -135,6 +135,15 @@ func (r *RuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. | |
| // Update rule cache (after cleanup) | ||
| r.Controller.updateRuleCache(ctx, rule) | ||
|
|
||
| // Update selector_matched_nodes_total from the node list we already fetched. | ||
| var matchedCount int | ||
| for i := range nodeList.Items { | ||
| if r.Controller.ruleAppliesTo(ctx, rule, &nodeList.Items[i]) { | ||
| matchedCount++ | ||
| } | ||
| } | ||
| metrics.SelectorMatchedNodes.WithLabelValues(rule.Name).Set(float64(matchedCount)) | ||
|
|
||
| // Handle dry run | ||
| if rule.Spec.DryRun { | ||
| if err := r.Controller.processDryRun(ctx, rule, nodeList); err != nil { | ||
|
|
@@ -209,6 +218,9 @@ func (r *RuleReconciler) reconcileDelete(ctx context.Context, rule *readinessv1a | |
| metrics.RuleLastReconciliationTime.DeleteLabelValues(rule.Name) | ||
| metrics.BootstrapCompleted.DeleteLabelValues(rule.Name) | ||
| metrics.BootstrapDuration.DeleteLabelValues(rule.Name) | ||
| metrics.SelectorMatchedNodes.DeleteLabelValues(rule.Name) | ||
| metrics.BootstrapCompletionErrors.DeleteLabelValues(rule.Name) | ||
| metrics.BootstrapNRCDuration.DeleteLabelValues(rule.Name) | ||
|
|
||
| // For multi-label metrics, use DeletePartialMatch to wipe all combinations | ||
| metrics.NodesByState.DeletePartialMatch(ruleLabel) | ||
|
|
@@ -419,6 +431,8 @@ func (r *RuleReadinessController) evaluateRuleForNode(ctx context.Context, rule | |
| if duration > 0 { | ||
| metrics.BootstrapDuration.WithLabelValues(rule.Name).Observe(duration) | ||
| } | ||
| // TODO: pending decision — record BootstrapNRCDuration from readiness.k8s.io/taint-applied-<rule> to time.Now(); skip if missing. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR introduces a new format of bootstrap annotation where the value is a JSON. Instead of creating a new annotation
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that approach better too. As it was still in discussion, I went with the annotation one. Since #224 is still pending, I'll remove |
||
| // Alternative: dedicated status condition (needs nodes/status RBAC). | ||
| } else { | ||
| log.V(4).Info("Skipping bootstrap duration metric for legacy node or missing transition", | ||
| "node", node.Name, | ||
|
|
@@ -437,8 +451,12 @@ func (r *RuleReadinessController) evaluateRuleForNode(ctx context.Context, rule | |
| // Record add taint latency and taint operation counter | ||
| metrics.TaintOperations.WithLabelValues(rule.Name, "add").Inc() | ||
| recordLatency("add_taint") | ||
| // TODO: pending decision — on first taint add, patch readiness.k8s.io/taint-applied-<rule> with time.Now() when absent (same r.Patch(), no extra RBAC). | ||
| // Alternative: dedicated status condition (needs nodes/status RBAC). | ||
|
|
||
| case !shouldRemoveTaint && currentlyHasTaint: | ||
| // Adopted taints were already on the node — we never wrote taint-applied-<rule>, | ||
| // so BootstrapNRCDuration is skipped at completion. | ||
| if isFirstEvaluation { | ||
| log.Info("Adopting pre-existing taint", "node", node.Name, "rule", rule.Name, "taint", rule.Spec.Taint.Key) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,6 +117,35 @@ var ( | |
| }, | ||
| []string{"rule"}, | ||
| ) | ||
|
|
||
| // SelectorMatchedNodes tracks how many nodes match a rule's NodeSelector. | ||
| SelectorMatchedNodes = prometheus.NewGaugeVec( | ||
| prometheus.GaugeOpts{ | ||
| Name: "node_readiness_selector_matched_nodes_total", | ||
| Help: "Number of nodes matched by a rule's NodeSelector", | ||
| }, | ||
| []string{"rule"}, | ||
| ) | ||
|
|
||
| // BootstrapCompletionErrors tracks failures writing the bootstrap-completion annotation. | ||
| BootstrapCompletionErrors = prometheus.NewCounterVec( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't find this in the doc. Where are we planning to use these metrics? I feel counter without reason of failure won't be that useful. Please correct me if I am understanding this wrongly
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes it wasnt there in the doc, I actually found this gap while implementing the metrics. Right now, if writing the bootstrap annotation fails after all retries are exhausted, the node gets excluded from |
||
| prometheus.CounterOpts{ | ||
| Name: "node_readiness_bootstrap_completion_errors_total", | ||
| Help: "Total failures writing the bootstrap-completion annotation", | ||
| }, | ||
| []string{"rule"}, | ||
| ) | ||
|
|
||
| // BootstrapNRCDuration tracks time from NRC taint application to bootstrap completion. | ||
| // Unlike bootstrap_duration_seconds, this excludes pre-NRC node boot time. | ||
| BootstrapNRCDuration = prometheus.NewHistogramVec( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing implementation here or is this the pending decision one?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes its the pending one, was waiting for the decision. Btw I think it's cleaner to remove this metric from the PR for now and open a separate PR once PR #224 merges. what do you say? |
||
| prometheus.HistogramOpts{ | ||
| Name: "node_readiness_bootstrap_nrc_duration_seconds", | ||
| Help: "Time from NRC taint application to bootstrap completion for bootstrap-only rules", | ||
| Buckets: []float64{1, 5, 10, 30, 60, 120, 300, 600, 1200}, // 1s to 20min | ||
| }, | ||
| []string{"rule"}, | ||
| ) | ||
| ) | ||
|
|
||
| func init() { | ||
|
|
@@ -131,4 +160,7 @@ func init() { | |
| metrics.Registry.MustRegister(NodesByState) | ||
| metrics.Registry.MustRegister(ConditionEvaluationFailures) | ||
| metrics.Registry.MustRegister(RuleLastReconciliationTime) | ||
| metrics.Registry.MustRegister(SelectorMatchedNodes) | ||
| metrics.Registry.MustRegister(BootstrapCompletionErrors) | ||
| metrics.Registry.MustRegister(BootstrapNRCDuration) | ||
| } | ||
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.
we already do the same thing inside processAllNodesForRule here
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.
Yeah, I intentionally kept it before the dry-run check so the gauge gets updated for both dry-run and normal rules in one place. If I move it into processAllNodesForRule, dryrun rules would never update the metric as they go through processDryRun. Open to refactor it if you think splitting it across both paths is cleaner.