Schijf vol volgens df maar niet volgens du
Uw VPS schijf lijkt vol maar du vindt de ruimte niet? Dit komt vaak door verwijderde bestanden die nog openstaan. Gebruik lsof om het proces te vinden en de schijfruimte vrij te maken.
Waarom df aangeeft dat de schijf vol is terwijl du iets anders zegt
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 alleen de naam uit een directory. De datablokken worden pas vrijgegeven wanneer de laatste open bestandsdescriptor die naar die inode wijst, 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 op een standaard Ubuntu VPS met reeds geïnstalleerde tools, spoort het proces dat het bestand vasthoudt op via /proc en maakt de ruimte vrij zonder een reboot. De andere oorzaken voor hetzelfde symptoom volgen hierna: een inodetabel zonder vrije vermeldingen, bestanden die verborgen zijn onder een mountpoint en blokken die gereserveerd zijn voor root.
Voer elk commando uit en lees de uitvoer. De waarden hangen af van uw schijf; vergelijk daarom de situatie voor en na op uw eigen machine in plaats van te vergelijken met de cijfers in deze handleiding.
Wat df telt en wat du telt
df (disk free) vraagt elk aangekoppeld bestandssysteem om zijn 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 pad dat u opgeeft, 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.
-xhoudtduop één bestandssysteem. Zonder deze optie looptdu /elk bestandssysteem binnen dat onder/is aangekoppeld en produceert het een totaal datdf /nooit heeft gemeten.-sprint één samenvattende regel per argument in plaats van één regel per map.
Dat levert het paar op om naast elkaar uit te voeren op het bestandssysteem waar u zich op richt.
df -h /
sudo du -xhs / 2>/dev/nulldf antwoordt onmiddellijk. du duurt minuten op een groot bestandssysteem, omdat het elk bestand onderweg 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.
Het mismatch-scenario bewust 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 wordt bepaald door 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 output wordt de waarde van free. Als deze syntaxis nieuw voor u is, behandelt command substitution in bash dit uitgebreid. fallocate reserveert echte blokken zonder deze 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 werk door de bytes daadwerkelijk te schrijven.
Vergelijk deze df -h . met degene die u heeft vastgelegd. De kolom met gebruikte ruimte 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/nullDe 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 open houdt. $! bevat het proces-ID van die achtergrondtaak. rm verwijdert vervolgens de naam terwijl de descriptor nog open is.
Lees de output. ls kan het bestand niet vinden, omdat de naam is verdwenen. du is weer bijna op het beginpunt, omdat dit namen doorloopt. df is niet veranderd, omdat de blokken nog steeds toegewezen zijn. 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 een verwijderd 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 deze wijst. Het proces-ID is het tweede element van het pad dat wordt geprint. Voer dit 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 | headstat -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 deze 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 aantal toegewezen blokken van de verwijderde inode. Samen beantwoorden ze de vraag die ertoe doet: welke service houdt dit bestand in leven.
Als de machine al over lsof beschikt, geeft sudo lsof +L1 open bestanden weer waarvan het link-aantal tot nul is gedaald en toont het de groottes in één tabel. Dit 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 is de verkeerde eerste stap: het haalt de service offline en vernietigt het bewijsmateriaal. Er zijn vier voorzichtigere opties, in de volgorde waarin u ze kunt proberen.
Kopieer eerst de data als u deze nog wilt behouden. Het lezen van het descriptor-pad leest de actieve inode.
sudo cp /proc/<pid>/fd/<n> /root/recovered.logDit is het enige geval waarin een verwijderd bestand eenvoudig terug te halen is. Daarom begint het herstellen van bestanden verwijderd met rm -rf met de vraag of een proces het bestand nog open heeft staan. Zodra de laatste descriptor sluit, is die weg definitief verloren.
Maak het bestand vervolgens leeg via de descriptor. Het pad /proc 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 probleemloos 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 landt dan ver in het bestand en creëert het opnieuw met een gat aan het begin. Een gat wordt niet gealloceerd, 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 blok-aantal dat daar niet meer bij past. Herstart het proces als u wilt dat de grootte ook weer vanaf nul begint.
Vraag de service ten derde om de logs opnieuw te openen. Een daemon waarvan het logbestand onder zijn voeten is verwijderd, is de meest voorkomende praktijkversie 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, want het verkeerde signaal naar de verkeerde daemon sturen, stopt het proces.
sudo systemctl kill -s USR1 nginxDit stuurt het signaal naar het proces dat systemd registreert als het hoofdproces van de unit. Een unit die het verkeerde Type= voor de manier waarop de daemon daadwerkelijk start declareert, kan uw signaal afleveren bij een proces dat het verwijderde bestand nooit open had, waardoor de ruimte bezet blijft.
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 volstaat.
kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/nullVergelijk 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 is eenvoudiger om die waarde te zien veranderen dan telkens handmatig df uit te voeren. watch herhaalt een commando met een vast interval en print de output telkens op dezelfde plek, zodat watch df -h / laat zien hoe de kolom met gebruikte ruimte verandert naarmate de ruimte terugkeert.
Wanneer de totalen overeenstemmen en de schijf nog steeds vol is
Als df en een root du -x met elkaar overeenstemmen, is er geen sprake van een verwijderd bestand. De resterende oorzaken zijn van een andere aard en vereisen elk een eigen controle.
Geen inodes meer beschikbaar, wel nog 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 toont df -h dat er nog ruimte is.
df -h /
df -i /Het eerste commando telt de blokken en het tweede telt de inodes. Vergelijk de kolom voor het gebruik van beide. Als het blokgebruik laag is en het inode-gebruik zijn limiet heeft bereikt, dan is het probleem een zeer groot aantal zeer kleine bestanden.
df staat -i en --output niet tegelijkertijd toe in dezelfde aanroep. Wanneer u de ruwe aantallen wilt inzien of wilt doorsturen naar een ander commando, selecteer dan de inode-velden bij naam en laat -i weg.
df --output=itotal,iused,iavail,ipcent /Deze kolommen bevatten dezelfde administratie die df -i weergeeft, maar dan in een formaat dat u 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 | headHerhaal hetzelfde commando een niveau dieper in de map die als grootste naar voren kwam, 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 wordt vastgelegd tijdens mkfs. Het verhogen van dit aantal vereist het opnieuw aanmaken van het bestandssysteem en het terugzetten van een back-up. XFS wijst inodes toe wanneer dat nodig is en kent daarom niet op dezelfde manier een vast plafond. Een machine die containers draait, bereikt beide limieten sneller dan de meeste andere systemen, omdat imagelagen veel kleine bestanden bevatten. Op een dergelijke machine is Docker-schijfgebruik opschonen op een VPS de specifieke oplossing; dit levert veel meer ruimte op dan een algemene opschoonactie van het bestandssysteem.
Ruimte verborgen onder een mount point
Een map kan bestanden bevatten voordat er iets op wordt gemount. Mount een bestandssysteem over die map en de bestanden eronder blijven precies waar ze waren: nog steeds toegewezen, nog steeds meegeteld door df, maar niet langer bereikbaar via de bestandsnaam. du kan ze niet zien omdat de mount ze afdekt.
Demonstreer dit 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/coveredDe middelste ls toont een lege map. De kopie is nergens heen gegaan: deze staat nog steeds op het root-bestandssysteem en komt 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-bestandssysteem een tweede keer op een andere locatie. Een bind mount toont één bestandssysteem zonder de bestandssystemen 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/rootcheckAlles 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 de machine kan repareren. Een proces dat als gewone gebruiker draait, loopt als eerste tegen die grens aan, terwijl df nog een kleine hoeveelheid ruimte toont. Controleer 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 in dezelfde eenheden af, waardoor de verhouding tussen beide direct zichtbaar is. df rapporteert de kolom 'available' als de ruimte die een normale gebruiker nog mag gebruiken; daarom is de som van 'used' en 'available' kleiner 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 data-bestandssysteem is redelijk. Laat op het root-bestandssysteem voldoende ruimte over zodat root nog kan schrijven, omdat een root-bestandssysteem zonder enige vrije ruimte veel lastiger te repareren is. Het is ook wat u beschermt tegen buitensluiting: een sleutel die wordt toegevoegd aan authorized_keys op een bestandssysteem zonder vrije ruimte kan onvolledig of helemaal niet worden geschreven, en de volgende inlogpoging reageert met Permission denied (publickey) om een reden die niets met de sleutel zelf te maken heeft. 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:
dutelt een inode één keer, zelfs wanneer meerdere namen ernaar verwijzen. Een boomstructuur vol hard links rapporteert daarom minder dan de som van de individuele bestanden. - Sparse files:
durapporteert de werkelijk toegewezen blokken, terwijlls -lde schijnbare grootte rapporteert. Voeg--apparent-sizetoe om het andere getal te zien. - Rechten: wanneer u het commando als gewone gebruiker uitvoert, slaat
duover wat het niet kan lezen en rapporteert het een te lage waarde. De foutmeldingen die het afdrukt, zijn de meldingen die mensen naar/dev/nullomleiden en vervolgens negeren. - Bestandssysteemgrenzen: zonder
-xteltdu /elk bestandssysteem dat onder/is aangekoppeld. Het totaal kan daardoor hoger uitvallen dan watdf /rapporteert.
df heeft ook één gewoonte die het waard is om te kennen. Het rapporteert elk bestandssysteem afzonderlijk. Voer het daarom uit op het exacte pad waar de mislukte schrijfactie plaatsvindt. Een afzonderlijke /boot raakt volgens een eigen schema vol naarmate kernelpakketten zich opstapelen, en oude kernels verwijderen op Ubuntu is een andere taak dan ruimte vrijmaken op /.
Een werkvolgorde voor een incident
- Voer
df -h <path>endf -i <path>uit op het bestandssysteem waar de mislukte schrijfactie op gericht was, niet uit reflex op/. - Voer
sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -huit en navigeer vervolgens naar de grootste map. - Als
duniet kan verklaren watdfrapporteert als gebruikt, doorzoek/procdan op verwijderde bestanden die nog steeds geopend zijn. - Als beide overeenkomen, koppel het bestandssysteem dan elders aan (bind mount) en zoek naar bestanden die onder een aankoppelpunt staan.
- Als het inode-gebruik de limiet 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 gissen.
FAQ
Waarom geeft df aan dat de schijf vol is, terwijl du veel minder verbruik ziet?
De meest voorkomende 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 stopt met tellen. 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 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 stilletjes 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 toont elke descriptor 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 kiezen. Hiervoor is geen pakket nodig; dit is essentieel omdat installatie op een bestandssysteem zonder vrije ruimte kan mislukken.
Kan ik de ruimte vrijmaken zonder het proces te beëindigen?
Soms wel. 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 naar het huidige einde gaan. Als dat 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 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; dit kunt u zien met sudo tune2fs -l op het apparaat. 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 eenmaal worden geteld onder elk pad waar ze verschijnen. Voeg -x toe om du op één bestandssysteem te houden en geef df hetzelfde pad mee, zodat beide commando's hetzelfde beschrijven.