aboutsummaryrefslogtreecommitdiffstats
path: root/rules
diff options
context:
space:
mode:
Diffstat (limited to 'rules')
-rw-r--r--rules/alerts.yml140
1 files changed, 140 insertions, 0 deletions
diff --git a/rules/alerts.yml b/rules/alerts.yml
new file mode 100644
index 0000000..cd3b1a7
--- /dev/null
+++ b/rules/alerts.yml
@@ -0,0 +1,140 @@
+# Alerting rules for the collectors' metrics.
+#
+# Loaded when services.telemetry.server.alerts.enable is on. Most of the conditions are
+# one-liners because rules/smartctl.yml has already reduced drive health to a single
+# smartctl:health_code per drive -- see that file for how the verdict is derived.
+#
+# Every alert carries the `host` label, so notifications say which machine. That label
+# comes from the relabelling in modules/server.nix, not from the exporters.
+#
+# These are deliberately conservative: long `for` durations, and thresholds chosen so a
+# healthy fleet is silent. If you want different numbers, set
+# services.telemetry.server.alerts.defaultRules = false and point
+# services.telemetry.server.prometheus.ruleFiles at your own copy.
+
+groups:
+ - name: drives
+ rules:
+ # smartctl:health_code is 2 when the drive's own SMART self-assessment failed, an
+ # attribute is at or below its vendor threshold, or the drive reported errors it
+ # could not correct. Any of those means replace it.
+ - alert: DriveFailing
+ expr: smartctl:health_code == 2
+ for: 15m
+ labels:
+ severity: critical
+ annotations:
+ summary: "{{ $labels.host }}: drive {{ $labels.device }} is failing"
+ description: >-
+ SMART reports this drive as failing. Check the Drive Health dashboard for
+ which attribute tripped, and plan a replacement.
+
+ # Reallocated, pending or uncorrectable sectors on SATA; a non-empty grown defect
+ # list on SAS. Not failing yet, so this is a long-fuse warning rather than a page.
+ - alert: DriveCaution
+ expr: smartctl:health_code == 1
+ for: 6h
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: drive {{ $labels.device }} has degraded sectors"
+ description: >-
+ Watch the trend rather than the absolute number -- a stable count is
+ tolerable, growth is not.
+
+ - alert: SelfTestFailed
+ expr: smart_selftest_last_passed == 0
+ for: 1h
+ labels:
+ severity: critical
+ annotations:
+ summary: "{{ $labels.host }}: {{ $labels.device }} failed its {{ $labels.test_type }} self-test"
+
+ # Sustained operation above ~55C measurably shortens drive life.
+ - alert: DriveOverheating
+ expr: smartctl_device_temperature{temperature_type="current"} > 55
+ for: 30m
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: {{ $labels.device }} is at {{ $value }}C"
+
+ - name: pools
+ rules:
+ - alert: PoolNotOnline
+ expr: zfs_pool_health_code != 0
+ for: 5m
+ labels:
+ severity: critical
+ annotations:
+ summary: "{{ $labels.host }}: pool {{ $labels.pool }} is not ONLINE"
+
+ # Errors ZFS could not repair from redundancy: data has been lost and the affected
+ # files need restoring from backup.
+ - alert: PoolDataErrors
+ expr: zfs_pool_data_errors > 0
+ for: 5m
+ labels:
+ severity: critical
+ annotations:
+ summary: "{{ $labels.host }}: pool {{ $labels.pool }} has unrepairable data errors"
+
+ # ZFS write performance degrades sharply past ~80%, and a pool near 100% becomes
+ # effectively read-only. 85% leaves room to react.
+ - alert: PoolNearlyFull
+ expr: zfs_pool_capacity_ratio > 0.85
+ for: 1h
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: pool {{ $labels.pool }} is {{ $value | humanizePercentage }} full"
+
+ - alert: ScrubOverdue
+ expr: time() - zfs_pool_last_scrub_end_timestamp_seconds > 45 * 86400
+ for: 1h
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: pool {{ $labels.pool }} has not been scrubbed in 45 days"
+
+ # Two intervals late means the auto-snapshot timer has missed at least one run.
+ - alert: SnapshotsOverdue
+ expr: >-
+ (time() - zfs_snapshot_newest_timestamp_seconds)
+ > on(label) group_left() (2 * zfs_snapshot_interval_seconds)
+ for: 1h
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: {{ $labels.label }} snapshots of {{ $labels.dataset }} are overdue"
+
+ - name: collection
+ rules:
+ # Covers both exporters. Without this, every other alert here goes quiet when a
+ # machine disappears -- absence of data is not absence of a problem.
+ - alert: ExporterDown
+ expr: up == 0
+ for: 10m
+ labels:
+ severity: critical
+ annotations:
+ summary: "{{ $labels.host }}: the {{ $labels.job }} exporter is not responding"
+
+ # The ZFS collector runs every minute and the SMART one every 15, so a file older
+ # than 30 minutes means its timer is failing and the dashboards are showing stale
+ # numbers without saying so.
+ - alert: CollectorStale
+ expr: time() - node_textfile_mtime_seconds > 1800
+ for: 15m
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: {{ $labels.file }} has not been refreshed"
+
+ - alert: TextfileParseError
+ expr: node_textfile_scrape_error > 0
+ for: 15m
+ labels:
+ severity: warning
+ annotations:
+ summary: "{{ $labels.host }}: a collector wrote a malformed .prom file"