Route alerts by severity, and prove where they landed - #34
Conversation
Alertmanager was configured with a single catch-all route and a receiver with `webhook_configs: []` -- one that parses, validates, and delivers nowhere. Every alert took the same path, and the tests could only assert that alerts reached Alertmanager's API, never that they reached anyone. There are still no vendor receivers, and that is deliberate. A config full of fake PagerDuty keys would prove nothing a reader could reuse. What is reusable is everything above the vendor, and that is what this adds: - critical routes to a pager path with group_wait 0s and a 15m repeat. Both matter. At the default a page waits to see whether a second alert joins its group, which is a fine trade for a ticket and a bad one for a cluster that has lost quorum; and a critical that notifies once and goes quiet is indistinguishable from one nobody sent. - warning routes to a ticket path that batches and does not repeat overnight. - grouping is by alertname and cluster, deliberately not by instance. Three nodes sealing at once is one incident and would otherwise page three times. - two inhibit rules, so an outage pages for the cause rather than for each consequence: no scrape targets at all explains every node being down, and lost quorum explains there being no active node. Swapping the sink for a real receiver is a one-line change per receiver. The tree, the grouping and the inhibition are the part that transfers. PROVING IT The receivers post to alert-sink, which records each delivery. That is not a stand-in for a pager. It exists because Alertmanager's own API cannot answer the question worth asking: not "did this alert arrive" but "which receiver did it reach". Without something on the receiving end, an alert routed to the wrong place looks exactly like one routed correctly. The integration suite now reads it and asserts a critical alert was delivered to the pager path and not also filed as a ticket. tests/alert-routing covers the tree itself. Where the answer belongs to Alertmanager -- which route matches a label set -- the question goes to amtool out of the pinned image rather than to a reimplementation of its matching rules, because a test that reimplements the thing it tests agrees with itself and nothing else. Those cases need Docker and skip without it. The check worth reading is that every alert in the rule file carries a severity that has a route of its own. An alert with a misspelled severity matches no route, falls through to the catch-all, and is never paged for -- and nothing else here would notice. The suite asserts that failure mode directly by asking amtool where severity=crticial goes. Nine mutations confirm the assertions are load-bearing: dropping a severity label, making critical wait to be grouped, stopping it repeating, making warning repeat every 15 minutes, grouping per instance, misspelling an alertname in an inhibit rule, pointing a route at an undefined receiver, emptying a receiver's webhook_configs back to how it was, and deleting the warning route are each caught by the assertion added for them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified in CI, not just greenThe four
The integration suite confirms delivery end to end — 82 passed (80 That second one is worth keeping. A route that matches twice pages and The regression this guardsThe previous config was: receivers:
- name: default
webhook_configs: []That parses. It validates. Still not covered, deliberately
Both are stated in |
Alertmanager had a single catch-all route and a receiver configured as
webhook_configs: []— one that parses, validates, and deliversnowhere. Every alert took the same path, and the tests could only
assert alerts reached Alertmanager's API, never that they reached anyone.
There are still no vendor receivers, deliberately. A config full of
fake PagerDuty keys proves nothing a reader could reuse. What transfers
is everything above the vendor:
pageticketgroup_wait0s30srepeat_interval15m12hgroup_wait: 0sis the point of the split — at the default a page waitsto see whether a second alert joins its group, which is fine for a ticket
and bad for a cluster that has lost quorum. And a critical that notifies
once then goes quiet is indistinguishable from one nobody sent.
Grouping is by
alertname+cluster, notinstance: three nodessealing at once is one incident, and per-instance grouping would page
three times for it.
Two inhibit rules so an outage pages for the cause, not each consequence:
no scrape targets at all explains every node being down, and lost quorum
explains there being no active node.
Proving it
Receivers post to
alert-sink, which records each delivery. Not astand-in for a pager — it exists because Alertmanager's API cannot
answer the question worth asking: not "did this alert arrive" but "which
receiver did it reach". Without a receiving end, a misrouted alert looks
exactly like a correctly routed one.
The integration suite now reads it and asserts a critical alert reached
the pager path and was not also filed as a ticket.
tests/alert-routingcovers the tree. Where the answer belongs toAlertmanager — which route matches a label set — the question goes to
amtoolfrom the pinned image rather than a reimplementation of itsmatching rules, because a test that reimplements what it tests agrees
with itself and nothing else. Those cases need Docker and skip without it
(I have no Docker locally, so the four amtool assertions are unverified
until CI runs them — everything else passes here).
The check worth reading
Every alert in the rule file must carry a severity that has a route of
its own. One with a misspelled severity matches no route, falls to the
catch-all, and is never paged for — nothing else here would notice. The
suite asserts that failure mode directly by asking
amtoolwhereseverity=crticialgoes.Mutations
Nine, each caught by the assertion added for it:
webhook_configs: [](the previous state)Still not covered
Whether your PagerDuty key is correct. And that two alerts firing at once
really does suppress the second — inhibition is checked structurally so a
typo cannot leave an inert rule, but arranging two specific alerts to
overlap on a live cluster is a fixture problem rather than a monitoring
one.
docs/monitoring.mdsays both.