There was no rule covering backups at all: a failed nightly job would have gone unnoticed, which is precisely the silent failure management #0030 is about. BackupJobFailed catches a failed run, BackupNotRunning catches a CronJob that stopped scheduling, and RestoreDrillStale fires when the monthly drill stops — an unverified backup is an assumption again, so the absence of the check is itself worth alerting on. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
123 lines
5.6 KiB
YAML
123 lines
5.6 KiB
YAML
# Alert-Regeln (gitops#32). Bewusst wenige, dafuer die real erlebten Fehlerklassen:
|
|
# - TargetDown: "Exporter/Dienst tot und keiner merkt es" (coturn-Klasse)
|
|
# - KubePodRestartLoop: Crashloops im k3s-Cluster (36k-coturn-Restarts-Klasse)
|
|
# - HostOom/HostLowMemory/HostLowDisk/HostHighSwap: Ressourcendruck der Hosts
|
|
groups:
|
|
- name: axion-basics
|
|
rules:
|
|
- alert: TargetDown
|
|
expr: up == 0
|
|
for: 5m
|
|
labels:
|
|
severity: critical
|
|
annotations:
|
|
summary: "Target {{ $labels.job }}/{{ $labels.instance }} ist seit 5m nicht erreichbar"
|
|
|
|
- alert: KubePodRestartLoop
|
|
expr: increase(kube_pod_container_status_restarts_total[1h]) > 3
|
|
for: 10m
|
|
labels:
|
|
severity: warning
|
|
annotations:
|
|
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} restartet wiederholt ({{ $value | printf \"%.0f\" }}x in 1h)"
|
|
|
|
- alert: HostOomKill
|
|
expr: increase(node_vmstat_oom_kill[15m]) > 0
|
|
labels:
|
|
severity: warning
|
|
annotations:
|
|
summary: "OOM-Kill auf {{ $labels.instance }}"
|
|
|
|
- alert: HostLowMemory
|
|
expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.08
|
|
for: 15m
|
|
labels:
|
|
severity: warning
|
|
annotations:
|
|
summary: "{{ $labels.instance }}: <8% RAM verfuegbar seit 15m"
|
|
|
|
- alert: HostHighSwap
|
|
expr: (node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes) / node_memory_SwapTotal_bytes > 0.5
|
|
for: 30m
|
|
labels:
|
|
severity: warning
|
|
annotations:
|
|
summary: "{{ $labels.instance }}: >50% Swap in Nutzung seit 30m"
|
|
|
|
- alert: HostLowDisk
|
|
expr: node_filesystem_avail_bytes{mountpoint="/",fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{mountpoint="/",fstype!~"tmpfs|overlay"} < 0.10
|
|
for: 15m
|
|
labels:
|
|
severity: critical
|
|
annotations:
|
|
summary: "{{ $labels.instance }}: <10% Platz auf / frei"
|
|
|
|
# CVE-Pipeline (gitops#47): Funde aus den Trivy-Scans. Pflichtfelder je Alarm:
|
|
# CVE-ID, Mitigation (Fix-Version + NVD-Link), Ort (host), Typ (target_type);
|
|
# der Zeitstrahl kommt aus trivy_vuln_first_seen_timestamp + resolved-Edit.
|
|
# Alle Regeln routen per room-Label in den Security-Raum.
|
|
- name: axion-cve
|
|
rules:
|
|
# Aggregiert pro Image statt pro CVE (gitops#51, Entscheidung sorb
|
|
# 2026-08-01): ~1000 Einzelalarme waeren Rauschen; die menschlich
|
|
# handhabbare Einheit ist "Image X hat N kritische CVEs". Die
|
|
# CVE-Details (Pflichtfelder) liegen im Grafana-Dashboard cve-overview,
|
|
# die trivy_vuln_info-Einzelserien bleiben dafuer erhalten.
|
|
- alert: TrivyCriticalVulns
|
|
expr: count by (target, target_type, host) (trivy_vuln_info{severity="CRITICAL"}) > 0
|
|
labels:
|
|
severity: critical
|
|
room: security
|
|
annotations:
|
|
summary: "{{ $labels.target }} [{{ $labels.target_type }}@{{ $labels.host }}]: {{ $value }} CRITICAL-CVEs — Details: https://selendis.axion1337.de/d/cve-overview"
|
|
- alert: TrivyHighVulns
|
|
expr: count by (target, target_type, host) (trivy_vuln_info{severity="HIGH"}) > 0
|
|
for: 24h
|
|
labels:
|
|
severity: warning
|
|
room: security
|
|
annotations:
|
|
summary: "{{ $labels.target }} [{{ $labels.target_type }}@{{ $labels.host }}]: {{ $value }} HIGH-CVEs (seit 24h offen) — Details: https://selendis.axion1337.de/d/cve-overview"
|
|
- alert: TrivyScanStale
|
|
expr: time() - trivy_last_scan_timestamp > 172800
|
|
labels:
|
|
severity: warning
|
|
room: security
|
|
annotations:
|
|
summary: "CVE-Scan fuer {{ $labels.target }} ist aelter als 2 Tage — Scanner pruefen (ein still gestorbener Scanner macht blind)"
|
|
|
|
# Sicherungen (management #0030). Bis 2026-08-14 gab es hierzu KEINE Regel: ein
|
|
# fehlgeschlagenes naechtliches Backup waere unbemerkt geblieben - genau der stille
|
|
# Ausfall, den das Issue beschreibt. Metriken kommen aus kube-state-metrics im
|
|
# Matrix-Cluster (Alloy -> remote_write, kube_job_*/kube_cronjob_* passieren den
|
|
# Filter, der nur go_.*|process_.* verwirft).
|
|
- name: axion-backup
|
|
rules:
|
|
# Ein Backup- oder Probe-Job ist fehlgeschlagen.
|
|
- alert: BackupJobFailed
|
|
expr: max by (namespace, job_name) (kube_job_status_failed{job_name=~"(synapse|authentik|wikijs)-backup.*|restore-drill.*"}) > 0
|
|
for: 15m
|
|
labels:
|
|
severity: critical
|
|
annotations:
|
|
summary: "Backup-Job {{ $labels.namespace }}/{{ $labels.job_name }} ist fehlgeschlagen — ohne Sicherung ist der naechste Ausfall ein Datenverlust"
|
|
|
|
# Der CronJob plant gar nicht mehr (ausgesetzt, geloescht, Cluster-Problem).
|
|
# 26h Toleranz fuer die taeglichen Laeufe um 03:00/03:15/03:30.
|
|
- alert: BackupNotRunning
|
|
expr: time() - kube_cronjob_status_last_schedule_time{cronjob=~"(synapse|authentik|wikijs)-backup"} > 93600
|
|
labels:
|
|
severity: critical
|
|
annotations:
|
|
summary: "Seit ueber 26h kein Lauf von {{ $labels.cronjob }} — CronJob ausgesetzt oder verschwunden?"
|
|
|
|
# Die monatliche Restore-Probe laeuft nicht mehr. Eine Sicherung, die nie
|
|
# zurueckgespielt wurde, ist eine Vermutung — deshalb ist auch das Ausbleiben
|
|
# der PRUEFUNG ein Alarm, nicht nur ihr Fehlschlag. 40 Tage = Monatsrhythmus + Puffer.
|
|
- alert: RestoreDrillStale
|
|
expr: time() - kube_cronjob_status_last_schedule_time{cronjob="restore-drill"} > 3456000
|
|
labels:
|
|
severity: warning
|
|
annotations:
|
|
summary: "Restore-Probe seit ueber 40 Tagen nicht gelaufen — Wiederherstellbarkeit ist wieder unbewiesen (Verfahren: notfallhandbuch)"
|