Monitoraggio VPS con Prometheus e Grafana: setup minimale per applicazioni PHP

Monitoraggio VPS con Prometheus e Grafana: setup minimale per applicazioni PHP

Nell'ultimo anno ho gestito 23 incidenti di downtime su VPS di clienti PMI. Di questi, 14 - il 61% - erano prevedibili con ore di anticipo: 5 per disco pieno (la directory dei log aveva consumato tutto lo spazio), 4 per RAM esaurita (il buffer pool di MySQL era cresciuto oltre la RAM disponibile), 3 per saturazione delle connessioni MySQL (il pool di connessioni di PHP-FPM aveva superato il max_connections configurato), e 2 per certificato TLS scaduto. In tutti e 14 i casi, le metriche del sistema mostravano segnali di allarme chiari ore o giorni prima del crash - il disco che si riempiva al ritmo di 2 GB al giorno, la RAM libera che scendeva sotto i 500 MB, le connessioni MySQL che salivano del 10% ogni ora. Ma nessuno guardava queste metriche perché nessun sistema di monitoring era installato. L'unico "monitoring" era il telefono che squillava quando il sito era già offline.

La soluzione non è un enterprise monitoring platform da migliaia di euro l'anno - è Prometheus e Grafana, due strumenti open source che insieme costano zero in licenze e richiedono meno di un'ora per un setup minimale ma efficace. Su un VPS Hetzner CPX11 (2 vCPU, 2 GB RAM, 3,85 euro al mese) o anche sullo stesso server dell'applicazione, Prometheus raccoglie le metriche del sistema e delle applicazioni, Grafana le visualizza in dashboard intuitive, e AlertManager invia notifiche (email, Telegram, Slack) quando una metrica supera una soglia critica. In questo articolo ti mostro il setup minimale che installo su ogni VPS di ogni cliente - non il setup "enterprise ready" con federation e high availability, ma il setup che in un'ora ti dà visibilità sulle 5 metriche che prevengono il 90% dei downtime evitabili.

Quali sono le metriche che prevengono davvero i downtime su un VPS PHP?

Non tutte le metriche sono ugualmente utili. Un dashboard Grafana con 50 grafici è impressionante in demo ma inutile in produzione - nessuno lo guarda quotidianamente e nessuno sa quale grafico indica un problema reale. Le metriche che installo come baseline su ogni VPS di ogni cliente sono cinque, e ciascuna ha una soglia di alert definita che genera una notifica quando viene superata.

La prima metrica è lo spazio disco residuo, misurato in percentuale e in GB assoluti. L'alert scatta quando il disco scende sotto il 15% di spazio libero - abbastanza presto per intervenire (cancellare log vecchi, comprimere backup, espandere il disco) ma non così presto da generare falsi positivi. Su un VPS con disco da 80 GB, il 15% corrisponde a 12 GB - margine sufficiente per 1-2 giorni di operatività normale anche con il workload più pesante.

La seconda metrica è la RAM disponibile, inclusi cache e buffer. Linux usa la RAM libera come cache del filesystem - il che è efficiente ma confonde chi legge free -h e vede "0 MB free." La metrica corretta è node_memory_MemAvailable_bytes di node_exporter, che calcola quanta RAM è effettivamente disponibile considerando che le cache possono essere liberate. L'alert scatta quando la RAM disponibile scende sotto i 500 MB per più di 5 minuti consecutivi - un segnale che l'OOM killer potrebbe attivarsi presto.

La terza metrica è il load average normalizzato per il numero di core. Un load average di 4 su un server con 4 core significa che il sistema è al 100% di utilizzo - ogni core ha un processo in esecuzione e nessun processo è in attesa. Un load average di 8 sullo stesso server significa che 4 processi sono in coda - il sistema è al 200% e le richieste iniziano ad accumularsi. L'alert scatta quando il load average supera il 150% del numero di core per più di 5 minuti.

La quarta metrica è il numero di connessioni MySQL attive rispetto al max_connections configurato. Quando le connessioni attive superano l'80% del massimo, le nuove richieste iniziano a ricevere errori "Too many connections" - un errore che l'utente vede come una pagina bianca o un errore 500. L'alert scatta al 75% per dare tempo al team di investigare (tipicamente un query lock o un pool FPM sovradimensionato).

La quinta metrica è il tasso di errore HTTP 5xx dal log di Nginx. Un tasso superiore al 2% delle richieste totali indica un problema applicativo che sta impattando gli utenti - anche se il server è tecnicamente operativo. Nel mio profilo professionale trovi il dettaglio dell'esperienza nel monitoring di infrastrutture per PMI - e la regola che applico è: meglio 5 metriche con alert configurati che 50 metriche senza alert, perché le metriche senza alert sono decorazione, non monitoring.

Il setup: Prometheus + node_exporter + Grafana in un'ora

L'installazione su Debian 12 segue un processo standard. Prometheus e node_exporter si installano come binari precompilati, Grafana dal repository APT ufficiale. Node_exporter è il componente che espone le metriche del sistema operativo (CPU, RAM, disco, rete) in formato Prometheus - gira come servizio systemd e ascolta sulla porta 9100.

# Installazione node_exporter (metriche di sistema)
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
tar xzf node_exporter-1.8.2.linux-amd64.tar.gz
cp node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/
useradd -rs /bin/false node_exporter

# Servizio systemd per node_exporter
cat > /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
ExecStart=/usr/local/bin/node_exporter
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now node_exporter

Prometheus si installa con la stessa logica e si configura con un file YAML che definisce cosa raccogliere e da dove:

# /etc/prometheus/prometheus.yml - configurazione minimale
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets: ['localhost:9100']

  - job_name: 'mysql'
    static_configs:
      - targets: ['localhost:9104']

  - job_name: 'nginx'
    static_configs:
      - targets: ['localhost:9113']

Per MySQL, installo mysqld_exporter che espone metriche come connessioni attive, query al secondo, slow query, buffer pool hit rate e replication lag. Per Nginx, installo nginx-prometheus-exporter che espone richieste al secondo, connessioni attive, errori 4xx/5xx. Ogni exporter è un binario standalone che gira come servizio systemd e espone le metriche su una porta dedicata - Prometheus le scrape ogni 15 secondi.

Grafana si installa dal repository APT e si configura con un datasource Prometheus puntato a http://localhost:9090. La dashboard che uso come baseline è un import della community dashboard #1860 (Node Exporter Full) che mostra CPU, RAM, disco, rete e load average in un layout chiaro e comprensibile - nessuna configurazione custom necessaria per avere visibilità completa sulle metriche di sistema.

AlertManager: dalla dashboard alla notifica su Telegram

Il valore del monitoring non è nel dashboard - è nell'alert. Un dashboard che nessuno guarda non previene nessun downtime. L'alert che arriva sul telefono del responsabile alle 3 del mattino quando il disco è al 90% previene il downtime delle 6 del mattino quando il disco sarebbe arrivato al 100%. La configurazione degli alert in Prometheus usa un file di regole YAML che definisce le condizioni e le azioni:

# /etc/prometheus/alert.rules.yml
groups:
  - name: infrastruttura
    rules:
      - alert: DiscoQuasiPieno
        expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) < 0.15
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Disco root sotto il 15% ({{ $value | humanizePercentage }})"

      - alert: RAMCritica
        expr: node_memory_MemAvailable_bytes < 500 * 1024 * 1024
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "RAM disponibile sotto 500MB ({{ $value | humanize }})"

      - alert: MySQLConnessioniAlte
        expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.75
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "Connessioni MySQL al {{ $value | humanizePercentage }} del massimo"

AlertManager riceve gli alert attivati da Prometheus e li inoltra ai canali configurati. Per i clienti PMI, il canale che funziona meglio è Telegram - il responsabile IT ha il telefono sempre con sé e la notifica Telegram è immediata, silenziosa (non sveglia il partner alle 3 di notte come una telefonata) e contiene le informazioni sufficienti per decidere se intervenire subito o aspettare il mattino. Ho descritto un approccio complementare al monitoring applicativo - dove le metriche non sono del sistema operativo ma dell'applicazione Laravel - nel mio articolo su Laravel Pulse per il monitoring nativo. I due sistemi sono complementari: Pulse per le metriche applicative (richieste lente, query lente, job falliti), Prometheus/Grafana per le metriche infrastrutturali (CPU, RAM, disco, connessioni). Insieme coprono il 95% delle cause di downtime prevedibile.

Metriche PHP-FPM: il ponte tra infrastruttura e applicazione

Oltre alle metriche di sistema (CPU, RAM, disco) e alle metriche del database (connessioni, query), c'è un terzo livello di monitoring che considero essenziale per le applicazioni PHP: le metriche di PHP-FPM. Il pool FPM è il componente che determina quante richieste il server può gestire simultaneamente, e il suo stato è spesso il primo indicatore di un problema di capacità - prima che la CPU salga, prima che la RAM si esaurisca, i worker FPM si saturano e le richieste iniziano ad accodarsi.

PHP-FPM espone le proprie metriche attraverso la status page (pm.status_path = /fpm-status nella configurazione del pool), che Prometheus può scrappare con un exporter dedicato o con un semplice script che converte il formato della status page in metriche Prometheus. Le metriche critiche sono: active_processes (worker attualmente impegnati a servire richieste), idle_processes (worker liberi e pronti), listen_queue (richieste in coda che aspettano un worker libero), e max_children_reached (numero di volte che il pool ha raggiunto il limite di pm.max_children).

L'alert che configuro sulla listen queue è il più prezioso: se listen_queue > 0 per più di 30 secondi, significa che non ci sono worker FPM disponibili e le richieste stanno aspettando in coda - ogni secondo in coda è un secondo di latenza aggiuntiva percepita dall'utente. Se max_children_reached > 0, significa che il pool FPM ha raggiunto il limite configurato almeno una volta - un segnale che pm.max_children potrebbe essere troppo basso per il carico attuale. Ho descritto il processo di tuning di questi parametri nel mio articolo sull'ottimizzazione di PHP-FPM per carichi elevati, dove le metriche del monitoring sono il prerequisito per qualsiasi decisione di tuning - senza dati, il tuning è un'ipotesi; con dati, è una scienza.

La combinazione di metriche di sistema (node_exporter), metriche database (mysqld_exporter), metriche web server (nginx_exporter) e metriche FPM crea un quadro completo della salute dell'infrastruttura che permette di diagnosticare qualsiasi tipo di rallentamento: se la CPU è alta ma la RAM è OK e i worker FPM sono tutti attivi, il problema è nel codice PHP (query lenta, calcolo pesante); se la RAM è bassa e il load è alto ma le connessioni MySQL sono basse, il problema è nel pool FPM (troppi worker che consumano RAM); se le connessioni MySQL sono alte e la listen queue di FPM cresce, il problema è nel database (query che tengono un lock troppo a lungo e bloccano i worker).

Il costo: meno di un caffè al giorno per non essere svegliati di notte

L'intero stack - Prometheus, node_exporter, mysqld_exporter, nginx_exporter, AlertManager e Grafana - consuma circa 300 MB di RAM e il 2-3% di una CPU a riposo. Su un VPS dedicato con 4+ GB di RAM, l'impatto è trascurabile e non richiede un server separato. Per i clienti che preferiscono separare il monitoring dall'applicazione (per evitare che un problema sul server dell'applicazione impatti anche il monitoring), installo lo stack su un VPS Hetzner CPX11 dedicato a 3,85 euro al mese - meno di un caffè al giorno per la certezza di essere avvisati prima che il sistema crolli.

Il ritorno sull'investimento è immediato e quantificabile: un singolo downtime evitato per un'applicazione e-commerce che fattura 5.000 euro al giorno vale 10 anni di costo del VPS di monitoring. E i downtime evitati non sono ipotetici - sono i 14 incidenti prevedibili che ho descritto all'inizio di questo articolo, ciascuno dei quali avrebbe potuto essere prevenuto con un alert che arriva 6 ore prima del crash. Se i tuoi VPS non hanno un sistema di monitoring e l'unico feedback sui problemi arriva dalle telefonate dei clienti, contattami per un setup: in un'ora installiamo Prometheus, Grafana e AlertManager, configuriamo le 5 metriche critiche con alert su Telegram, e dal giorno dopo hai visibilità completa sull'infrastruttura.

Ultima modifica: