La domanda è quasi sempre la stessa: stanotte il sito impiegava venti secondi a rispondere e al mattino si era sistemato da solo. Non c’è più niente da guardare — top risponde per il secondo corrente, mentre la domanda riguarda le tre di notte. Qui sotto: che cosa significano davvero i numeri del carico, quali vanno letti a coppie e perché una misurazione isolata inganna più spesso di quanto aiuti.

Load average: tre numeri e un fraintendimento diffuso

uptime
cat /proc/loadavg
nproc

I tre numeri sono medie su uno, cinque e quindici minuti. Non vanno confrontati con zero, ma con il numero di core che riporta nproc. Un valore di 8 su una macchina a otto core è carico pieno ma sano: c’è esattamente tanto lavoro quanto il server riesce a svolgere. Un valore di 2 su un VPS a un core è una coda lunga il doppio della capacità, e ogni richiesta aspetta il proprio turno.

Il rapporto fra i tre numeri indica la direzione. Il valore su un minuto nettamente più alto di quello su quindici significa che il carico sta salendo proprio adesso. Il contrario significa che il picco è passato e se ne vede la coda.

Veniamo ora al fraintendimento che rovina la lettura di questi numeri più di ogni altra cosa. Su Linux il load average non è «utilizzo della CPU». A differenza di altri sistemi Unix, Linux vi conta non solo i processi in esecuzione o pronti a esserlo, ma anche quelli nello stato D, il sonno ininterrompibile. Vale a dire l’attesa del disco o di un filesystem di rete. Da qui il server con load 12 e la CPU quasi ferma: non si lavora, si sta in fila.

CPU o disco

Distinguere i due casi è la prima cosa da fare:

vmstat 1 5
iostat -x 1 3

Nell’output di vmstat contano tre colonne. r è quanti processi sono in coda per la CPU, b quanti sono bloccati in attesa di I/O e wa la quota di tempo in cui la CPU è rimasta ferma ad aspettare il disco. Un wa stabilmente sopra il 10–15 % con us e sy modesti significa che il server è limitato dal disco: aggiungere core non serve, quelli che ci sono sono già liberi.

Quali processi stiano aspettando si può elencare per nome:

ps -eo state,pid,comm,wchan:30 | awk '$1 ~ /^D/'

Un esempio dalla nostra esperienza diretta. Con la configurazione predefinita Suricata scrive una trentina di tipi di evento e non imposta alcuna rotazione: su un server in produzione questo ha prodotto 15 GB di log in due giorni. Il carico non si manifestava come CPU occupata, ma proprio come I/O wait: il sistema scriveva senza sosta e tutto il resto stava in coda dietro di lui. Il rimedio non è un piano più grande, ma un elenco di eventi più corto e una rotazione configurata.

Il caso opposto, puramente di CPU e assai meno evidente. Una pagina web richiamava apt con l’utente sotto cui gira PHP-FPM. Root tiene una cache binaria in /var/cache/apt/pkgcache.bin — 70 MB su un host con una dozzina di repository — e la mappa in memoria a costo zero. Un utente comune non può scrivere in quella directory e ricostruisce la cache da capo a ogni chiamata. Misurato sulla stessa identica macchina: 0,01 secondi di tempo CPU come root contro 4,2 secondi come utente non privilegiato. Lo stesso comando, una differenza di quattrocento volte, moltiplicata per ogni apertura della pagina.

La conclusione dei due casi è la stessa: il carico si misura, non si indovina. Cronometrare separatamente ogni comando sospetto richiede mezz’ora e di solito porta altrove rispetto a dove puntava il sospetto iniziale.

Memoria: free non mostra quello che sembra

free -h

La colonna used da sola dice pochissimo, e la colonna free è addirittura fuorviante: Linux affida la memoria inutilizzata alla cache delle pagine e la restituisce alle applicazioni alla prima richiesta. La colonna da leggere è available, cioè quanta memoria si può occupare senza finire in swap. Un buff/cache grande non è un problema, ma il segno di un sistema che lavora come deve.

Nello swap conta non il valore attuale ma la forma. Salito e tornato a zero: c’è stato un picco breve. Salito una volta e rimasto lì: il picco è già avvenuto, delle pagine sono state sfrattate e nessuno le riporta indietro; il server sembra tranquillo anche se a un certo punto la memoria gli è mancata. Se lo scambio è in corso adesso lo dicono le colonne si e so di vmstat; valori diversi da zero lì sono la forma di rallentamento che gli utenti percepiscono di più.

Se durante il calo notturno un processo è semplicemente sparito, la spiegazione di solito è qui:

journalctl -k --since yesterday | grep -i "out of memory"
dmesg -T | grep -i "killed process"

Una riga come Out of memory: Killed process 1234 (mysqld) chiude la questione meglio di qualsiasi grafico: il database non «è caduto da solo», l’ha fermato il kernel perché la memoria era finita.

Chi lo sta facendo

ps aux --sort=-%cpu | head -10
ps aux --sort=-%mem | head -10

Un’avvertenza: il %CPU di ps è una media su tutta la vita del processo, quindi una fiammata breve in quell’elenco si perde. Per quella servono top oppure pidstat 1 5, che misurano su un intervallo.

Quando il carico diventa una questione di sicurezza

Un 100 % di CPU piatto durante la notte, con un processo dal nome insignificante e la directory di lavoro in /tmp o /dev/shm, è il quadro classico di un miner, non quello di un sito cresciuto. Un picco di traffico in entrata insieme a più scritture nei log è un attacco alle password in corso. Un volume di log che schizza all’improvviso è di norma una tempesta di falsi positivi di un filtro.

Gli strumenti di protezione stessi sono una categoria a parte. Su uno dei nostri server il load si teneva intorno a tre senza alcun visitatore, e in cima a ps per tempo CPU accumulato non c’erano né il sito né il database, ma CrowdSec, fail2ban, Falco e Suricata. Non è un guasto e non è un motivo per spegnerli, ma il prezzo della protezione conviene conoscerlo in cifre: su un VPS piccolo si sente.

A che serve osservare

Tutto quanto sopra risponde alla domanda «che cosa sta succedendo adesso». La domanda del mattino — che cosa è successo alle tre di notte — questi comandi non la coprono: della notte trascorsa non esistono dati se nessuno li ha registrati. E mettere in piedi Prometheus e Grafana per un singolo VPS è discutibile, perché lo stack che osserva finisce per pesare più del server osservato.

Bastano una riga su un database ogni cinque minuti e una pagina che ne disegni le ultime 24 ore: load average, utilizzo della CPU e I/O wait separati, memoria e swap, letture e scritture del disco, riempimento della partizione, inode, descrittori di file, connessioni. Su un unico asse temporale le coppie si leggono a occhio: wa alto con la CPU tranquilla, swap mai tornato a zero, descrittori vicini al limite — il futuro errore too many open files diventa visibile ore prima che accada. Come appare tutto questo messo insieme lo mostra la pagina demo qui sotto.