df Penuh, tetapi du Tidak: Temukan Ruang yang Hilang
Pelajari mengapa df penuh tetapi du berbeda, temukan file terhapus yang masih dibuka proses, lalu bebaskan ruang tanpa reboot di VPS Ubuntu.
Mengapa df melaporkan disk penuh, sedangkan du menunjukkan hasil berbeda
df melaporkan disk penuh, sedangkan du tidak dapat menemukan penggunaan ruang tersebut karena sebuah proses masih menahan file yang sudah dihapus. Menghapus file akan menghapus namanya dari direktori. Blok data baru dilepaskan setelah deskriptor file terakhir yang menunjuk ke inode tersebut ditutup. du menelusuri nama file, sehingga tidak menghitung apa pun. df meminta informasi kepada filesystem tentang jumlah blok yang dialokasikan, sehingga file yang tidak lagi memiliki nama tetap dihitung.
Panduan ini mereproduksi kondisi tersebut pada VPS Ubuntu biasa menggunakan tool yang sudah terpasang, menemukan proses yang menahan file melalui /proc, dan membebaskan ruang tanpa reboot. Penyebab lain dengan gejala yang sama akan dibahas setelahnya: tabel inode yang tidak memiliki entri kosong, file yang tersembunyi di bawah mount point, dan blok yang dicadangkan untuk root.
Jalankan setiap command dan baca output Anda sendiri. Nilainya bergantung pada disk Anda. Karena itu, bandingkan kondisi sebelum dan sesudah pada mesin Anda sendiri, bukan dengan angka yang tercetak dalam panduan.
Yang dihitung oleh df dan yang dihitung oleh du
df (disk free) meminta setiap filesystem yang di-mount untuk memberikan perhitungannya sendiri: berapa banyak blok yang tersedia, berapa banyak yang dialokasikan, dan berapa banyak yang kosong. Perintah ini tidak pernah membuka direktori. Hasilnya mencakup setiap blok yang dialokasikan, termasuk blok milik file yang tidak ditunjuk oleh entri direktori mana pun.
du (disk usage) melakukan hal sebaliknya. Perintah ini mulai dari path yang Anda berikan, membaca direktori, mengambil statistik setiap entri yang ditemukan, lalu menjumlahkan bloknya. File tanpa nama tidak terlihat olehnya. Demikian juga direktori yang tidak boleh dibacanya. Karena itu, pengguna biasa mendapatkan total yang lebih kecil daripada root. Jalankan du dengan sudo sebelum menarik kesimpulan apa pun dari perbandingan tersebut.
Dua opsi selalu penting saat Anda membandingkan keduanya.
-xmembuatdutetap berada pada satu filesystem. Tanpa opsi ini,du /akan memasuki setiap filesystem yang di-mount di bawah/dan menghasilkan total yang tidak pernah diukur olehdf /.-smencetak satu baris ringkasan untuk setiap argumen, bukan satu baris untuk setiap direktori.
Dengan demikian, Anda dapat menjalankan pasangan perintah berikut secara berdampingan pada filesystem yang ingin diperiksa.
df -h /
sudo du -xhs / 2>/dev/nulldf memberikan hasil segera. du memerlukan waktu beberapa menit pada filesystem besar karena perintah ini mengambil statistik setiap file yang ditemuinya. Jika kedua total tersebut sangat berbeda, dan du dijalankan sebagai root dengan -x, ruang yang hilang dialokasikan untuk sesuatu yang tidak memiliki nama.
Sengaja membuat ketidaksesuaian
Lakukan ini pada VPS pengujian. Semua perintah di bawah menggunakan bash dan coreutils, sehingga tidak ada yang perlu diinstal.
Catat keadaan awal filesystem yang menyimpan /var/tmp.
cd /var/tmp
df -h .
df --output=used -B1 .Perintah kedua mencetak byte yang digunakan tanpa pembulatan. Dengan demikian, pemeriksaan pada akhir proses menjadi akurat.
Sekarang buat sebuah file. Ukurannya berasal dari ruang kosong yang dilaporkan oleh mesin itu sendiri, sehingga demonstrasi ini menyesuaikan dengan disk apa pun yang Anda gunakan.
free=$(df --output=avail -B1 . | tail -n 1)
fallocate -l $((free / 10)) ghost.bin
ls -l ghost.bin
df -h .$(...) adalah substitusi perintah: shell menjalankan perintah di dalamnya, lalu output-nya menjadi nilai free. Jika sintaks ini masih baru bagi Anda, substitusi perintah di bash menjelaskannya dengan tepat. fallocate mencadangkan blok nyata tanpa menulis data ke dalamnya, sehingga prosesnya selesai seketika. Pada filesystem yang tidak mendukungnya, perintah tersebut gagal. head -c $((free / 10)) /dev/zero > ghost.bin menjalankan fungsi yang sama dengan menulis byte secara langsung.
Bandingkan df -h . ini dengan hasil yang Anda catat. Kolom yang digunakan bertambah, sedangkan kolom yang tersedia berkurang.
Sekarang buka file tersebut dari proses lain, lalu hapus file itu.
sleep infinity < ghost.bin &
holder=$!
rm ghost.bin
ls -l ghost.bin
df -h .
sudo du -xhs . 2>/dev/nullRedirection adalah inti dari trik ini. sleep infinity < ghost.bin & memulai proses latar belakang yang standard input-nya berasal dari file tersebut. Dengan demikian, shell membuka file dan menyerahkan file descriptor kepada sleep, yang menjaganya tetap terbuka. $! menyimpan process ID dari pekerjaan latar belakang tersebut. rm kemudian menghapus nama file itu, sementara file descriptor masih terbuka.
Baca output-nya. ls tidak dapat menemukan file tersebut karena namanya sudah dihapus. du kembali mendekati kondisi awal karena perintah itu menelusuri nama file. df tidak berubah karena bloknya masih dialokasikan. Filesystem dan pohon direktori kini tidak lagi sesuai, dan selisih di antara keduanya adalah file yang baru saja Anda hapus.
Menemukan proses yang menahan file yang telah dihapus
Setiap file descriptor yang terbuka muncul di bawah /proc/<pid>/fd/ sebagai symbolic link ke file yang dirujuknya. Setelah file di-unlink, kernel menandai target link tersebut sebagai deleted. Jadi, untuk menemukan proses yang menahannya, cari link yang targetnya memuat penanda tersebut.
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null-lname mencocokkan target symbolic link, bukan namanya, %p menampilkan path descriptor, dan %l menampilkan targetnya. process ID merupakan elemen kedua dari path yang ditampilkan. Jalankan perintah ini dengan sudo karena tanpa opsi tersebut Anda hanya dapat membaca /proc/<pid>/fd untuk proses milik Anda sendiri. Redirect stderr membuang pesan dari proses yang keluar saat find melakukan penelusuran.
Server yang sibuk dapat menahan beberapa file yang telah dihapus pada saat yang sama. Sebagian besar file tersebut kecil dan tidak berbahaya. Urutkan berdasarkan ukuran agar hanya file yang menarik yang berada di bagian atas.
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 mengikuti link hingga ke inode itu sendiri, sehingga %s melaporkan ukuran file yang tidak lagi memiliki nama. Pengurutan berdasarkan angka tersebut menempatkan file terbesar di urutan pertama.
Selanjutnya, identifikasi proses di balik descriptor tersebut. Path di bagian atas daftar itu memuat kedua angka yang Anda perlukan. Masukkan angka tersebut ke dalam variabel terlebih dahulu dengan mengganti PID dan N sesuai hasil listing Anda.
pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"ps menampilkan nama program dan durasi program tersebut telah berjalan. stat -L menampilkan ukuran serta jumlah block yang dialokasikan untuk inode yang telah dihapus. Keduanya menjawab pertanyaan utama: service mana yang masih mempertahankan file ini.
Jika mesin sudah memiliki lsof, sudo lsof +L1 mencantumkan file terbuka yang jumlah link-nya telah turun menjadi nol dan menampilkan ukurannya dalam satu tabel. Perintah tersebut tidak tersedia pada image Ubuntu minimal. Selain itu, menginstal package pada filesystem tanpa ruang kosong juga dapat gagal. Karena itu, penelusuran dengan /proc adalah versi yang selalu dapat digunakan.
Kosongkan ruang tanpa reboot
Reboot memang dapat memperbaikinya, tetapi itu bukan langkah pertama yang tepat: service akan berhenti dan bukti masalah akan hilang. Ada empat opsi yang lebih aman, dalam urutan berikut.
Pertama, salin datanya jika masih diperlukan. Membaca path descriptor akan membaca inode yang masih aktif.
sudo cp /proc/<pid>/fd/<n> /root/recovered.logIni satu-satunya kondisi ketika file yang sudah dihapus mudah dipulihkan. Karena itu, memulihkan file yang dihapus dengan rm -rf dimulai dengan memeriksa apakah suatu proses masih membuka file tersebut. Setelah descriptor terakhir ditutup, cara ini tidak dapat digunakan lagi.
Kedua, kosongkan file melalui descriptor tersebut. Path /proc mengarah ke inode yang sama, sehingga proses truncation membebaskan blok tanpa menghentikan proses.
sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /Cara ini bekerja dengan baik ketika writer membuka file dalam mode append, karena setiap penulisan diarahkan ke akhir file saat ini. Jika tidak, proses akan mempertahankan offset penulisan lama. Penulisan berikutnya akan berada jauh di dalam file dan membuatnya kembali dengan hole di bagian awal. Hole tidak menggunakan alokasi, sehingga blok tetap bebas dan df mempertahankan ruang yang baru saja dikembalikannya. Yang berubah kembali hanya ukurannya: jalankan sudo stat -L "/proc/$pid/fd/$n" lagi setelah proses menulis, dan perintah tersebut akan melaporkan ukuran lama bersama jumlah blok yang tidak lagi sesuai. Restart proses jika ukuran juga harus dimulai dari nol.
Ketiga, minta service membuka ulang log-nya. Daemon yang file log-nya dihapus saat masih dibuka adalah bentuk umum masalah ini. Banyak daemon membuka ulang file log setelah menerima signal: nginx menggunakan SIGUSR1, sedangkan rsyslog menggunakan SIGHUP. Periksa dokumentasi daemon yang sedang digunakan, jangan menebak. Signal yang salah untuk daemon yang salah dapat menghentikannya.
sudo systemctl kill -s USR1 nginxKeempat, restart unit tersebut. sudo systemctl restart <unit> menutup semua descriptor yang masih dibuka proses lama, sehingga blok pasti kembali tersedia. Dalam demonstrasi di atas, pemegangnya adalah sleep yang Anda jalankan sendiri, sehingga menghentikannya sudah cukup.
kill $holder
df -h .
df --output=used -B1 .
sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/nullBandingkan byte yang digunakan dengan nilai yang Anda catat sebelum membuat file. Nilainya kembali sama, dan find tidak lagi melaporkan descriptor Anda. Verifikasi menggunakan perintah yang sama dengan perintah yang menemukan masalah adalah kebiasaan yang perlu dipertahankan.
Memantau perubahan nilai tersebut lebih mudah daripada menjalankan df berulang kali secara manual. watch mengulangi perintah pada interval tetap dan mencetak ulang output di tempat yang sama, sehingga watch df -h / menampilkan perubahan kolom penggunaan saat ruang kembali tersedia.
Jika totalnya sesuai dan disk masih penuh
Jika df dan du -x root memberikan hasil yang sama, tidak ada file terhapus yang terlibat. Penyebab yang tersisa berbeda jenis, dan masing-masing memiliki pemeriksaan sendiri.
Kehabisan inode, bukan blok
Sebuah inode menyimpan metadata untuk satu file. ext4 membuat jumlah inode yang tetap saat filesystem dibuat. Karena itu, filesystem dapat kehabisan inode meskipun masih memiliki blok yang tersedia. Pembuatan file baru kemudian gagal meskipun df -h menunjukkan masih ada ruang.
df -h /
df -i /Perintah pertama menghitung blok dan perintah kedua menghitung inode. Bandingkan kolom penggunaan pada keduanya. Jika penggunaan blok masih rendah, tetapi penggunaan inode sudah mencapai batas, masalahnya adalah jumlah file kecil yang sangat banyak.
df menolak -i dan --output dalam pemanggilan yang sama. Jadi, jika Anda ingin membaca jumlah mentah atau meneruskannya ke perintah lain, pilih field inode berdasarkan nama dan jangan gunakan -i.
df --output=itotal,iused,iavail,ipcent /Kolom tersebut memuat informasi akuntansi yang sama seperti yang dicetak df -i, dalam format yang dapat diuraikan.
Temukan file dengan menghitung entri, bukan byte.
sudo du --inodes -x -d 1 / 2>/dev/null | sort -rn | headUlangi perintah yang sama satu tingkat di bawah direktori yang memiliki jumlah tertinggi, sampai Anda mencapai bagian tree yang membuat file-file tersebut. Jika du Anda tidak mendukung --inodes, gunakan sudo find /var -xdev -type f | wc -l untuk menghitung subtree dengan cara yang lebih lambat.
Solusinya adalah menghapus atau memindahkan file-file tersebut. Anda tidak dapat menambahkan inode ke filesystem ext4 yang sudah ada, karena jumlahnya ditetapkan saat mkfs. Untuk menambah jumlah inode, filesystem harus dibuat ulang dan data dipulihkan dari backup. XFS mengalokasikan inode sesuai kebutuhan, sehingga tidak mencapai batas tetap dengan cara yang sama. Mesin yang menjalankan container mencapai kedua batas tersebut lebih cepat daripada kebanyakan mesin lain karena image layer menyimpan banyak file kecil. Pada mesin tersebut, membersihkan penggunaan disk Docker pada VPS adalah solusi khususnya, dan cara ini mengosongkan ruang jauh lebih banyak daripada pemindaian umum pada filesystem.
Ruang tersembunyi di bawah titik mount
Sebuah direktori dapat berisi file sebelum apa pun di-mount pada direktori tersebut. Mount sebuah filesystem di atas direktori itu, dan file di bawahnya tetap berada tepat di tempat semula: tetap dialokasikan, tetap dihitung oleh df, tetapi tidak lagi dapat diakses berdasarkan namanya. du tidak dapat melihatnya karena mount menutupinya.
Tunjukkan hal ini dengan tmpfs, yang tidak memerlukan disk tambahan. Bagian ini memerlukan mesin yang mengizinkan Anda melakukan mount, sehingga dapat dijalankan pada 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/coveredBagian tengah ls menampilkan direktori kosong. File yang disalin tidak berpindah ke mana pun: file tersebut masih berada di root filesystem dan akan muncul kembali saat Anda melakukan unmount. Sekarang bayangkan sebuah service yang menulis log ke path tersebut selama satu bulan sebelum seseorang me-mount sebuah volume di atasnya.
Untuk menemukan file sebenarnya pada server yang sedang berjalan, mount root filesystem untuk kedua kalinya di lokasi lain. Bind mount menampilkan satu filesystem tanpa filesystem lain yang di-mount di dalamnya.
sudo mkdir -p /mnt/rootcheck
sudo mount --bind / /mnt/rootcheck
sudo du -xhs /mnt/rootcheck/* 2>/dev/null | sort -h
sudo umount /mnt/rootcheckApa pun yang muncul dalam listing tersebut tetapi tidak muncul pada path normal berarti tersembunyi di bawah titik mount. Lakukan unmount terhadap bind mount setelah selesai, atau du berikutnya tanpa -x akan menghitung file yang sama dua kali.
Blok yang dicadangkan untuk root
ext4 menyisihkan sebagian bloknya untuk pengguna root agar disk penuh tidak mencegah root login dan memperbaiki mesin. Proses yang berjalan sebagai pengguna biasa akan mencapai batas tersebut lebih dahulu, sementara df masih menunjukkan sedikit ruang. Baca pengaturan pada filesystem Anda sendiri, bukan dengan mengasumsikan nilai default.
dev=$(df --output=source / | tail -n 1)
sudo tune2fs -l "$dev" | grep -i 'block count'Perintah tersebut menampilkan jumlah total blok dan jumlah blok yang dicadangkan dalam satuan yang sama, sehingga rasionya dapat dihitung langsung. df melaporkan kolom available sebagai ruang yang masih dapat digunakan oleh pengguna biasa. Karena itu, jumlah used dan available lebih kecil daripada size. Selisihnya adalah ruang cadangan.
Ubah pengaturan tersebut dengan sudo tune2fs -m <percent> "$dev". Perubahan langsung berlaku dan tidak memerlukan remount. Menurunkan ruang cadangan pada filesystem data terpisah merupakan pilihan yang wajar. Pada filesystem root, sisakan ruang yang cukup agar root tetap dapat menulis, karena filesystem root yang benar-benar tidak memiliki ruang kosong jauh lebih sulit diperbaiki. tune2fs berfungsi pada ext2, ext3, dan ext4. XFS tidak memiliki pengaturan yang setara.
Saat du menyesatkan jika digunakan sendiri
Empat kebiasaan du dapat menghasilkan total yang tampak salah.
- Hard link:
dumenghitung sebuah inode satu kali meskipun beberapa nama mengarah ke inode tersebut. Karena itu, tree yang penuh hard link melaporkan ukuran yang lebih kecil daripada jumlah file di dalamnya. - Sparse file:
dumelaporkan blok yang benar-benar dialokasikan, sedangkanls -lmelaporkan ukuran tampak. Tambahkan--apparent-sizeuntuk melihat nilai lainnya. - Permission: jika dijalankan sebagai pengguna biasa,
dumelewati data yang tidak dapat dibacanya sehingga hasilnya lebih kecil dari yang sebenarnya. Error yang ditampilkan biasanya dialihkan ke/dev/null, lalu tidak lagi diperiksa. - Batas filesystem: tanpa
-x,du /menghitung setiap filesystem yang di-mount di bawah/. Karena itu, totalnya dapat melebihi nilai yang dilaporkandf /.
df juga memiliki satu kebiasaan yang perlu diketahui. Perintah ini melaporkan setiap filesystem secara terpisah. Karena itu, jalankan pada path yang tepat yang menjadi target operasi write yang gagal. Filesystem terpisah seperti /boot dapat penuh sesuai jadwalnya sendiri saat paket kernel terus bertambah. Menghapus kernel lama di Ubuntu merupakan tugas yang berbeda dari mengosongkan ruang pada /.
Urutan kerja untuk insiden nyata
- Jalankan
df -h <path>dandf -i <path>pada filesystem yang menjadi target penulisan yang gagal, bukan langsung pada/tanpa pemeriksaan. - Jalankan
sudo du -xh -d 1 <mountpoint> 2>/dev/null | sort -h, lalu telusuri direktori terbesar. - Jika
dutidak dapat menjelaskan penggunaan ruang yang dilaporkandf, cari file yang telah dihapus tetapi masih terbuka di/proc. - Jika keduanya sesuai, lakukan bind mount filesystem ke lokasi lain, lalu cari file yang berada di bawah mount point.
- Jika penggunaan inode mencapai batas, hitung jumlah file, bukan byte.
Setiap langkah tersebut menggunakan perintah yang output-nya dapat dibaca. Inilah perbedaan antara memperbaiki masalah ini dan menebaknya.
FAQ
Mengapa df menampilkan disk penuh saat du menemukan penggunaan yang jauh lebih kecil?
Penyebab yang paling umum adalah file yang dihapus saat masih terbuka pada suatu proses. Menghapus file menghapus entri direktorinya, sehingga du tidak memiliki nama untuk ditelusuri dan berhenti menghitungnya. Inode dan bloknya tetap dialokasikan sampai descriptor terakhir ditutup, sedangkan df menghitung blok yang dialokasikan. Cari /proc/<pid>/fd untuk symbolic link yang targetnya ditandai sebagai telah dihapus. Dengan begitu, Anda dapat menemukan file tersebut dan proses yang masih menahannya. Sebelum mempercayai perbandingan ini, pastikan Anda menjalankan du sebagai root dan dengan -x, karena pengguna biasa akan melewati direktori yang tidak dapat dibacanya tanpa menampilkan error.
Bagaimana cara menemukan file yang dihapus tetapi masih terbuka tanpa lsof?
Gunakan catatan kernel sendiri tentang descriptor yang terbuka. sudo find /proc/[0-9]*/fd -lname '*(deleted)' -printf '%p -> %l\n' 2>/dev/null mencantumkan setiap descriptor yang menunjuk ke file tanpa nama, dan ID proses berada di dalam path yang ditampilkannya. sudo stat -Lc %s pada salah satu path descriptor tersebut menampilkan ukurannya, sehingga Anda dapat mengurutkannya dan memilih file yang relevan. Cara ini tidak memerlukan package apa pun. Hal ini penting karena instalasi package pada filesystem tanpa ruang kosong dapat gagal.
Dapatkah saya membebaskan ruang tanpa menghentikan proses?
Terkadang bisa. sudo truncate -s 0 /proc/<pid>/fd/<n> mengakses inode yang sama melalui descriptor dan membebaskan bloknya sementara proses tetap berjalan. Cara ini paling aman ketika proses membuka file dalam mode append, karena setiap penulisannya selalu menuju akhir file saat ini. Jika tidak, offset penulisan tetap berada di posisi sebelumnya. Penulisan berikutnya akan membuat ulang file dengan hole di bagian awal, sehingga ukuran yang dilaporkan kembali membesar sementara blok di bawah hole tetap bebas. Me-restart unit atau mengirimkan signal agar proses membuka ulang log menggunakan signal yang disebutkan dalam dokumentasinya adalah solusi yang tidak meninggalkan sparse file.
df menampilkan ruang kosong, tetapi penulisan tetap gagal. Apa penyebab lainnya?
Periksa inode dengan df -i pada path yang sama, karena filesystem dengan blok kosong tetapi tanpa inode kosong akan menolak file baru. Periksa apakah penulisan dijalankan oleh pengguna non-root pada filesystem ext4 yang hanya menyisakan blok cadangan. Kondisi ini dapat dilihat dengan sudo tune2fs -l pada device tersebut. Pastikan Anda membaca filesystem yang benar-benar menjadi target penulisan, karena /boot atau /var yang terpisah dapat penuh secara independen dari /.
Mengapa du melaporkan total yang lebih besar daripada df?
du tanpa -x akan masuk ke setiap filesystem yang di-mount di bawah path yang Anda berikan. Akibatnya, beberapa filesystem dijumlahkan, sedangkan df hanya menjelaskan satu filesystem. Bind mount memperburuk kondisi ini karena file yang sama dihitung satu kali pada setiap path tempat file tersebut muncul. Tambahkan -x agar du tetap berada pada satu filesystem, lalu berikan path yang sama kepada df agar kedua command menjelaskan hal yang sama.