Skip to content

Prometheus alerts

Maintenance map of the alert code: what goes where, and how to extend it without breaking anything. The admin-facing operation is described in Monitoring & Alerts.

The code is split in two to keep rule generation testable:

  • dnf/lib/alerts.nix : pure functions that produce Prometheus rules from the topology. Tested in dnf/tests/unit/lib/alerts_test.nix.
  • dnf/modules/service/prometheus.nix : impure wiring (Alertmanager, routing by severity, sops, Matrix bot, vhost, blackbox probes).

Any non-trivial logic goes into alerts.nix ; the module merely plugs it in.

Exposed via dnfLib (see dnf/lib/default.nix) :

HelperRole
serviceUnitsDNF service → systemd unit (e.g. idm → kanidm.service)
nodeClassNode class (critical / non-critical / disabled): alert-* features then profile
nodeAlertEligibleSelection: a node is only monitored if it is a network/server node, carries alert-non-critical, or hosts a mapped service
severityForClassClass → severity (critical or warning)
hostExpectedUnitsA host’s expected units (from its enabled services)
mkNodeRuleGroupsNodeDown, ServiceDown, SystemdUnitFailed (+ local/WAN reach label)
mkResourceRuleGroupsDisk, RAM, load, inodes, OOM, read-only FS, disk prediction, clock, conntrack
mkNetworkRuleGroupsBlackbox probes (gateway, tailnet, DNS, ZoneInternetDown)
mkHttpRuleGroupsHTTP probes: ServiceEndpointDown + TLS certificate expiry
mkResticRuleGroupsBackup freshness (ResticBackupStale/Critical)
mkSmartctlRuleGroupsSMART disk health (DiskSmartFailing, DiskTemperatureHigh)
mkPostfixRuleGroupsPostfix relay (PostfixRelayUnhealthy, PostfixDeferredQueueHigh)
mkSynapseRuleGroupsSynapse (SynapseRestarting, SynapseHighErrorRate)
mkMaintenanceRuleGroupsMaintenance flag (silence during rebuild)
mkTailscaleRuleGroupsTailnet self-repair (TailscaleUnhealthy, TailscaleFlapping)
mkHeadscaleRuleGroupsTailnet audit on the HCS (HeadscaleNodeDrift, HeadscaleUnexpectedNode, HeadscalePolicyInvalid, HeadscaleOidcFallback, HeadscaleNodeExpiring, HeadscaleAuditStale)
mergeRuleGroupsMerges fragments into a single document
mkAlertRuleGroupsShortcut: nodes + resources + restic + SMART + tailscale + headscale
mkSilenceRoutesAlertmanager routes to the null receiver (known, accepted alerts)

Every scrape target carries a host label set to the hostname, set by mkHostTargets in prometheus.nix (one static_config per host rather than a single target list). Two effects:

  • the Matrix bot shows DiskSpaceLow at srv-backup instead of the raw instance <ip>:<port>;
  • a silence route can target a single machine.

Setting it at scrape time propagates it for free to all alerts from these jobs, including the generic resource rules, which know nothing of the topology at evaluation time.

Blackbox probes have no host (the target is an IP). PeerGatewayDown therefore sets it in the rule, with the name of the targeted gateway. Without it, a zone’s rules would differ only by their expr → promtool flags them as duplicates.

Monitoring is opt-out for infrastructure, opt-in for the rest. nodeAlertEligible retains a node only if it is of class critical (gateway/hcs/server profile or alert-critical feature), if it carries alert-non-critical explicitly, or if it hosts at least one service mapped in serviceUnits. A bare laptop/desktop therefore generates no node alerts.

A zone’s Prometheus connects to hosts in other zones (e.g. the public HCS) only over the WAN. When internet goes down, these targets become unreachable and would trigger a false “host down” alert. To prevent this:

  • each node rule carries a reach label (local same zone, wan cross-zone);
  • an ICMP blackbox probe to external IPs feeds ZoneInternetDown (fired only if all external targets fail);
  • an Alertmanager inhibition rule (equal: [zone]) silences reach="wan" alerts of the zone while ZoneInternetDown is active — the real cause is notified, not the symptoms.

So we merge everything via mergeRuleGroups, then emit only one entry :

dnf/modules/service/prometheus.nix
services.prometheus.rules = [
(builtins.toJSON (dnfLib.mergeRuleGroups (
[ (dnfLib.mkAlertRuleGroups { inherit nodes; /* … */ }) ]
++ lib.optional alerting.silenceOnRebuild (dnfLib.mkMaintenanceRuleGroups { /* … */ })
++ lib.optional alerting.network.enable (dnfLib.mkNetworkRuleGroups { /* … */ })
)))
];
I want to…I touch…
monitor a new serviceserviceUnits in alerts.nix
a new rule familya mkXRuleGroups + add it to the module’s mergeRuleGroups
wire up a metrics exporterexporter in its module (gated monitoring-node, bind preferredIp) + scrape job + gated mkXRuleGroups in prometheus.nix
change a default thresholddefaultThresholds in alerts.nix
stop alerting on an ro FS mounted on purposereadOnlyByDesignFstypes (thresholds) in alerts.nix
monitor (or not) the space of a network mountremoteFstypes (thresholds) in alerts.nix
change a node’s classfeature alert-*[:zone] (no code)
force/exclude a node from monitoringfeature alert-non-critical / alert-disabled
monitor a URL + its TLS certificatealerting.network.httpProbeUrls in the config
tune internet outage detectionalerting.network.internetProbeTargets
silence a known and accepted alertalerting.silences in a consumer module (no code)
silence a broken systemd unit across the whole fleetignoredUnits in dnf/config/alerts.nix
a new destinationthe alertmanager block of prometheus.nix (receiver + route)