ci: validate the vmalert rule set before it can reach production #440
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!440
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "feat/validate-alert-rules"
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 #439.
The gap
The 49 rules in
roles/monitoring/templates/alert-rules.yml.j2had no gate. TheRender alert rulestask carries novalidate:and notifiesRestart vmalertdirectly, so a typo went straightto production.
grep -rn promtoolover the repo returned nothing.Why it is worse than one broken alert
vmalert parses the rule file as a whole, and
vmalert.container.j2does not pass-rule.validateExpressions=false. One unbalanced paren is fatal to all 49 rules, not to one, andRestart=alwaysmakes that a crash loop rather than a stopped unit. Measured against the pinnedv1.147.0:
Nothing would notice. The
Watchdogdead-man's switch exists, but its Alertmanager receiverdeliberately has no integrations yet, so it only protects against vmalert death once an external
monitor consumes it and alarms on silence.
Shape
scripts/check-alert-rules.py, wired into CI on theansibleorscriptschange gates.Render with the real variables under
StrictUndefined. All 34 variables the template usesresolve with no vault password: 30 from the monitoring role's defaults, 4 from plaintext
group_vars/all/vars.yml, plusansible_managed. So nothing needs faking, and strictness is thepoint. Substituting a placeholder for an undefined variable renders
> 1, which parses, so thecheck would pass on a role default renamed out from under the template. That is the most likely
failure of all.
Parse with vmalert, not promtool. promtool works on today's file but is the wrong engine:
vmalert is VictoriaMetrics and accepts MetricsQL that promtool rejects, so promtool could only
approximate and would drift toward false failures.
-dryRunhits the same config parse the realstartup does and needs no datasource. The tag is read from the role defaults, so no second literal
can drift.
PyYAML and Jinja2 both arrive with the
ansiblebaked into the runner image (#65), so nothing newis installed.
The self-test earned its keep immediately
--self-testmutates the inputs and asserts the check fails. It runs in CI, not just once by hand.On its first run it failed, and it was right to. A threshold set to null renders the literal string
None, andx > Noneis legal PromQL, becauseNoneparses as a vector selector. vmalert acceptedthe file and the rule could never have fired.
StrictUndefineddid not help either, because the keyexisted. Fixed by dropping null values at the merge step so the strict render rejects them.
Verification
./scripts/check-alert-rules.pyreportsOK: 49 vmalert rules render and parse, matching the 49- alert:lines in the template../scripts/check-alert-rules.py --self-testpasses 4/4, and each case was observed failing beforethe corresponding guard existed.
ruff,prettier,markdownlint,ansible-lint,shellcheckandgitleaksall clean.Not in scope
registry_mirror_images, so this step pulls from docker.io. Thatleaves it as one of the few CI steps still reaching outside the instance (#137). Mirroring it is a
cheap follow-up.
Watchdogheartbeat an external consumer is the larger gap, and is what makes avmalert failure silent today. Not tracked yet.