df pieno ma du no: trova lo spazio nascosto
Scopri perché df è pieno mentre du non trova lo spazio: file eliminati ancora aperti, inode esauriti, mount point nascosti e blocchi riservati.
Perché df segnala il disco pieno mentre du indica il contrario
df segnala che il disco è pieno, mentre du non riesce a trovare lo spazio occupato perché un processo mantiene aperto un file che è stato eliminato. L'eliminazione di un file rimuove il relativo nome da una directory. I blocchi di dati vengono rilasciati solo quando viene chiuso l'ultimo file descriptor aperto che punta a quell'inode. du percorre i nomi dei file, quindi non conta nulla. df chiede al filesystem quanti blocchi sono allocati, quindi continua a contare il file che non ha più un nome.
Questa guida riproduce il problema su un VPS Ubuntu standard, usando strumenti già installati, individua il processo che mantiene aperto il file tramite /proc e libera lo spazio senza riavviare il sistema. Seguono anche le altre cause dello stesso sintomo: una tabella degli inode senza voci libere, file nascosti sotto un mount point e blocchi riservati a root.
Esegui ogni comando e leggi l'output del tuo sistema. I valori dipendono dal disco in uso. Confronta quindi i valori prima e dopo sul tuo computer, invece di confrontarli con un valore riportato nella guida.
Cosa conta df e cosa conta du
df (disk free) interroga ogni filesystem montato per ottenere i propri dati contabili: quanti blocchi esistono, quanti sono allocati e quanti sono liberi. Non apre mai una directory. Il risultato comprende tutti i blocchi allocati, inclusi quelli appartenenti a un file a cui non punta alcuna voce di directory.
du (disk usage) fa l'opposto. Parte dal percorso indicato, legge le directory, esegue stat su ogni voce trovata e somma i blocchi. Un file senza nome è invisibile. Lo stesso vale per qualsiasi directory che non può leggere, per questo un utente normale ottiene un totale inferiore rispetto a root. Eseguire du con sudo prima di trarre conclusioni dal confronto.
Quando si confrontano i due comandi, contano sempre due opzioni.
-xmantieneduall'interno di un solo filesystem. Senza questa opzione,du /attraversa ogni filesystem montato sotto/e produce un totale chedf /non stava mai misurando.-sstampa una sola riga riepilogativa per argomento invece di una riga per directory.
È quindi possibile eseguire affiancati i due comandi sul filesystem interessato.
df -h /
sudo du -xhs / 2>/dev/nulldf restituisce subito il risultato. du richiede minuti su un filesystem di grandi dimensioni, perché esegue stat su ogni file durante la scansione. Quando i due totali sono molto diversi e du è stato eseguito come root con -x, lo spazio mancante è allocato a qualcosa che non ha nome.
Riprodurre intenzionalmente la discrepanza
Eseguire questa procedura su una VPS di test. Tutto ciò che segue usa bash e coreutils, quindi non viene installato nulla.
Registrare lo stato iniziale del filesystem che contiene /var/tmp.
cd /var/tmp
df -h .
df --output=used -B1 .Il secondo comando stampa i byte utilizzati senza arrotondamenti, rendendo esatto il controllo finale.
Creare ora un file. La sua dimensione viene ricavata dallo spazio libero riportato direttamente dalla macchina, quindi la dimostrazione si adatta a qualsiasi disco.
free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .$(...) è la sostituzione di comando: la shell esegue il comando al suo interno e l'output diventa il valore di free. Se questa sintassi non è familiare, la sostituzione di comando in bash la descrive in dettaglio. fallocate riserva blocchi reali senza scrivervi dati, per questo termina immediatamente. Se il filesystem non supporta questa funzione, il comando non riesce; head -c $((free / 10)) /dev/zero > ghost.bin svolge la stessa operazione scrivendo effettivamente i byte.
Confrontare questo df -h . con quello registrato in precedenza. La colonna degli elementi utilizzati è aumentata e quella dello spazio disponibile si è ridotta.
Mantenere ora il file aperto da un altro processo, quindi eliminarlo.
sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/nullLa redirezione è l'elemento essenziale. sleep infinity < ghost.bin & avvia un processo in background il cui standard input è quel file; la shell apre quindi il file e passa il descrittore a sleep, che lo mantiene aperto. $! contiene l'ID del processo del job in background. rm rimuove quindi il nome mentre il descrittore è ancora aperto.
Leggere l'output. ls non riesce a trovare il file perché il nome non esiste più. du è tornato vicino al valore iniziale perché percorre i nomi. df non è cambiato perché i blocchi sono ancora allocati. Il filesystem e l'albero delle directory ora non sono coerenti: la differenza tra i due rappresenta il file appena eliminato.
Individuare il processo che mantiene aperto il file eliminato
Ogni descrittore di file aperto compare in /proc/<pid>/fd/ come collegamento simbolico al file a cui fa riferimento. Quando il file è stato scollegato, il kernel contrassegna come eliminata la destinazione del collegamento. Per individuare il processo che lo mantiene aperto, occorre quindi trovare un collegamento la cui destinazione contiene questo contrassegno.
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null-lname confronta la destinazione di un collegamento simbolico anziché il suo nome, %p stampa il percorso del descrittore e %l stampa la relativa destinazione. L'ID del processo è il secondo elemento del percorso stampato. Eseguire il comando con sudo, perché in caso contrario è possibile leggere /proc/<pid>/fd soltanto per i propri processi. Il reindirizzamento di stderr elimina i messaggi generati dai processi che terminano mentre find percorre la struttura.
Un server operativo mantiene aperti diversi file eliminati in ogni momento, ma la maggior parte è piccola e non crea problemi. Ordinarli per dimensione permette di lasciare in cima soltanto quelli interessanti.
sudo bash -c 'for fd in /proc/[0-9]*/fd/*; do
target=$(readlink "$fd" 2>/dev/null) || continue
case "$target" in
*"(deleted)") echo "$(stat -Lc %s "$fd" 2>/dev/null) $fd $target" ;;
esac
done' | sort -rn | headstat -L segue il collegamento fino all'inode, quindi %s restituisce la dimensione del file che non ha più un nome. Ordinare in base a questo numero porta per primo il file più grande.
Individuare quindi il processo associato al descrittore in cima all'elenco. Il percorso in cima contiene entrambi i numeri necessari. Inserirli prima in due variabili, sostituendo PID e N con i valori stampati dal proprio elenco.
pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"ps indica il programma e da quanto tempo è in esecuzione. stat -L stampa la dimensione e il numero di blocchi allocati dell'inode eliminato. Insieme, questi dati rispondono alla domanda principale: quale servizio mantiene in uso questo file.
Se il sistema dispone già di lsof, sudo lsof +L1 elenca i file aperti il cui conteggio dei collegamenti è sceso a zero e ne mostra le dimensioni in un'unica tabella. Questo comando non è presente in un'immagine Ubuntu minimale e l'installazione di un pacchetto su un filesystem senza spazio libero può a sua volta non riuscire. Per questo, la scansione con /proc è la variante che funziona sempre.
Liberare spazio senza riavviare
Un riavvio risolve il problema, ma non è la prima operazione corretta: interrompe il servizio e distrugge le informazioni utili per l'analisi. Esistono quattro opzioni meno invasive, da provare nell'ordine indicato.
Per prima cosa, copia i dati altrove se vuoi ancora conservarli. La lettura del percorso del descriptor legge l'inode attivo.
sudo cp /proc/<pid>/fd/<n> /root/recovered.logQuesto è l'unico caso in cui è facile recuperare un file eliminato. Per questo il recupero dei file eliminati con rm -rf inizia chiedendo se un processo mantiene ancora aperto il file. Quando l'ultimo descriptor viene chiuso, questa possibilità scompare.
Secondo, svuota il file tramite il descriptor. Il percorso /proc conduce allo stesso inode, quindi il troncamento libera i blocchi mentre il processo continua a funzionare.
sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /Questa operazione funziona correttamente quando il writer ha aperto il file in modalità append, perché ogni scrittura viene quindi eseguita alla fine corrente del file. In caso contrario, il processo mantiene il vecchio offset di scrittura. La scrittura successiva viene quindi eseguita molto più avanti nel file e lo ricrea con un hole all'inizio. Un hole non alloca spazio, quindi i blocchi restano liberi e df continua a mostrare lo spazio appena restituito. Cambia solo la dimensione: esegui di nuovo sudo stat -L "/proc/$pid/fd/$n" dopo una scrittura del processo. Il comando mostrerà la dimensione precedente accanto a un numero di blocchi che non corrisponde più a quella dimensione. Riavvia il processo quando vuoi riportare a zero anche la dimensione.
Terzo, chiedi al servizio di riaprire i log. Un daemon il cui file di log è stato eliminato mentre era ancora aperto è il caso reale più comune di questo problema. Molti daemon riaprono i file di log dopo aver ricevuto un segnale: nginx usa SIGUSR1 e rsyslog usa SIGHUP. Consulta la documentazione del daemon in uso invece di procedere per tentativi, perché l'invio del segnale sbagliato al daemon sbagliato può arrestarlo.
sudo systemctl kill -s USR1 nginxQuesto invia il segnale al processo che systemd registra come processo principale dell'unità. Di conseguenza, un'unità che dichiara il valore Type= errato rispetto alla modalità effettiva di avvio del daemon può inviare il segnale a un processo che non ha mai mantenuto aperto il file eliminato. Lo spazio resta quindi occupato.
Quarto, riavvia l'unità. sudo systemctl restart <unit> chiude ogni descriptor mantenuto dal vecchio processo, quindi i blocchi vengono certamente liberati. Nella dimostrazione precedente, il processo che mantiene aperto il file è un sleep avviato manualmente. È quindi sufficiente terminarlo.
kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/nullConfronta i byte utilizzati con il valore registrato prima di creare il file. I valori coincidono di nuovo e find non mostra più il descriptor. Verificare il risultato con lo stesso comando che ha rilevato il problema è un'abitudine utile.
Osservare l'evoluzione di questo valore è più semplice che eseguire manualmente df in continuazione. watch ripete un comando a intervalli fissi e ristampa l'output nella stessa posizione, quindi watch df -h / mostra la variazione della colonna dello spazio utilizzato mentre lo spazio viene liberato.
Quando i totali coincidono ma il disco è ancora pieno
Se df e un du -x root coincidono, non è coinvolto alcun file eliminato. Le cause rimanenti sono di altro tipo e ciascuna richiede una verifica specifica.
Esaurimento degli inode, non dei blocchi
Un inode contiene i metadati di un file. ext4 crea un numero fisso di inode quando viene creato il filesystem, quindi un filesystem può esaurire gli inode anche se dispone ancora di blocchi liberi. La creazione di nuovi file fallisce anche se df -h mostra spazio disponibile.
df -h /
df -i /Il primo comando conta i blocchi e il secondo conta gli inode. Confronta la colonna dell'utilizzo in entrambi gli output. Un utilizzo ridotto dei blocchi e un utilizzo degli inode al limite indicano la presenza di un numero molto elevato di file molto piccoli.
df rifiuta -i e --output nella stessa esecuzione. Quando devi leggere i conteggi grezzi o passarli a un altro comando, seleziona i campi degli inode per nome e lascia disattivato -i.
df --output=itotal,iused,iavail,ipcent /Queste colonne riportano gli stessi dati di contabilizzazione mostrati da df -i, in un formato che puoi analizzare.
Individua i file contando le voci invece dei byte.
sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | headRipeti lo stesso comando un livello più in basso nella directory che ha il valore maggiore, fino a raggiungere il ramo che sta creando i file. Se il tuo du non supporta --inodes, usa sudo find /var -xdev -type f | wc -l per contare lentamente un sottoalbero.
La soluzione consiste nell'eliminare o spostare quei file. Non puoi aggiungere inode a un filesystem ext4 esistente, perché il numero viene fissato al momento della creazione, indicato da mkfs. Per aumentarlo devi quindi ricreare il filesystem e ripristinare i dati da un backup. XFS alloca gli inode quando servono, quindi non raggiunge lo stesso tipo di limite fisso. Una macchina che esegue container raggiunge entrambi i limiti prima della maggior parte dei sistemi, perché i layer delle immagini contengono molti file piccoli. Su quella macchina, ridurre l'utilizzo del disco di Docker su un VPS è la soluzione specifica e recupera molto più spazio di una scansione generale del filesystem.
Spazio nascosto sotto un punto di mount
Una directory può contenere file prima che vi venga montato un filesystem. Montando un filesystem sopra quella directory, i file sottostanti rimangono esattamente dove si trovavano: continuano a occupare spazio, vengono ancora conteggiati da df e non sono più raggiungibili tramite il nome. du non può vederli perché il mount li copre.
Dimostralo con tmpfs, che non richiede spazio libero su disco. Questa procedura richiede una macchina sulla quale disponi dell'autorizzazione a eseguire mount, quindi funziona su un KVM VPS.
sudo mkdir -p /srv/covered
sudo cp /etc/services /srv/covered/
ls /srv/covered
sudo mount -t tmpfs tmpfs /srv/covered
ls /srv/covered
sudo umount /srv/covered
ls /srv/coveredIl comando centrale ls mostra una directory vuota. La copia non è stata spostata: si trova ancora sul filesystem root e torna visibile non appena esegui l'unmount. Ora immagina un servizio che abbia scritto log in quel percorso per un mese prima che qualcuno vi montasse sopra un volume.
Per trovare i file reali su un server in esecuzione, monta una seconda volta il filesystem root in un altro percorso. Un bind mount mostra un filesystem senza i filesystem montati al suo interno.
sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheckTutto ciò che compare in quell'elenco ma non è presente nel percorso normale è nascosto sotto un punto di mount. Quando hai terminato, esegui l'unmount del bind mount; in caso contrario, un successivo du senza -x conterebbe gli stessi file due volte.
Blocchi riservati per root
ext4 riserva una parte dei blocchi all'utente root, così un disco pieno non impedisce a root di accedere e riparare il sistema. Un processo eseguito da un utente normale raggiunge prima questo limite, mentre df mostra ancora un piccolo margine. Leggi l'impostazione del filesystem in uso invece di presupporre il valore predefinito.
dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'Il comando stampa il numero totale di blocchi e il numero di blocchi riservati usando la stessa unità, quindi il rapporto tra i due valori è diretto. df riporta nella colonna disponibile lo spazio che un utente normale può ancora utilizzare. Per questo la somma di spazio usato e disponibile è inferiore alla dimensione totale. La differenza corrisponde alla riserva.
Modifica il valore con sudo tune2fs -m <percent> "$dev". La modifica si applica immediatamente e non richiede di rimontare il filesystem. Ridurre la riserva su un filesystem separato usato per i dati è ragionevole. Sul filesystem root, lascia una riserva sufficiente affinché root possa ancora scrivere, perché un filesystem root completamente esaurito è molto più difficile da riparare. La riserva impedisce inoltre di perdere l'accesso al sistema: una chiave aggiunta a authorized_keys su un filesystem senza spazio libero può essere scritta solo parzialmente oppure non essere scritta affatto, e il login successivo restituisce Permission denied (publickey) per un motivo che non dipende dalla chiave. tune2fs funziona con ext2, ext3 ed ext4. XFS non dispone di un'impostazione equivalente.
Dove du può trarre in inganno se usato da solo
Quattro comportamenti di du producono totali che sembrano errati.
- Hard link:
duconta un inode una sola volta anche quando più nomi vi puntano, quindi un albero pieno di hard link riporta un valore inferiore alla somma dei suoi file. - File sparse:
duriporta i blocchi effettivamente allocati, mentrels -lriporta la dimensione apparente. Aggiungere--apparent-sizemostra l'altro valore. - Permessi: se viene eseguito come utente normale,
duignora ciò che non può leggere e riporta un valore inferiore a quello reale. Gli errori che stampa sono quelli che spesso vengono reindirizzati a/dev/null, interrompendo così l'analisi. - Confini del filesystem: senza
-x,du /conta ogni filesystem montato sotto/, quindi il totale può superare quello riportato dadf /.
Anche df ha un comportamento importante da conoscere. Riporta ogni filesystem separatamente, quindi va eseguito sul percorso esatto verso cui punta la scrittura che non riesce. Un /boot separato si riempie secondo tempistiche proprie mentre si accumulano i pacchetti del kernel, e rimuovere i vecchi kernel su Ubuntu è un'attività diversa dalla liberazione di spazio su /.
Una procedura operativa per un incidente reale
- Eseguire
df -h <path>edf -i <path>sul filesystem in cui era previsto il write non riuscito, invece che su/per riflesso. - Eseguire
sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -h, quindi scendere nella directory più grande. - Se
dunon riesce a spiegare lo spazio chedfindica come utilizzato, cercare in/proci file eliminati che sono ancora aperti. - Se i due valori coincidono, eseguire il bind mount del filesystem in un'altra posizione e cercare i file presenti sotto un mount point.
- Se il limite riguarda l'utilizzo degli inode, contare i file invece dei byte.
Ogni passaggio utilizza un comando di cui è possibile leggere l'output. Questa è la differenza tra risolvere il problema e procedere per ipotesi.
FAQ
Perché df indica che il disco è pieno mentre du rileva un utilizzo molto inferiore?
La causa più comune è un file eliminato mentre un processo lo aveva ancora aperto. La rimozione del file elimina la relativa voce dalla directory, quindi du non trova più alcun nome da attraversare e smette di conteggiarlo. L'inode e i relativi blocchi restano allocati finché non viene chiuso l'ultimo descrittore, mentre df conta i blocchi allocati. Cerca in /proc/<pid>/fd i link simbolici il cui target è contrassegnato come eliminato: in questo modo individui sia il file sia il processo che lo mantiene aperto. Prima di considerare attendibile il confronto, verifica di avere eseguito du come root e con -x, perché un utente normale ignora silenziosamente le directory che non può leggere.
Come posso trovare un file eliminato ancora aperto senza usare lsof?
Usa il registro dei descrittori aperti mantenuto dal kernel. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null elenca ogni descrittore che punta a un file privo di nome; l'ID del processo è incluso nel percorso visualizzato. sudo stat -Lc %s, eseguito su uno di quei percorsi dei descrittori, ne restituisce la dimensione, così puoi ordinarli e individuare quello rilevante. Non è necessario installare alcun pacchetto. Questo è importante perché l'installazione di un pacchetto su un filesystem senza spazio libero può non riuscire.
Posso liberare lo spazio senza terminare il processo?
A volte. sudo truncate -s 0 /proc/<pid>/fd/<n> raggiunge lo stesso inode tramite il descrittore e libera i relativi blocchi mentre il processo continua a funzionare. È la soluzione più pulita quando il processo ha aperto il file in modalità append, perché le scritture vengono sempre eseguite alla fine corrente del file. In caso contrario, l'offset di scrittura resta nella posizione precedente e la scrittura successiva ricrea il file con un buco all'inizio. La dimensione indicata torna quindi ad aumentare, mentre i blocchi sottostanti al buco restano liberi. Riavviare l'unità o inviarle il segnale indicato nella relativa documentazione per riaprire i log è la soluzione che non lascia file sparse.
df indica spazio libero, ma le scritture continuano a non riuscire. Quale altra causa è possibile?
Controlla gli inode con df -i sullo stesso percorso, perché un filesystem con blocchi liberi ma senza inode disponibili rifiuta la creazione di nuovi file. Verifica se la scrittura viene eseguita da un utente non root su un filesystem ext4 in cui sono rimasti soltanto i blocchi riservati; sudo tune2fs -l eseguito sul dispositivo lo mostrerà. Verifica di leggere il filesystem effettivamente utilizzato dalla scrittura, perché un /boot o un /var separato può riempirsi indipendentemente da /.
Perché du riporta un totale superiore a df?
du senza -x attraversa ogni filesystem montato sotto il percorso indicato, quindi somma più filesystem mentre df descrive un solo filesystem. I bind mount peggiorano il problema, perché gli stessi file vengono conteggiati una volta per ciascun percorso in cui compaiono. Aggiungi -x per mantenere du all'interno di un singolo filesystem e assegna a df lo stesso percorso, in modo che entrambi i comandi descrivano lo stesso contenuto.