fix(monitoring): the alert watching the watchdog matched zero series #443
Inga granskare
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-infra!443
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "fix/textfile-staleness-alerts"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
Closes #442.
What was wrong
UnitMetricStalecould never fire. Its matcher selected zero series.node_exporter labels textfile metrics with the path as it sees them, and
node-exporter.container.j2mountsnode_textfile_dirat/textfile. The live label is/textfile/unit.prom, never the bare basename.This is the alert whose own comment said "so the watchdog itself is watched".
Measured against production, with a negative control:
time() - node_textfile_mtime_seconds{file="unit.prom"}time() - node_textfile_mtime_seconds{file="/textfile/unit.prom"}26.5Second gap: nothing watched the cross-probe
bitborg-monitoring-probe.shwritesmonitoring_probe.promandmonitoring_alerting.prom. Neitherhad a freshness alert, and the textfile collector serves the last value indefinitely. A dead
bitborg-monitoring-probe.timerfreezes both at1and the dead-man's switch is silently gone.The asymmetry is the point. The probe covers vmalert dying. vmalert can cover the probe dying,
precisely because vmalert is alive in that scenario. Only the first half existed.
What changed
UnitMetricStalematchesfile=~"(.*/)?unit\\.prom". Regex rather than the literal path, whichwould only move the fragility to the mount point. The regex also survives a node_exporter that
reverts to basenames.
MonitoringProbeStaleandAlertingHealthMetricStale, same pattern and threshold. Theirdescriptions distinguish the two cases: both firing means the timer is dead, only the second
firing means the probe runs but its watchdog query fails.
check-alert-rules.pyand the CI comment now state what the check cannot do, and drop a staleclaim. Both said the Watchdog "only protects against vmalert death once an EXTERNAL monitor
consumes it and alarms on silence". True of the Alertmanager receiver, which does deliberately
have no integrations. Misleading about the system: the cross-probe bypasses Alertmanager and
reads
ALERTS{alertname="Watchdog"}straight from VictoriaMetrics. Confirmed live atbitborg_monitoring_alerting_healthy{host="bitborg-prod"} = 1.the live count, so the literal could only drift again.
UnitMetricStaledescription, which sent operators togitborg-unit-metrics. Thelive unit is
bitborg-unit-metrics.Why
\\.and not\.The
expr:line is a YAML plain scalar, so backslashes pass through untouched. MetricsQL thenunescapes the string literal itself. A single
\.is a parse error at query time:Verified after rendering, so what vmalert receives is what was tested.
Evidence
Each rule shown able to FIRE, not merely to parse, by evaluating the real rendered expression with
the threshold inverted:
UnitMetricStale/textfile/unit.promMonitoringProbeStale/textfile/monitoring_probe.promAlertingHealthMetricStale/textfile/monitoring_alerting.promThe last two match the 2 min timer. At the real 900s threshold none fire, which is correct.
Negative control:
{file=~"(.*/)?nosuchfile\\.prom"}returns seriesFetched 0, so the regex form isnot matching everything vacuously.
./scripts/check-alert-rules.pyreports 51 rules render and parse againstvictoriametrics/vmalert:v1.147.0, and--self-testis still 4/4.Note for the applier
This touches alert rules, so the apply restarts vmalert and resets every pending
for:timer.Deliberately not in scope
A third stage for
check-alert-rules.pythat extracts each rule's selectors, queries production,and flags any matching zero series. That is what would have caught this. It needs datasource access
from CI and a way to express legitimately-empty selectors, so it deserves its own issue.
UnitMetricStale selected `{file="unit.prom"}`. node_exporter labels textfile metrics with the path as it sees them, and node-exporter.container.j2 mounts node_textfile_dir at /textfile, so the live label is `/textfile/unit.prom`. The matcher had never selected a series, so the one alert whose stated job was watching the watchdog could not fire. Measured against production, with a negative control: {file="unit.prom"} seriesFetched 0, empty {file="/textfile/unit.prom"} seriesFetched 1, 26.5 Matched by regex rather than by the literal path, which would only move the fragility to the mount point. `\\.` is required: YAML plain scalars pass backslashes through untouched and MetricsQL then unescapes the string literal, so a single `\.` is a parse error at query time. Added MonitoringProbeStale and AlertingHealthMetricStale over the cross-probe's two textfiles. Nothing watched the probe at all. The asymmetry matters: the probe covers vmalert dying, and vmalert can cover the probe dying precisely because it is alive in that case. Only the first half was implemented. All three shown able to fire against the live datasource before this landed, by evaluating the real expressions with the threshold inverted: unit.prom 21s monitoring_probe.prom 133s monitoring_alerting.prom 139s The last two match the 2 min timer. At the real 900s threshold none fire. check-alert-rules.py cannot catch this class of defect and now says so. A selector matching zero series renders and parses cleanly, so both its stages pass. Its claim that the Watchdog only protects "once an EXTERNAL monitor consumes it" was also stale, and repeated in the CI comment: the cross-probe reads ALERTS{alertname="Watchdog"} straight from VictoriaMetrics, bypassing Alertmanager, so the receiver having no integrations does not weaken it. Dropped the hardcoded rule count from both docs rather than updating 49 to 51. The script prints the live count. Also corrected the stale unit name in the UnitMetricStale description, which pointed operators at gitborg-unit-metrics. The live unit is bitborg-unit-metrics. Closes #442