Il server Linux è in crisi?
Indice navigabile

10 comandi prima di premere il tasto RESET.
Prima o poi capita a tutti.
È venerdì, magari sono le 17:59, stai già pensando al fine settimana e improvvisamente arriva la notifica che nessun sysadmin vorrebbe ricevere:
«Il servizio non risponde.»
Ti colleghi in SSH e parte il solito rituale.
Qualcuno suggerisce di riavviare, qualcun altro dà la colpa alla rete, mentre il collega più ottimista sostiene che «fino a cinque minuti fa funzionava».
Grazie, informazione preziosissima.
Il problema è che un server GNU/Linux può smettere di funzionare correttamente per decine di motivi differenti: memoria esaurita, filesystem pieni, processi terminati, servizi in ascolto sull’interfaccia sbagliata o qualche applicazione che ha deciso di trasformare i log nella propria autobiografia.
La buona notizia è che non servono necessariamente strumenti complicati per iniziare a capire cosa stia succedendo.
Spesso bastano dieci comandi, un po’ di metodo e soprattutto la capacità di leggere quello che il sistema ci sta raccontando.
Prima regola: non riavviare alla cieca
Il riavvio è uno strumento, non una strategia di troubleshooting.
Certo, a volte risolve temporaneamente il problema. Ma potrebbe anche cancellare proprio gli indizi necessari per comprenderlo.
Un processo che consuma memoria viene terminato, una connessione problematica scompare e il sistema torna apparentemente a funzionare.
Fino alla prossima volta.
Prima di intervenire, quindi, cerchiamo di raccogliere qualche evidenza.
Mani in pasta
1. uptime — Da quanto tempo è acceso il server?
Partiamo dal comando più semplice:
uptime
In una sola riga otteniamo tre informazioni importanti: ora corrente, tempo trascorso dall’avvio e load average.
Se un server che dovrebbe essere acceso da mesi risulta avviato da cinque minuti, qualcosa merita un approfondimento.
Un riavvio inatteso? Un problema di alimentazione? Un kernel panic?
Il load average, invece, ci permette di capire quanta attività sta gestendo il sistema attraverso tre valori relativi agli ultimi 1, 5 e 15 minuti.
Attenzione però: load average non significa percentuale di utilizzo della CPU.
Comprende anche processi in attesa di risorse, come operazioni di I/O in stato non interrompibile.
Per interpretarlo correttamente conviene conoscere il numero di CPU disponibili:
nproc
Un load average di 12 su una macchina con 4 CPU logiche merita sicuramente attenzione, ma non dimostra automaticamente che il processore sia il problema.
2. dmesg — Quando il kernel decide di intervenire
Un’applicazione può terminare improvvisamente senza lasciare messaggi significativi nei propri log.
Non è detto che sia stata colpa dell’applicazione.
Potrebbe essere intervenuto l’OOM Killer, il meccanismo del kernel Linux che termina determinati processi quando il sistema deve affrontare condizioni critiche di memoria.
Per cercarne le tracce:
sudo dmesg -T | grep -Ei 'out of memory|killed process|oom'
Se troviamo messaggi relativi alla terminazione di processi, abbiamo una pista interessante.
Sui sistemi che utilizzano systemd possiamo anche consultare il journal del kernel:
sudo journalctl -k -b
E, quando il journal persistente è configurato, esaminare il boot precedente:
sudo journalctl -k -b -1
Quest’ultimo comando è particolarmente utile dopo riavvii inattesi.
Un dettaglio da ricordare: le date mostrate da dmesg -T sono una conversione dei timestamp del kernel e, in alcune circostanze, possono essere imprecise.
3. free -h — La RAM è davvero finita?
Un grande classico del troubleshooting GNU/Linux è vedere poca memoria libera e dichiarare immediatamente lo stato di emergenza.
free -h
Il punto è che Linux cerca di sfruttare la memoria inutilizzata anche per il caching.
Ed è una cosa positiva.
Per questo motivo guardare esclusivamente la colonna free può portarci a conclusioni completamente sbagliate.
La colonna available fornisce una stima molto più utile della memoria disponibile per avviare nuove applicazioni senza dover ricorrere pesantemente allo swap.
Se available è molto bassa e il sistema sta utilizzando intensamente lo swap, allora vale la pena approfondire.
Se invece abbiamo poca memoria free ma parecchia memoria available, potremmo semplicemente trovarci davanti a un sistema che sta lavorando correttamente.
Insomma, RAM utilizzata non significa necessariamente RAM sprecata.
4. df -h e df -i — Il disco è pieno, oppure no?
Qui arriviamo a una delle situazioni più interessanti.
Un servizio prova a creare un file e restituisce:
No space left on device
Controlliamo lo spazio disponibile:
df -h /
Il filesystem risulta occupato soltanto al 40%.
E allora perché non riusciamo a scrivere?
Perché lo spazio in byte non è l’unica risorsa che può esaurirsi.
Proviamo:
df -i /
Questo comando mostra l’utilizzo degli inode, strutture utilizzate dal filesystem per rappresentare i file e gli altri oggetti.
Su filesystem come ext4 è possibile esaurire gli inode disponibili pur avendo ancora molti gigabyte liberi.
Può capitare quando un’applicazione genera milioni di piccoli file temporanei, oppure quando un sistema di logging è configurato in modo particolarmente creativo.
Il risultato è paradossale soltanto in apparenza: abbiamo spazio, ma non possiamo creare nuovi file.
Ecco perché questi due comandi andrebbero sempre utilizzati insieme.
5. du — Chi sta mangiando tutto lo spazio?
Se il problema riguarda effettivamente lo spazio occupato, dobbiamo identificare dove si trova.
Un comando particolarmente comodo è:
sudo du -xh -d 1 / 2>/dev/null | sort -h | tail
Questo permette di visualizzare le directory più grandi, limitando l’analisi a un solo filesystem.
Le opzioni sono interessanti:
- -x evita di attraversare altri filesystem;
- -h rende leggibili le dimensioni;
- -d 1 limita la profondità dell’analisi;
- sort -h ordina correttamente valori espressi in KB, MB e GB.
Individuata la directory sospetta, possiamo ripetere il comando al suo interno.
Se invece sospettiamo un problema di inode, possiamo utilizzare:
sudo du --inodes -x -d 1 / 2>/dev/null | sort -n | tail
In questo caso cerchiamo le directory che contengono il maggior numero di elementi, non quelle che occupano più byte.
Ricordiamoci però una cosa: identificare una directory enorme non significa che possiamo cancellarne il contenuto indiscriminatamente.
Specialmente se contiene database, volumi Docker o dati applicativi.
6. lsof +L1 — Il file fantasma che occupa spazio
Questo è uno dei miei preferiti.
Immaginiamo una situazione nella quale df segnala un filesystem quasi completamente pieno, ma du non riesce a giustificare tutto lo spazio occupato.
Dove sono finiti quei gigabyte?
Potrebbero essere trattenuti da file cancellati che risultano ancora aperti da qualche processo.
Su Linux, eliminare un file dal filesystem non comporta necessariamente il rilascio immediato dello spazio se esistono ancora riferimenti aperti.
Per individuare questi casi:
sudo lsof +L1
Il comando può mostrare file che non hanno più collegamenti nel filesystem, ma che risultano ancora utilizzati.
Un caso tipico riguarda i file di log.
Qualcuno cancella un log gigantesco pensando di recuperare spazio, ma il processo continua a mantenere aperto il relativo file descriptor.
Risultato: il nome del file scompare, ma lo spazio resta occupato.
La soluzione può consistere nel far riaprire correttamente i log all’applicazione oppure, quando necessario, riavviare il servizio interessato.
Esistono anche tecniche per troncare direttamente il contenuto attraverso il file descriptor, ma sono interventi da valutare attentamente: possiamo perdere dati importanti o compromettere il comportamento dell’applicazione.
Prima si capisce il problema, poi si decide come intervenire.
7. ss -tulpn — Il servizio è davvero in ascolto?
Un’applicazione risulta avviata, systemd la considera attiva, ma dalla rete non è raggiungibile.
Il primo sospettato, naturalmente, è il firewall.
Povero firewall, sempre colpevole fino a prova contraria.
Prima di modificare le regole, però, controlliamo:
sudo ss -tulpn
Il comando ci permette di identificare i socket TCP e UDP in ascolto e, quando disponibili i permessi necessari, i processi associati.
La colonna che ci interessa particolarmente è quella dell’indirizzo locale.
Se un servizio TCP ascolta soltanto su 127.0.0.1:8000, significa che è raggiungibile localmente, ma non direttamente attraverso l’interfaccia di rete esterna.
Se invece ascolta su 0.0.0.0:8000, accetta connessioni sulle interfacce IPv4 disponibili.
Questo, attenzione, non garantisce che sia raggiungibile dalla rete: potrebbero esserci firewall, problemi di routing o altri controlli lungo il percorso.
Ma almeno abbiamo verificato un elemento fondamentale.
E abbiamo evitato di aprire porte a caso.
8. journalctl — I log, quelli utili
Quando dobbiamo esaminare un servizio gestito da systemd, journalctl è uno degli strumenti più comodi.
Invece di leggere migliaia di righe, possiamo concentrarci sull’unità interessata e su un intervallo temporale preciso:
sudo journalctl -u nginx.service --since "30 minutes ago" --no-pager
Oppure filtrare warning ed errori:
sudo journalctl -u nginx.service --since "30 minutes ago" -p warning --no-pager
Per conoscere lo stato del servizio:
systemctl status nginx.service
Una cosa importante è verificare non soltanto se il servizio risulta active, ma anche da quanto tempo è in esecuzione e se viene riavviato ripetutamente.
Un servizio apparentemente funzionante potrebbe trovarsi in un ciclo continuo di crash e riavvii.
Il processo esiste, certo.
Ma questo non significa che stia bene.
9. tail -F — Seguire i log senza perdere il filo
Quando dobbiamo osservare in tempo reale un file di log possiamo utilizzare:
tail -F /var/log/applicazione.log
Perché -F maiuscolo invece del classico -f?
Con GNU tail, il comportamento predefinito di -f segue il file descriptor aperto.
Se un sistema di log rotation rinomina il file e ne crea uno nuovo, potremmo continuare a osservare quello vecchio.
L’opzione -F segue invece il nome del file e tenta di riaprirlo quando viene sostituito o ricreato.
Una differenza piccola, ma estremamente utile durante le attività di troubleshooting.
Naturalmente anche -f ha i suoi utilizzi: tutto dipende da cosa vogliamo seguire.
10. watch -d — Il problema è davvero risolto?
Abbiamo individuato il problema, applicato una correzione e il servizio sembra nuovamente funzionante.
Possiamo chiudere tutto?
Non ancora.
La parte finale di ogni intervento dovrebbe consistere nel verificare che la soluzione abbia effettivamente prodotto il risultato atteso.
Per farlo possiamo utilizzare:
watch -d -n 2 'df -i /; ss -s'
Il comando esegue periodicamente le verifiche indicate.
L’opzione -n 2 imposta un intervallo di due secondi, mentre -d evidenzia le differenze rispetto all’esecuzione precedente.
Possiamo quindi osservare, ad esempio, se gli inode tornano disponibili o se il numero di connessioni si stabilizza.
Ovviamente non dobbiamo limitarci a questi parametri: la verifica deve essere coerente con il problema affrontato.
Un servizio che riprende a rispondere per trenta secondi non è necessariamente un servizio risanato.
Un piccolo bonus: organizziamo anche gli accessi SSH
Se gestiamo molti server, ricordare continuamente indirizzi IP, utenti e parametri di connessione diventa poco pratico.
Il file ~/.ssh/config permette di definire configurazioni riutilizzabili.
Ad esempio, possiamo associare il nome server-produzione a un host specifico, indicando utente, indirizzo e chiave SSH.
In questo modo sarà sufficiente eseguire:
ssh server-produzione
Lo stesso alias potrà essere utilizzato per trasferimenti e tunnel SSH.
Non risolve direttamente i problemi del server, ma elimina qualche inutile complicazione proprio quando abbiamo meno voglia di affrontarla.
Conclusioni
Il troubleshooting non consiste nel conoscere il maggior numero possibile di comandi.
Consiste nel sapere quale domanda porre al sistema, quale strumento utilizzare e soprattutto come interpretare la risposta.
Un servizio non risponde? Prima verifichiamo se è effettivamente in ascolto.
Il disco risulta pieno? Controlliamo spazio, inode e file ancora aperti.
L’applicazione scompare? Esaminiamo il kernel e la memoria.
Abbiamo risolto? Verifichiamo che il problema non si ripresenti.
Soprattutto, ricordiamoci che in un ambiente di produzione ogni intervento dovrebbe essere proporzionato al rischio e possibilmente preceduto dalla raccolta delle evidenze necessarie.
Perché la differenza tra un intervento di troubleshooting e un riavvio disperato sta tutta qui: nel primo caso sappiamo cosa stiamo facendo; nel secondo speriamo che funzioni.
E la speranza, per quanto apprezzabile, non è ancora uno strumento di monitoraggio.
Le opinioni in quanto tali sono opinabili e nulla ti vieta di approfondire l’argomento.
Risorse: