SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor

Schijf vol volgens df maar niet volgens du: oplossing

Geeft df aan dat uw schijf vol is terwijl du geen ruimtegebruik toont? Dit komt vaak door openstaande bestandsdescriptors van verwijderde bestanden. Leer hoe u het proces vindt.

Waarom df aangeeft dat de schijf vol is terwijl du iets anders meldt

df rapporteert dat de schijf vol is, terwijl du de ruimte niet kan vinden omdat een proces nog steeds een verwijderd bestand open heeft staan. Het verwijderen van een bestand verwijdert enkel de naam uit een map. De datablokken worden pas vrijgegeven wanneer de laatste open bestandsdescriptor die naar die inode verwijst, wordt gesloten. du doorloopt bestandsnamen en telt het bestand daarom niet mee. df vraagt het bestandssysteem hoeveel blokken er zijn toegewezen, waardoor het bestand dat geen naam meer heeft nog steeds wordt meegeteld.

Deze handleiding reproduceert dit scenario op een standaard Ubuntu VPS met reeds geïnstalleerde tools, identificeert het proces dat het bestand vasthoudt via /proc en maakt de ruimte vrij zonder een herstart. De overige oorzaken voor hetzelfde symptoom volgen hierna: een inode-tabel zonder vrije vermeldingen, bestanden die verborgen zijn onder een mount point en blokken die zijn gereserveerd voor root.

Voer elk commando uit en lees uw eigen uitvoer. De waarden zijn afhankelijk van uw schijf; vergelijk daarom de situatie voor en na op uw eigen machine in plaats van te kijken naar de getallen in deze handleiding.

Wat df telt en wat du telt

df (disk free) vraagt elk aangekoppeld bestandssysteem om de eigen administratie: hoeveel blokken er bestaan, hoeveel er zijn toegewezen en hoeveel er vrij zijn. Het opent nooit een map. Het antwoord omvat elk toegewezen blok, inclusief blokken die behoren bij een bestand waar geen enkel mapitem naar verwijst.

du (disk usage) doet het tegenovergestelde. Het begint bij een opgegeven pad, leest mappen, voert een stat uit op elk item dat het vindt en telt de blokken bij elkaar op. Een bestand zonder naam is onzichtbaar voor dit commando. Dat geldt ook voor elke map die het niet mag lezen; daarom krijgt een gewone gebruiker een lager totaal dan root. Voer du uit onder sudo voordat u conclusies trekt uit de vergelijking.

Twee opties zijn altijd van belang wanneer u beide vergelijkt.

  • -x houdt du op één bestandssysteem. Zonder deze optie loopt du / elk bestandssysteem binnen dat onder / is aangekoppeld en produceert het een totaal dat df / nooit heeft gemeten.
  • -s print één samenvattende regel per argument in plaats van één regel per map.

Dit levert het paar op dat u naast elkaar kunt uitvoeren op het bestandssysteem dat u wilt onderzoeken.

df -h /
sudo du -xhs / 2>/dev/null

df geeft direct antwoord. du duurt minuten op een groot bestandssysteem, omdat het onderweg elk bestand moet controleren. Wanneer de twee totalen ver uit elkaar liggen en du is uitgevoerd als root met -x, dan is de ontbrekende ruimte toegewezen aan iets dat geen naam heeft.

De mismatch opzettelijk reproduceren

Voer dit uit op een test-VPS. Alles hieronder is bash en coreutils, dus er wordt niets geïnstalleerd.

Leg de begintoestand vast van het bestandssysteem dat /var/tmp bevat.

cd /var/tmp
df -h .
df --output=used -B1 .

Het tweede commando toont het aantal gebruikte bytes zonder afronding, wat de controle aan het einde exact maakt.

Maak nu een bestand aan. De grootte is gebaseerd op de vrije ruimte die de machine zelf rapporteert, zodat de demonstratie werkt op elke schijf.

free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .

$(...) is command substitution: de shell voert het commando daarbinnen uit en de uitvoer wordt de waarde van free. Als deze syntaxis nieuw voor u is, behandelt command substitution in bash dit correct. fallocate reserveert echte blokken zonder ze te beschrijven, waardoor het direct klaar is. Op een bestandssysteem dat dit niet ondersteunt, faalt het commando en doet head -c $((free / 10)) /dev/zero > ghost.bin hetzelfde door de bytes daadwerkelijk te schrijven.

Vergelijk deze df -h . met degene die u heeft vastgelegd. De kolom met gebruikt geheugen is gegroeid en de kolom met beschikbare ruimte is gekrompen.

Houd het bestand nu open vanuit een ander proces en verwijder het vervolgens.

sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/null

De redirect is de hele truc. sleep infinity < ghost.bin & start een achtergrondproces waarvan de standaardinvoer dat bestand is, dus de shell opent het bestand en geeft de descriptor aan sleep, die het openhoudt. $! bevat het proces-ID van die achtergrondtaak. rm verwijdert vervolgens de naam terwijl de descriptor nog open is.

Lees de uitvoer. ls kan het bestand niet vinden, omdat de naam weg is. du is weer bijna op het beginpunt, omdat dit commando namen doorloopt. df is niet veranderd, omdat de blokken nog steeds zijn toegewezen. Het bestandssysteem en de mappenstructuur spreken elkaar nu tegen, en het gat daartussen is het bestand dat u zojuist heeft verwijderd.

Het proces vinden dat het verwijderde bestand vasthoudt

Elke open bestandsdescriptor verschijnt onder /proc/<pid>/fd/ als een symbolische link naar het bestand waarnaar het verwijst. Wanneer het bestand is ontkoppeld (unlinked), markeert de kernel het doel van die link als verwijderd. Het vinden van de houder betekent dus het vinden van een link waarvan het doel die markering draagt.

sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null

-lname komt overeen met het doel van een symbolische link in plaats van de naam, %p print het pad van de descriptor en %l print waarnaar het verwijst. Het proces-ID is het tweede element van het pad dat het print. Voer het uit met sudo, omdat u anders alleen /proc/<pid>/fd voor uw eigen processen kunt lezen. De stderr-omleiding verwijdert de ruis van processen die afsluiten terwijl find de mappen doorloopt.

Een drukke server houdt op elk moment verschillende verwijderde bestanden vast, en de meeste daarvan zijn klein en onschadelijk. Sorteer ze op grootte zodat alleen de interessante bestanden bovenaan blijven staan.

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 | head

stat -L volgt de link naar de inode zelf, dus %s rapporteert de grootte van het bestand dat geen naam meer heeft. Sorteren op dat getal plaatst het grootste bestand bovenaan.

Identificeer vervolgens het proces achter de winnende descriptor. Het pad bovenaan die lijst bevat beide getallen die u nodig heeft, dus plaats ze eerst in variabelen en vervang PID en N door wat uw eigen lijst heeft geprint.

pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"

ps benoemt het programma en toont hoe lang het al draait. stat -L print de grootte en het toegewezen aantal blokken van de verwijderde inode. Samen beantwoorden ze de vraag die ertoe doet: welke service houdt dit bestand in leven.

Als de machine al lsof heeft, geeft sudo lsof +L1 open bestanden weer waarvan het link-aantal tot nul is gedaald en toont het de groottes in één tabel. Het is niet aanwezig op een minimale Ubuntu-image, en het installeren van een pakket op een bestandssysteem zonder vrije ruimte kan zelf mislukken, dus de /proc-methode is de versie die altijd werkt.

Ruimte vrijmaken zonder herstart

Een herstart lost het probleem op, maar het is de verkeerde eerste stap: het haalt de service offline en vernietigt het bewijsmateriaal. Er bestaan vier voorzichtigere opties, in de volgorde waarin u ze moet proberen.

Kopieer eerst de gegevens als u ze nog wilt behouden. Het lezen van het descriptor-pad leest de actieve inode.

sudo cp /proc/<pid>/fd/<n> /root/recovered.log

Dit is het enige geval waarin een verwijderd bestand eenvoudig terug te halen is. Daarom begint bestanden herstellen die met rm -rf zijn verwijderd met de vraag of een proces het bestand nog open heeft staan. Zodra de laatste descriptor sluit, is die weg afgesloten.

Maak ten tweede het bestand leeg via de descriptor. Het /proc-pad leidt naar dezelfde inode, dus het trunkeren ervan geeft de blokken vrij terwijl het proces blijft draaien.

sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /

Dit werkt correct wanneer de schrijver het bestand in append-modus heeft geopend, omdat elke schrijfactie dan naar het huidige einde van het bestand gaat. Wanneer dit niet het geval is, behoudt het proces zijn oude schrijf-offset. De volgende schrijfactie komt dan ver in het bestand terecht en creëert het opnieuw met een gat aan het begin. Een gat wordt niet toegewezen, dus de blokken blijven vrij en df behoudt de ruimte die zojuist is vrijgekomen. Wat terugkeert is enkel de bestandsgrootte: voer sudo stat -L "/proc/$pid/fd/$n" opnieuw uit nadat het proces heeft geschreven, en het rapporteert de oude grootte naast een bloktelling die er niet meer mee overeenkomt. Herstart het proces als u wilt dat de grootte ook weer vanaf nul begint.

Vraag ten derde aan de service om de logbestanden opnieuw te openen. Een daemon waarvan het logbestand onder zijn voeten is verwijderd, is de meest voorkomende versie van dit probleem. Veel daemons openen hun logbestanden opnieuw bij ontvangst van een signaal: nginx gebruikt SIGUSR1 en rsyslog gebruikt SIGHUP. Raadpleeg de documentatie van de betreffende daemon in plaats van te gokken, omdat het verkeerde signaal naar de verkeerde daemon sturen deze kan stoppen.

sudo systemctl kill -s USR1 nginx

Herstart ten vierde de unit. sudo systemctl restart <unit> sluit elke descriptor die het oude proces vasthield, waardoor de blokken met zekerheid terugkeren. Voor het bovenstaande voorbeeld is de houder een sleep die u zelf heeft gestart, dus het beëindigen ervan is voldoende.

kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null

Vergelijk het aantal gebruikte bytes met de waarde die u noteerde voordat u het bestand aanmaakte. Ze komen weer overeen en find rapporteert uw descriptor niet langer. Het is een goede gewoonte om te verifiëren met hetzelfde commando dat het probleem aan het licht bracht.

Het verloop van die waarde in de gaten houden is eenvoudiger dan handmatig steeds opnieuw df uitvoeren. watch herhaalt een commando met een vast interval en print de output op dezelfde plek, dus watch df -h / toont de kolom met gebruikte ruimte terwijl deze terugkeert.

Wanneer de totalen overeenstemmen en de schijf toch vol is

Als df en een root du -x met elkaar overeenstemmen, is er geen sprake van een verwijderd bestand. De overige oorzaken zijn van een andere aard en vereisen elk een eigen controle.

Geen inodes meer beschikbaar, maar wel schijfruimte

Een inode bevat de metadata van één bestand. ext4 maakt een vast aantal inodes aan bij het formatteren van het bestandssysteem. Hierdoor kan een bestandssysteem zonder inodes komen te zitten terwijl er nog vrije blokken beschikbaar zijn. Het aanmaken van nieuwe bestanden mislukt dan, ook al geeft df -h aan dat er nog ruimte is.

df -h /
df -i /

Het eerste commando telt de blokken en het tweede de inodes. Vergelijk de kolom voor het gebruik van beide. Een laag blokgebruik in combinatie met een maximaal inode-gebruik wijst op een zeer groot aantal zeer kleine bestanden.

df staat -i en --output niet tegelijkertijd toe in dezelfde aanroep. Als u de ruwe aantallen wilt inzien of wilt doorgeven aan een ander commando, selecteer dan de inode-velden bij naam en laat -i weg.

df --output=itotal,iused,iavail,ipcent /

Deze kolommen bevatten dezelfde gegevens als df -i, maar in een formaat dat u eenvoudig kunt verwerken.

Zoek de bestanden door het aantal items te tellen in plaats van de bytes.

sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | head

Herhaal hetzelfde commando één niveau dieper in de map die als grootste naar voren komt, totdat u de boomstructuur vindt die de bestanden aanmaakt. Als uw du geen --inodes ondersteunt, dan telt sudo find /var -xdev -type f | wc -l een subboom op de langzame manier.

De oplossing is het verwijderen of verplaatsen van deze bestanden. U kunt geen inodes toevoegen aan een bestaand ext4-bestandssysteem, omdat het aantal vastligt vanaf het moment van mkfs. Het verhogen van dit aantal vereist het opnieuw aanmaken van het bestandssysteem en het terugzetten van een back-up. XFS wijst inodes toe naar behoefte en kent daarom niet op dezelfde wijze een vast plafond. Een machine die containers draait, bereikt beide limieten sneller dan gemiddeld, omdat imagelagen veel kleine bestanden bevatten. Op een dergelijke machine is het opschonen van Docker-schijfgebruik op een VPS de specifieke oplossing; dit levert aanzienlijk meer ruimte op dan een algemene opschoonactie van het bestandssysteem.

Ruimte verborgen onder een mount point

Een directory kan bestanden bevatten voordat er iets op wordt gemount. Mount een filesystem over die directory en de bestanden eronder blijven precies waar ze waren: ze blijven gealloceerd, worden nog steeds meegeteld door df, maar zijn niet langer bereikbaar via hun naam. du kan ze niet zien omdat de mount ze afdekt.

Toon dit aan met tmpfs, waarvoor geen extra schijfruimte nodig is. Dit onderdeel vereist een machine waarop u mag mounten, dus het werkt op een 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/covered

De middelste ls toont een lege directory. De kopie is nergens heen gegaan: deze staat nog steeds op het root-filesystem en komt direct terug zodra u de mount verwijdert. Stel u nu een service voor die een maand lang naar dat pad heeft gelogd voordat iemand er een volume overheen mountte.

Om de werkelijke bestanden op een draaiende server te vinden, mount u het root-filesystem een tweede keer op een andere locatie. Een bind mount toont één filesystem zonder de filesystems die erin zijn gemount.

sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheck

Alles wat in die lijst verschijnt maar niet onder het normale pad, ligt begraven onder een mount point. Unmount de bind mount wanneer u klaar bent, anders telt een latere du zonder -x dezelfde bestanden twee keer.

Gereserveerde blokken voor root

ext4 reserveert een deel van de blokken voor de root-gebruiker, zodat een volle schijf niet voorkomt dat root kan inloggen en het systeem kan herstellen. Een proces dat onder een gewone gebruiker draait, loopt als eerste tegen die grens aan, terwijl df nog een kleine hoeveelheid ruimte toont. Lees de instelling op uw eigen bestandssysteem in plaats van uit te gaan van de standaardwaarde.

dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'

Dit drukt het totale aantal blokken en het aantal gereserveerde blokken uit in dezelfde eenheden, waardoor de verhouding tussen beide direct zichtbaar is. df rapporteert de kolom 'available' als de ruimte die een normale gebruiker nog mag gebruiken; dit is de reden waarom 'used' plus 'available' kleiner uitvalt dan de totale grootte. Het verschil is de reserve.

Wijzig dit met sudo tune2fs -m <percent> "$dev". De wijziging is direct van kracht en vereist geen remount. Het verlagen van de reserve op een apart databestandssysteem is redelijk. Laat op het root-bestandssysteem voldoende ruimte over zodat root nog kan schrijven, omdat een root-bestandssysteem dat volledig vol is, veel lastiger te herstellen is. tune2fs werkt op ext2, ext3 en ext4. XFS heeft geen equivalente instelling.

Waar du u op het verkeerde been zet

Vier gewoonten van du produceren totalen die onjuist lijken.

  • Hard links: du telt een inode slechts één keer, zelfs als er meerdere namen naar verwijzen. Een map vol hard links rapporteert daarom minder dan de som van de afzonderlijke bestanden.
  • Sparse files: du rapporteert de daadwerkelijk toegewezen blokken, terwijl ls -l de schijnbare grootte rapporteert. Voeg --apparent-size toe om het andere getal te zien.
  • Rechten: als u het commando als een gewone gebruiker uitvoert, slaat du alles over wat het niet kan lezen, wat leidt tot een te lage rapportage. De foutmeldingen die het programma afdrukt, zijn precies de meldingen die mensen naar /dev/null omleiden en vervolgens niet meer lezen.
  • Bestandssysteemgrenzen: zonder -x telt du / elk bestandssysteem dat onder / is aangekoppeld. Het totaal kan daardoor hoger uitvallen dan wat df / rapporteert.

df heeft ook één gewoonte die het waard is om te kennen. Het rapporteert elk bestandssysteem afzonderlijk, dus voer het uit op het exacte pad waar de mislukte schrijfactie plaatsvindt. Een afzonderlijke /boot raakt op zijn eigen moment vol naarmate kernelpakketten zich opstapelen, en oude kernels verwijderen op Ubuntu is een andere taak dan ruimte vrijmaken op /.

Een werkvolgorde voor een daadwerkelijk incident

  1. Voer df -h <path> en df -i <path> uit op het bestandssysteem waar de mislukte schrijfactie op gericht was, en niet uit reflex op /.
  2. Voer sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -h uit en navigeer vervolgens naar de grootste map.
  3. Als du niet kan verklaren wat df rapporteert als gebruikt, doorzoek /proc dan op verwijderde bestanden die nog steeds geopend zijn.
  4. Als beide overeenkomen, voer dan een bind mount uit van het bestandssysteem op een andere locatie en zoek naar bestanden die onder een mount point staan.
  5. Als het inode-gebruik de limiet heeft bereikt, tel dan het aantal bestanden in plaats van de bytes.

Elke stap bevat een commando waarvan u de uitvoer kunt lezen. Dat is het verschil tussen dit probleem oplossen en ernaar gissen.

FAQ

Waarom geeft df aan dat de schijf vol is terwijl du veel minder verbruik rapporteert?

De gebruikelijke oorzaak is een bestand dat is verwijderd terwijl een proces het nog open had staan. Het verwijderen van het bestand verwijdert de directory-vermelding, waardoor du geen naam meer heeft om te volgen en het bestand niet langer meetelt. De inode en de bijbehorende blokken blijven toegewezen totdat de laatste descriptor wordt gesloten, en df telt alle toegewezen blokken. Doorzoek /proc/<pid>/fd naar symbolische links waarvan het doel als verwijderd is gemarkeerd; zo vindt u zowel het bestand als het proces dat het vasthoudt. Voordat u op de vergelijking vertrouwt, moet u bevestigen dat u du als root en met -x heeft uitgevoerd, omdat een gewone gebruiker mappen die niet leesbaar zijn geruisloos overslaat.

Hoe vind ik een verwijderd bestand dat nog open staat zonder lsof?

Gebruik het eigen overzicht van open descriptoren van de kernel. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null somt elke descriptor op die wijst naar een bestand zonder naam, en het proces-ID staat in het pad dat wordt weergegeven. sudo stat -Lc %s op een van die descriptorpaden rapporteert de grootte, zodat u ze kunt sorteren en de relevante kunt selecteren. Hiervoor is geen enkel pakket vereist, wat van belang is omdat installatie op een bestandssysteem zonder vrije ruimte kan mislukken.

Kan ik de ruimte vrijmaken zonder het proces te beëindigen?

Soms. sudo truncate -s 0 /proc/<pid>/fd/<n> bereikt dezelfde inode via de descriptor en geeft de blokken vrij terwijl het proces blijft draaien. Dit is het meest effectief wanneer het proces het bestand in append-modus heeft geopend, omdat schrijfacties altijd aan het huidige einde plaatsvinden. Als dit niet het geval is, blijft de schrijf-offset staan waar deze was en creëert de volgende schrijfactie het bestand opnieuw met een gat aan het begin. Hierdoor springt de gerapporteerde grootte terug, terwijl de blokken onder het gat vrij blijven. Het herstarten van de unit, of het sturen van een signaal om de logs opnieuw te openen volgens de documentatie van de applicatie, is de oplossing die geen sparse file achterlaat.

df toont vrije ruimte maar schrijfacties mislukken toch. Wat kan er nog meer aan de hand zijn?

Controleer het aantal inodes met df -i op hetzelfde pad, aangezien een bestandssysteem met vrije blokken maar zonder vrije inodes geen nieuwe bestanden accepteert. Controleer of de schrijfactie wordt uitgevoerd door een niet-rootgebruiker op een ext4-bestandssysteem waar alleen de gereserveerde blokken over zijn, wat sudo tune2fs -l op het apparaat zal aantonen. Controleer of u het bestandssysteem leest waarop de schrijfactie daadwerkelijk plaatsvindt, omdat een afzonderlijke /boot of /var onafhankelijk van / volloopt.

Waarom rapporteert du een groter totaal dan df?

du zonder -x doorkruist elk bestandssysteem dat onder het opgegeven pad is aangekoppeld, waardoor het meerdere bestandssystemen optelt terwijl df er slechts één beschrijft. Bind mounts verergeren dit, omdat dezelfde bestanden worden meegeteld voor elk pad waaronder ze verschijnen. Voeg -x toe om du op één bestandssysteem te houden en geef df hetzelfde pad mee, zodat beide commando's exact hetzelfde beschrijven.