はじめに
「ディスクが 90% まで埋まっているのに、du で数えても容量が足りない」
Linuxサーバの運用をやっていると、必ず一度は出会う症状です。多くの場合、犯人は
削除済みなのにプロセスが開いたままのファイル(deleted ファイル) です。
この記事では、切り分け → 発見 → deletedファイルの中身の確認 → 救出 → 容量の解放 までを記載しています。
さらに lsof で空振りしたとき、次に何を疑うかも記載しておりますので参考にしてください。
症状
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 200G 180G 20G 90% /
180G 使われている、と言われている。ところが実際に数えると足りません。
$ du -xsh /
120G /
差は 60G。「この 60G はどこにあるのか」というのが今回の出発点です。
なお du には -x(--one-file-system)を付けてください。付けないと
/proc や別マウントのボリュームまで数えてしまい、そもそも df と比較できない値
になります。切り分けの前提が崩れるので、ここは省略しないところです。
なぜ差が出るのか
rm が消しているのは、ファイルの中身ではなく ディレクトリエントリ(名前) です。
Linux では、inode への参照がすべて無くなったときに初めてブロックが解放されます。
参照とは「名前(ハードリンク)」と「開いているファイルディスクリプタ」の両方です。
つまり、
- 名前は消えた →
duはディレクトリを辿るので見えない - プロセスがまだ開いている → ブロックは解放されず
dfは使用中と数える
この状態が「df と du が合わない」の正体です。ファイルシステムは壊れていません。
プロセスがファイルから手を離した瞬間に、容量はきれいに戻ります。
手順 1: deleted ファイルを探す
+L1 は「リンク数が 1 未満(= 0)のファイル」を絞り込むオプションです。
名前が消えているものだけが出てきます。
sudo lsof -nP +L1
lsof の全プロセス走査は root 権限が必要で、それなりに負荷がかかる操作のためご注意ください。
表示例(列の並びは lsof のバージョンで多少変わります)
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 1234 appuser 45w REG 253,0 53687091200 0 12345678 /var/log/app.log (deleted)
読むべきところは 3 つです。
| 列 | 意味 |
|---|---|
PID / FD
|
後で /proc/PID/fd/FD として触る宛先。ここでは 1234 と 45 |
SIZE/OFF |
消費している容量。この例は約 50GiB |
NLINK |
0 になっている = 名前が無い |
手順 2: 大きいものから当たる
件数が多いときはサイズ順に並べます。
sudo lsof -nP +L1 | sort -k7 -nr | head -20
SIZE/OFF 列が 0t123456 のようなオフセット表記になっている行は、
数値としてまともに並びません。その場合は -s を付けてサイズ表示を強制してください。
手順 3: 中身を確認する
削除済みでも、プロセスが掴んでいる間は /proc 経由で読めます。
ここでlsofコマンドで確認した PID / FD を使います。
sudo less /proc/1234/fd/45
追記され続けているログなら、こちらのほうが実態が見えます。
sudo tail -f /proc/1234/fd/45
「本当に消していいログなのか」をここで判断できるので、
容量を空けたい気持ちを一旦抑えて、必ず先に中身を見ておくことをおすすめします。
手順 4: 必要なら救出する
sudo cp /proc/1234/fd/45 /mnt/backup/recovered.log
ひとつ注意です。コピー先には元ファイルと同じだけの空き容量が必要です。
50GiB の deleted ファイルを、残り 20GiB の同じファイルシステムにコピーすれば、
容量不足を解決するどころか完全に埋め切ってしまいます。
救出するなら、別ファイルシステムか、必要な部分だけを抜くのが安全です。
# 末尾 10 万行だけ確保する
sudo tail -n 100000 /proc/1234/fd/45 > /mnt/backup/recovered.log
元のファイル名に戻せるか
できません。
ln で /proc/PID/fd/45 にハードリンクを張って復活させたくなりますが、
リンクカウントが 0 になった inode に新しい名前を付けることはできません。
現実的な答えは「別ファイルとして救出して、それを正規の場所に置き直す」の一択です。
容量を解放する
deletedファイルを掴んでいるプロセスを落とせば解放されます。
sudo systemctl restart サービス名
再起動が許されるなら、これが一番きれいです。fd が閉じた瞬間に容量が戻ります。
再起動を避けたい場合、ログローテートに対応しているプロセスなら
kill -HUP 等のシグナルでログを開き直させられます。ただしどのシグナルで再オープンするかは実装次第です。対象プロセスの仕様を確認してください。
deleted ファイルではなかった場合
lsof +L1 で何も出てこないなら、原因は別にあります。よくある順に挙げます。
-
マウントで隠れたファイル
マウントポイントになっているディレクトリに、マウント前から中身が置かれていたケース。
dfでは消費されていますが、上にマウントされているためduからは見えません。mount --bindは(--rbindと違って)サブマウントを引き継がないので、
bind 先には「マウントされる前のディレクトリの中身」が現れます。sudo mkdir -p /mnt/root_check sudo mount --bind / /mnt/root_check # 例: /var が別マウントなら、マウント前の /var の中身がここに見える sudo du -xsh /mnt/root_check/var sudo umount /mnt/root_check疑う場所は
findmntで別マウントになっているディレクトリを見れば絞れます。
/mnt/root_check/*を丸ごと数えると/全体の再スキャンになるので、
逼迫している本番では対象を絞るのがおすすめです。 -
予約ブロック
ext4 は既定で数%を root 用に予約しています。dfの Avail が減って見える原因になります。sudo tune2fs -l /dev/sda1 | grep -i reserved -
duの実行権限不足
root 以外で走らせて読めないディレクトリがあると、過小集計されます。
切り分けは root権限 で行ってください。 -
inode の枯渇
容量ではなくdf -iの話です。「容量に余裕があるのに書けない」ならこちらを疑います。
まとめ
df の使用量が多い / du の集計が少ない(du は -x を付ける)
↓
sudo lsof -nP +L1 ── 出てこなければ「deleted ファイルではなかった場合」へ
↓
PID と FD を特定(NLINK=0、SIZE/OFF で大きい順)
↓
/proc/PID/fd/FD で中身を確認
↓
必要なら別ファイルシステムへ救出(空き容量に注意)
↓
プロセスを再起動、または再オープンのシグナルで解放
「ディスクがいっぱいなのに原因が見えない」ときは、まず deleted ファイルを疑う。
そして見つからなかったら、マウントで隠れたファイルを疑う。
この 2 段構えを覚えておくと、切り分けがぐっと早くなります。