df Penuh, tetapi du Tidak: Cari Ruang yang Hilang
Temukan penyebab pesan disk penuh saat du tidak menemukan ruangnya, termasuk file terhapus yang masih dibuka proses, inode habis, mount point, dan blok cadangan.
Mengapa df menyatakan penuh, sedangkan du menunjukkan sebaliknya
df melaporkan bahwa disk penuh, sedangkan du tidak dapat menemukan penggunaan ruang tersebut karena suatu proses masih membuka file yang telah dihapus. Menghapus file akan menghapus namanya dari direktori. Blok data baru dilepaskan setelah file descriptor terakhir yang mengarah ke inode tersebut ditutup. du menelusuri nama file, sehingga tidak menghitung apa pun. df menanyakan kepada filesystem jumlah blok yang dialokasikan, sehingga tetap menghitung file yang sudah tidak memiliki nama.
Panduan ini mereproduksi kondisi tersebut pada VPS Ubuntu biasa dengan tool yang sudah terpasang, menemukan proses yang masih menahan file melalui /proc, lalu membebaskan ruang tanpa reboot. Penyebab lain dengan gejala yang sama dibahas setelahnya: tabel inode yang tidak memiliki entri kosong, file yang tertutup oleh mount point, dan blok yang dicadangkan untuk root.
Jalankan setiap perintah dan baca output Anda sendiri. Nilainya bergantung pada disk Anda, jadi bandingkan kondisi sebelum dan sesudah pada mesin Anda sendiri, bukan dengan angka yang tercetak dalam panduan.
Apa yang dihitung oleh df dan apa yang dihitung oleh du
df (disk free) meminta setiap filesystem yang di-mount untuk melaporkan penggunaan internalnya: jumlah block yang ada, jumlah yang dialokasikan, dan jumlah yang masih bebas. Perintah ini tidak pernah membuka direktori. Hasilnya mencakup semua block yang dialokasikan, termasuk block 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 informasi setiap entri yang ditemukan, lalu menjumlahkan block-nya. File tanpa nama tidak terlihat oleh perintah ini. Direktori yang tidak boleh dibaca juga tidak terlihat. Karena itu, total yang diperoleh pengguna biasa lebih kecil daripada total yang diperoleh root. Jalankan du dengan sudo sebelum menarik kesimpulan dari perbandingan tersebut.
Ada dua opsi yang selalu penting saat membandingkan keduanya.
-xmembatasiduagar tetap 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, pasangan perintah berikut dapat dijalankan berdampingan pada filesystem yang ingin Anda periksa.
df -h /
sudo du -xhs / 2>/dev/nulldf memberikan hasil segera. du dapat memerlukan waktu beberapa menit pada filesystem besar karena perintah ini mengambil informasi setiap file selama proses berlangsung. Jika kedua total tersebut berbeda jauh, dan du dijalankan sebagai root dengan -x, ruang yang hilang dialokasikan untuk sesuatu yang tidak memiliki nama.
Reproduksi ketidaksesuaian dengan sengaja
Lakukan ini pada VPS pengujian. Semua perintah di bawah menggunakan bash dan coreutils, sehingga tidak ada yang perlu diinstal.
Catat kondisi 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 tepat.
Sekarang buat sebuah file. Ukurannya berasal dari ruang kosong yang dilaporkan oleh mesin itu sendiri, sehingga demonstrasi ini dapat menyesuaikan dengan disk apa pun yang Anda miliki.
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 baru bagi Anda, substitusi perintah dalam bash membahasnya secara lengkap. fallocate mencadangkan blok nyata tanpa menulis data ke dalamnya. Karena itu, perintah tersebut selesai seketika. Pada filesystem yang tidak mendukungnya, perintah 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 adalah file tersebut. Dengan demikian, shell membuka file itu dan menyerahkan file descriptor kepada sleep, yang mempertahankannya tetap terbuka. $! menyimpan ID proses dari job latar belakang tersebut. rm kemudian menghapus nama file, 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. Kini filesystem dan pohon direktori tidak lagi menunjukkan kondisi yang sama. Selisih di antara keduanya adalah file yang baru saja Anda hapus.
Temukan 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 memiliki 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 mencetak path descriptor, dan %l mencetak target yang dirujuknya. Process ID adalah elemen kedua dari path yang dicetak. Jalankan perintah ini dengan sudo, karena tanpa opsi tersebut Anda hanya dapat membaca /proc/<pid>/fd untuk proses milik Anda sendiri. Pengalihan stderr membuang pesan dari proses yang selesai saat find sedang melakukan penelusuran.
Server yang sibuk dapat menahan beberapa file yang telah dihapus setiap saat, dan sebagian besar berukuran kecil serta 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 sudah tidak memiliki nama. Pengurutan berdasarkan angka tersebut menempatkan file terbesar di urutan pertama.
Selanjutnya, identifikasi proses di balik descriptor yang berada di urutan teratas. Path pada bagian teratas daftar tersebut memuat kedua angka yang Anda perlukan. Masukkan keduanya ke dalam variabel terlebih dahulu, lalu ganti PID dan N dengan nilai yang dicetak oleh daftar Anda sendiri.
pid=PID
n=N
ps -o pid,user,etime,args -p "$pid"
sudo stat -L "/proc/$pid/fd/$n"ps menampilkan nama program dan lamanya program tersebut berjalan. stat -L mencetak ukuran serta jumlah block yang dialokasikan untuk inode yang telah dihapus. Keduanya menjawab pertanyaan utama: service mana yang membuat file ini tetap aktif.
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, dan menginstal package pada filesystem tanpa ruang kosong juga dapat gagal. Karena itu, penelusuran /proc adalah versi yang selalu dapat digunakan.
Kosongkan ruang tanpa reboot
Reboot memang memperbaiki masalah ini, tetapi itu bukan langkah pertama yang tepat: service akan berhenti dan bukti masalah akan hilang. Ada empat opsi yang lebih aman, dan urutannya adalah sebagai berikut.
Pertama, salin datanya jika masih diperlukan. Membaca path descriptor akan membaca inode yang sedang aktif.
sudo cp /proc/<pid>/fd/<n> /root/recovered.logIni adalah satu-satunya kondisi ketika file yang sudah dihapus mudah dikembalikan. 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 lagi tersedia.
Kedua, kosongkan file melalui descriptor tersebut. Path /proc mengarah ke inode yang sama, sehingga melakukan truncate akan membebaskan blok tanpa menghentikan proses.
sudo truncate -s 0 "/proc/$pid/fd/$n"
df -h /Cara ini berjalan baik ketika writer membuka file dalam mode append, karena setiap penulisan diarahkan ke akhir file saat ini. Jika tidak, proses akan mempertahankan offset penulisan lamanya. Penulisan berikutnya akan terjadi jauh di dalam file dan membuatnya kembali dengan hole di bagian awal. Hole tidak dialokasikan, sehingga blok tetap bebas dan df mempertahankan ruang yang baru saja dikembalikannya. Yang 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 digunakan adalah bentuk nyata yang umum dari masalah ini. Banyak daemon membuka ulang file log setelah menerima signal: nginx menggunakan SIGUSR1, sedangkan rsyslog menggunakan SIGHUP. Periksa dokumentasi daemon yang sedang digunakan dan jangan menebak, karena signal yang salah untuk daemon yang salah dapat menghentikannya.
sudo systemctl kill -s USR1 nginxPerintah tersebut mengirimkan signal ke proses yang dicatat systemd sebagai proses utama unit. Karena itu, unit yang mendeklarasikan Type= yang salah untuk cara daemon tersebut sebenarnya dijalankan dapat mengirimkan signal ke proses yang tidak pernah membuka file yang sudah dihapus. Ruang tersebut tetap tidak terbebaskan.
Keempat, restart unit. sudo systemctl restart <unit> menutup semua descriptor yang dibuka oleh proses lama, sehingga blok pasti kembali tersedia. Pada contoh sebelumnya, proses yang menahan file adalah sleep yang Anda jalankan sendiri. Jadi, menghentikan proses tersebut 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 dicatat sebelum file dibuat. Keduanya kembali sama, dan find tidak lagi melaporkan descriptor Anda. Verifikasi menggunakan perintah yang sama dengan yang menemukan masalah adalah kebiasaan yang perlu dipertahankan.
Memantau perubahan nilai tersebut lebih mudah daripada menjalankan df berulang kali secara manual. watch mengulangi sebuah perintah pada interval tetap dan mencetak ulang output di tempat yang sama, sehingga watch df -h / menampilkan perubahan pada kolom penggunaan saat ruang tersebut kembali tersedia.
Jika totalnya sesuai dan disk masih penuh
Jika df dan root du -x menunjukkan hasil yang sama, tidak ada file terhapus yang terlibat. Penyebab yang tersisa berbeda jenis, dan masing-masing memiliki pemeriksaan tersendiri.
Kehabisan inode, bukan block
Sebuah inode menyimpan metadata untuk satu file. ext4 membuat jumlah inode yang tetap saat filesystem dibuat, sehingga filesystem dapat kehabisan inode meskipun masih memiliki block yang kosong. Pembuatan file baru kemudian gagal meskipun df -h masih menunjukkan ruang yang tersedia.
df -h /
df -i /Perintah pertama menghitung block dan perintah kedua menghitung inode. Bandingkan kolom penggunaan pada keduanya. Jika penggunaan block rendah, tetapi penggunaan inode berada pada batasnya, 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 namanya dan jangan gunakan -i.
df --output=itotal,iused,iavail,ipcent /Kolom tersebut memuat informasi akuntansi yang sama seperti yang ditampilkan df -i, dalam format yang dapat Anda uraikan.
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 dengan hasil terbesar, hingga Anda mencapai tree yang membuat file-file tersebut. Jika du Anda tidak mendukung --inodes, sudo find /var -xdev -type f | wc -l 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 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 karena image layer menyimpan banyak file kecil. Pada mesin tersebut, membersihkan penggunaan disk Docker pada VPS adalah solusi khususnya, dan cara ini membebaskan jauh lebih banyak ruang daripada pembersihan umum pada filesystem.
Ruang yang tersembunyi di bawah titik mount
Sebuah direktori dapat berisi file sebelum apa pun di-mount di atasnya. Mount sebuah filesystem di atas direktori tersebut, dan file di bawahnya tetap berada di tempat semula: tetap menggunakan ruang, tetap dihitung oleh df, dan tidak lagi dapat diakses berdasarkan nama. du tidak dapat melihatnya karena tertutup oleh mount.
Tunjukkan hal ini dengan tmpfs, yang tidak memerlukan disk tambahan. Bagian ini memerlukan mesin tempat Anda diizinkan 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/coveredls di tengah menunjukkan direktori kosong. Salinan tersebut tidak berpindah ke mana pun: file itu masih berada di root filesystem dan akan muncul kembali saat Anda melakukan unmount. Sekarang bayangkan sebuah service yang menulis log ke path tersebut selama sebulan sebelum seseorang melakukan mount volume di atasnya.
Untuk menemukan file yang sebenarnya pada server yang sedang berjalan, mount root filesystem untuk kedua kalinya di lokasi lain. Bind mount menampilkan satu filesystem tanpa filesystem 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 di path normal berada di bawah sebuah titik mount. Lakukan unmount pada 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 yang 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 mencetak jumlah total blok dan jumlah blok yang dicadangkan dalam satuan yang sama, sehingga rasionya dapat dihitung secara langsung. df melaporkan kolom available sebagai ruang yang masih dapat digunakan oleh pengguna biasa. Karena itu, jumlah used ditambah available lebih kecil daripada size. Selisihnya adalah ruang cadangan.
Ubah pengaturan tersebut dengan sudo tune2fs -m <percent> "$dev". Perubahan berlaku segera dan tidak memerlukan remount. Mengurangi ruang cadangan pada filesystem data terpisah merupakan tindakan yang wajar. Pada root filesystem, sisakan ruang yang cukup agar root tetap dapat menulis, karena root filesystem yang benar-benar tidak memiliki ruang kosong jauh lebih sulit diperbaiki. Ruang ini juga mencegah Anda terkunci di luar sistem: key yang ditambahkan ke authorized_keys pada filesystem yang sudah tidak memiliki ruang dapat ditulis secara tidak lengkap atau sama sekali tidak dapat ditulis, lalu login berikutnya menampilkan Izin ditolak (publickey) karena alasan yang tidak terkait dengan key tersebut. tune2fs berfungsi pada ext2, ext3, dan ext4. XFS tidak memiliki pengaturan yang setara.
Di mana du dapat menyesatkan jika digunakan sendiri
Empat kondisi pada du menghasilkan total yang tampak salah.
- Hard link:
dumenghitung inode satu kali meskipun beberapa nama menunjuk ke inode tersebut. Karena itu, tree yang penuh dengan hard link melaporkan ukuran yang lebih kecil daripada jumlah file-file di dalamnya. - Sparse file:
dumelaporkan blok yang benar-benar dialokasikan, sedangkanls -lmelaporkan ukuran yang tampak. Tambahkan--apparent-sizeuntuk melihat angka lainnya. - Permission: jika dijalankan sebagai pengguna biasa,
dumelewati bagian yang tidak dapat dibaca dan melaporkan ukuran yang lebih kecil. Error yang ditampilkannya biasanya dialihkan ke/dev/null, lalu tidak lagi diperiksa. - Batas filesystem: tanpa
-x,du /menghitung setiap filesystem yang di-mount di bawah/. Akibatnya, totalnya dapat melebihi nilai yang dilaporkandf /.
df juga memiliki satu perilaku yang perlu diketahui. Perintah ini melaporkan setiap filesystem secara terpisah. Karena itu, jalankan pada path persis yang menjadi target write yang gagal. Filesystem terpisah /boot akan penuh mengikuti jadwalnya sendiri ketika paket kernel terus bertambah. Menghapus kernel lama di Ubuntu adalah pekerjaan yang berbeda dari mengosongkan ruang pada /.
Urutan kerja untuk insiden nyata
- Jalankan
df -h <path>dandf -i <path>pada filesystem yang menjadi target write 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 yang dilaporkan olehdf, cari file yang telah dihapus tetapi masih terbuka di/proc. - Jika keduanya sesuai, lakukan bind mount filesystem tersebut 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 command yang output-nya dapat Anda baca. Inilah perbedaan antara memperbaiki masalah ini dan sekadar menebak penyebabnya.
FAQ
Mengapa df menunjukkan disk penuh ketika du menemukan penggunaan yang jauh lebih kecil?
Penyebab yang paling umum adalah file yang dihapus ketika suatu proses masih membukanya. 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. Telusuri /proc/<pid>/fd untuk menemukan symbolic link yang targetnya ditandai sebagai telah dihapus, sehingga Anda mendapatkan file tersebut dan proses yang menahannya. Sebelum mempercayai perbandingan ini, pastikan Anda menjalankan du sebagai root dan dengan -x, karena pengguna biasa secara diam-diam melewati direktori yang tidak dapat dibacanya.
Bagaimana cara menemukan file yang telah 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 dicetaknya. sudo stat -Lc %s pada salah satu path descriptor tersebut melaporkan ukurannya, sehingga Anda dapat mengurutkannya dan memilih file yang relevan. Cara ini tidak memerlukan package apa pun. Hal ini penting karena pemasangan package pada filesystem tanpa ruang kosong dapat gagal.
Apakah ruang dapat dibebaskan tanpa menghentikan proses?
Terkadang. sudo truncate -s 0 /proc/<pid>/fd/<n> mengakses inode yang sama melalui descriptor dan melepaskan bloknya, sementara proses tetap berjalan. Cara ini paling aman ketika proses membuka file dalam mode append, karena penulisannya selalu menuju akhir file saat ini. Jika tidak, offset penulisan tetap berada di posisi sebelumnya dan penulisan berikutnya membuat kembali file dengan hole di bagian awal. Akibatnya, ukuran yang dilaporkan kembali membesar, sedangkan blok di bawah hole tetap kosong. Me-restart unit, atau mengirimkan sinyal agar proses membuka kembali log menggunakan sinyal yang disebutkan dalam dokumentasinya, adalah perbaikan yang tidak meninggalkan sparse file.
df menunjukkan ruang kosong, tetapi penulisan tetap gagal. Apa penyebab lainnya?
Periksa inode dengan df -i pada path yang sama, karena filesystem yang masih memiliki blok kosong tetapi tidak memiliki inode kosong akan menolak file baru. Periksa apakah penulisan dijalankan sebagai 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 memasuki setiap filesystem yang di-mount di bawah path yang Anda berikan. Akibatnya, beberapa filesystem dijumlahkan, sedangkan df hanya menjelaskan satu filesystem. Bind mount memperburuk keadaan karena file yang sama dihitung sekali pada setiap path tempat file tersebut muncul. Tambahkan -x agar du tetap berada pada satu filesystem, lalu berikan path yang sama kepada df sehingga kedua command menjelaskan hal yang sama.