This article is available in Italian only.
DanpLab · Lab NoteArchitettura operativa

Alerting che non fa rumore: Prometheus e Alertmanager in un homelab

Come progettare alert utili in un homelab: sintomi invece di cause, soglie con for e keep_firing_for, raggruppamento e inibizione in Alertmanager, finestre di manutenzione e metriche di backup con il textfile collector.

5 min readBased on real operational use
PrometheusAlertmanagerMonitoringHomelabSRE

Raccogliere metriche è la parte facile. La parte difficile è decidere quando quelle metriche devono interrompere la giornata di qualcuno. In molti homelab il problema non è la mancanza di monitoraggio: è l'eccesso di notifiche. Dopo due settimane di alert notturni su un picco di CPU al 92% per quaranta secondi, la reazione naturale è silenziare il canale. A quel punto il monitoraggio esiste ancora, ma non serve più a nulla.

Questo articolo descrive un impianto di alerting a basso rumore con Prometheus e Alertmanager, con regole, comandi e criteri verificabili. Niente dashboard decorative: solo quello che serve per far arrivare l'avviso giusto, alla persona giusta, nel momento in cui è utile.

Alert sul sintomo, non sulla causa

La prima scelta di progettazione è cosa misurare. Un alert dovrebbe descrivere un sintomo percepibile dall'utente o un requisito operativo violato, non una condizione interna del sistema.

  • Rumore: cpu_usage > 80%. Un host che compila pacchetti o fa un backup satura la CPU per minuti; non è un problema.
  • Utile: servizio_X_down, backup_non_riuscito_da_36h, endpoint_pubblico_non_risponde.
  • Utile: pieno_utilizzo_disco_al_90% con margine reale di intervento, non all'85% "per sicurezza".

Un criterio pratico: se l'alert scatta, deve esistere un'azione concreta da compiere oppure la regola va cancellata. Se non sai cosa fare quando suona, la regola sta solo addestrando chi la riceve a ignorarla.

for e keep_firing_for: distinguere un picco da un incidente

Prometheus permette di richiedere che la condizione resti vera per un certo tempo prima di generare l'alert. keep_firing_for (disponibile nelle versioni recenti di Prometheus) mantiene invece l'alert attivo per una coda di secondi dopo che la condizione è rientrata, evitando il classico lampeggio intermittente durante un riavvio dei servizi.

groups:
  - name: nodi
    interval: 30s
    rules:
      - alert: NodoNonRaggiungibile
        expr: up{job="node"} == 0
        for: 5m
        keep_firing_for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Nodo {{ $labels.instance }} non raggiungibile"
          description: "up == 0 da 5 minuti: verificare alimentazione, rete e accesso out-of-band."

Il parametro for è la prima difesa contro il rumore. Scegli in base al tempo di reazione ragionevole: cinque minuti per un host spento, dieci per un disco che si riempie, un'ora per un certificato in scadenza.

Per i servizi esposti conviene lavorare su un indicatore di errore aggregato, coerente con un ragionamento da error budget:

      - record: job:sli_error_ratio:rate5m
        expr: |
          sum(rate(http_requests_total{code=~"5.."}[5m])) by (job)
          /
          sum(rate(http_requests_total[5m])) by (job)

      - alert: ErrorBudgetBruciato
        expr: job:sli_error_ratio:rate5m > 0.01
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Oltre l'1% di errori 5xx su {{ $labels.job }}"

Alertmanager: raggruppare prima di notificare

Alertmanager non decide se un alert esiste, decide come e quando inviarlo. Le tre manopole che contano sono il raggruppamento, la finestra di attesa e la cadenza di ripetizione.

route:
  receiver: default
  group_by: [alertname, instance]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - matchers: [severity="critical"]
      receiver: oncall
      repeat_interval: 1h
    - matchers: [severity="warning"]
      receiver: default
      repeat_interval: 12h
receivers:
  - name: default
    webhook_configs:
      - url: http://127.0.0.1:8060/notify
  - name: oncall
    webhook_configs:
      - url: http://127.0.0.1:8060/notify

Tre dettagli che fanno la differenza:

  • group_by senza il nome dell'alert rischia di accorpare avvisi scollegati in un unico messaggio confuso. Tienilo esplicito.
  • group_wait breve per i critical (30 secondi) evita che un riavvio di nodo generi venti notifiche separate.
  • repeat_interval lungo sulle warning. Un warning che si ripete ogni quindici minuti viene letto come rumore anche quando è reale.

Inibizione: un alert che ne silenzia altri

Quando un nodo va giù, gli alert sui servizi che girano su quel nodo sono conseguenze, non cause. Le regole di inibizione evitano di ricevere dieci notifiche per un singolo guasto.

inhibit_rules:
  - source_matchers: [severity="critical"]
    target_matchers: [severity="warning"]
    equal: [alertname, instance]

Il campo equal va scelto con attenzione: se inibisci tutto ciò che condivide l'instance, un host spento silenzia correttamente i suoi servizi; se inibisci su etichette troppo generiche rischi di sopprimere incidenti indipendenti.

Manutenzione pianificata e silence operative

Le finestre di manutenzione ricorrenti (finestre di backup, aggiornamenti settimanali) hanno un posto dedicato in Alertmanager:

mute_timeouts:
  - name: finestra-backup
    time_intervals:
      - times:
          - start_time: '01:30'
            end_time: '03:30'
        weekdays: ['saturday', 'sunday']

Per interventi una volta sola, il silence via amtool è più pratico e lascia traccia:

amtool silence add alertname=NodoNonRaggiungibile \
  --duration=2h --comment="riavvio programmato nodo pve"

Metriche che nessun exporter produce: il textfile collector

I backup sono il classico caso in cui il fallimento è silenzioso: lo script gira alle 03:00, fallisce, e nessuno se ne accorge fino al giorno del ripristino. Il textfile collector di node_exporter copre questo vuoto senza scrivere un exporter dedicato.

Abilita il collector e fai scrivere lo script di backup in formato Prometheus:

node_exporter --collector.textfile.directory=/var/lib/node_exporter/textfile_collector

backup_last_success_timestamp_seconds 1758950400
backup_failed 0

La regola che trasforma il dato in un avviso utile:

      - alert: BackupNonRiuscito
        expr: time() - backup_last_success_timestamp_seconds > 129600
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Nessun backup riuscito da più di 36 ore"

Verifica prima del deploy

Una regola di alerting errata è peggio di una regola assente, perché fallisce in silenzio. Prima di ricaricare:

promtool check rules /etc/prometheus/rules/*.yml
amtool check-config /etc/alertmanager/alertmanager.yml
curl -s localhost:9090/api/v1/rules | jq '.data.groups[].rules[] | select(.state=="firing") | .name'

Checklist

  • Ogni alert ha un'azione associata; se non ce l'ha, va rimosso.
  • Le condizioni hanno un for proporzionato al tempo di reazione.
  • I critical sono raggruppati e ripetuti più spesso delle warning, non tutti insieme.
  • Esistono regole di inibizione per le conseguenze di un guasto del nodo.
  • Le finestre pianificate sono silenziate in modo tracciabile, non con alert disattivati a mano.
  • Il backup ha una metrica di esito e un alert sulla sua assenza.
  • Le regole passano promtool e la configurazione passa amtool prima del reload.

Un impianto di alerting si giudica da quanto è credibile: se ogni notifica che arriva è utile, chi la riceve reagisce. Tutto il resto è configurazione.