fix(monitoring): stop GrafanaSAProvisionerFailing permanent false alarm - #1453
Merged
Aviator-Coding merged 2 commits intoAug 27, 2026
Merged
Conversation
…ntly Verified live post-merge: the CronJob succeeded every 5 minutes for 2.5+ hours, but the alert never cleared. `time() - (metric or vector(0)) > 3600` doesn't fall back only when metric is absent - PromQL `or` unions by label set, and vector(0)'s empty label set never matches the metric's real labels, so both series survive and the always-true zero-label branch keeps the alert firing forever regardless of actual CronJob health. Switched to `absent(metric) or (time() - metric > 3600)`, which only produces a result from the absent() branch when the metric is truly missing. Confirmed empty-vs-populated behavior directly against the live Prometheus instance before and after the fix.
Contributor
--- kubernetes/apps/base/monitoring/grafana-sa-provisioner/app Kustomization: monitoring/grafana-sa-provisioner PrometheusRule: monitoring/grafana-sa-provisioner-rules
+++ kubernetes/apps/base/monitoring/grafana-sa-provisioner/app Kustomization: monitoring/grafana-sa-provisioner PrometheusRule: monitoring/grafana-sa-provisioner-rules
@@ -22,12 +22,14 @@
here is the failure mode by design. Check `kubectl -n monitoring logs -l
app.kubernetes.io/name=grafana-sa-provisioner --tail=50`; a 401 from admin
auth in those logs means grafana-admin-secret has drifted from live Grafana
state and needs a Grafana pod restart to clear (see kubernetes/apps/base/monitoring/grafana-sa-provisioner/README.md).
summary: Grafana Viewer service account reconciler has been failing for over
an hour.
- expr: time() - (kube_cronjob_status_last_successful_time{namespace="monitoring",
- cronjob="grafana-sa-provisioner"} or vector(0)) > 3600
+ expr: |-
+ absent(kube_cronjob_status_last_successful_time{namespace="monitoring", cronjob="grafana-sa-provisioner"})
+ or
+ (time() - kube_cronjob_status_last_successful_time{namespace="monitoring", cronjob="grafana-sa-provisioner"} > 3600)
for: 5m
labels:
severity: warning
|
Aviator-Coding
deleted the
fm/homeops-grafana-sa-provisioning-alert-fix
branch
August 27, 2026 00:48
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.
Intent
Post-merge live verification of PR #1450 (grafana-sa-provisioner self-healing Grafana service-account/token reconciler) uncovered a real bug in the merged PrometheusRule alert, which this follow-up fixes.
What I verified live against the cluster after PR #1450 merged and the captain restarted the Grafana pod (the documented one-time activation restart): the grafana-sa-provisioner CronJob's very first post-restart cycle succeeded and recreated the Viewer-scoped SA + token; the token propagated through the reconciler's own Secret -> PushSecret -> the existing 1Password item grafana-mcp/GRAFANA_SERVICE_ACCOUNT_TOKEN -> the toolhive-grafana ExternalSecret (confirmed byte-identical token value on both ends); the Reloader annotation correctly triggered a rollout of the grafana-mcp StatefulSet (confirmed via a live "Reloaded ... Changes detected in toolhive-grafana of type SECRET" Kubernetes event, and confirmed the new pod's live GRAFANA_SERVICE_ACCOUNT_TOKEN env var matches exactly what the reconciler minted); and the previously-401ing federated query now works end-to-end (direct curl proof: GET /api/org 200, GET /api/datasources 200, and a live PromQL query through the Prometheus datasource proxy returning real cluster metrics - all via the rotated token). A6 Viewer-only scope was also verified behaviorally: a write attempt (POST /api/dashboards/db) with this token correctly returns 403.
While confirming the GrafanaSAProvisionerFailing alert's behavior over an extended observation window (CronJob had been succeeding every 5 minutes for 2.5+ hours), I found the alert was firing continuously and never clearing. Root-caused by querying the live Prometheus instance directly: the shipped expression "time() - (kube_cronjob_status_last_successful_time{...} or vector(0)) > 3600" does not behave as a fallback-only-when-absent. PromQL's "or" unions series by label set rather than substituting one operand for the other; vector(0) produces a single series with an empty label set, which never matches the real metric's (non-empty) label set, so "metric or vector(0)" always returns BOTH series when the metric exists, not just the metric's. "time() - 0 > 3600" is essentially always true, so that phantom zero-label series stays in the alert's result set permanently regardless of the real metric's freshness - I verified this directly by querying "metric or vector(0)" against live Prometheus and seeing two result series (the real one plus the phantom empty-label one).
This PR fixes the expression to "absent(metric) or (time() - metric > 3600)", which I verified live: absent() on this metric with the real matchers only produces a result when the series is genuinely missing (confirmed empty result while the metric exists and is fresh; confirmed a populated result when querying a nonexistent cronjob name as a sanity check), so the alert now correctly stays silent while the CronJob is healthy and will only fire when it has genuinely never succeeded or has gone stale for over an hour - matching the original design intent ("silence is the failure mode", but not the reverse: constant false alarm is not a design goal either). No other files changed; this is a single-expression fix to the PrometheusRule already shipped in kubernetes/apps/base/monitoring/grafana-sa-provisioner/app/prometheusrule.yaml.
What Changed
GrafanaSAProvisionerFailingPromQL expression fromtime() - (metric or vector(0)) > 3600toabsent(metric) or (time() - metric > 3600)so a healthy CronJob no longer leaves a permanent phantom empty-label series in the alert result set.vector(0)fallbacks and requireabsent()for the never-succeeded case.Risk Assessment
✅ Low: Single, live-verified PromQL correction replaces the broken metric-or-vector(0) union with absent() plus a staleness check, restoring the intended fire/clear behavior with no other behavioral surface.
Testing
Exercised the shipped alert with Prometheus promtool end-to-end: the absent()/age expression is silent while the CronJob is healthy and fires for stale or never-succeeded cases, while the pre-fix vector(0) form keeps a permanent false alarm; the provisioner CI script was updated to lock that behavior in and the full focused run passed.
Evidence: PromQL regression proof (old false-positive vs fixed silence)
Evidence: Structured promtool proof JSON
Evidence: Full grafana-sa-provisioner-test.py run
Evidence: Old buggy alert false-positive transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
✅ **Review** - passed
✅ No issues found.
✅ **Test** - passed
✅ No issues found.
python3 scripts/ci/grafana-sa-provisioner-test.py(reconciler scenarios + kustomize semantics + promtool alert/expr checks)podman … promtool test ruleson shipped PrometheusRule: healthy silent, stale>1h fires, metric-absent firespodman … promtool test rulesregression: oldmetric or vector(0)false-positive on healthy series (empty-label phantom)podman … promtoolpromql_expr_test:metric or vector(0)returns two series; shipped expr returns zero samples when healthy✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.