dfは空きありなのに容量不足 — 削除済みファイル占有とinode枯渇の切り分け
この記事で解決できること
dfには空きがあるのにNo space left on deviceが出る理由が分かるdfは満杯なのにduの合計と合わない(削除済みファイル占有)を特定できるlsofとdf -iで 2 方向の乖離 を切り分け、安全に空きを回収できる
結論(最短)
df -h満杯だがduは正常 → 削除済みファイルをプロセスが開いたまま。lsof +L1で特定し、サービス再起動 or fd 切り詰めで回収df -hに空きがあるのに書けない → inode 枯渇(df -iでIUse%確認)か 予約ブロック(非 root 書き込み不可)
前提(対象環境)
- OS:Ubuntu / Debian 系(ext4 想定、
tune2fs等) - シェル:bash
- 権限:
sudoが使える想定
なぜ df と実際の空き容量がズレるのか?
結論: df はファイルシステムのスーパーブロックが持つ集計値を読むだけで、開いたままの削除済み inode や inode テーブルの枯渇は容量(%)に現れないため乖離が起きる。
df はディレクトリを走査せず、ファイルシステムが保持する集計値(空きブロック数・空き inode 数)を読む。一方 du は実在するパスを辿ってサイズを合算する。この実装差が乖離の正体で、典型は次の 3 パターン。
| 症状 | 真因 | 確認コマンド |
|---|---|---|
df 満杯・du 少ない |
削除済みファイルをプロセスが open 中 | lsof +L1 |
df に空き・書けない |
inode 枯渇 | df -i |
df に約 5% 空き・非 root が書けない |
予約ブロック | tune2fs -l |
キーワードは 「容量(ブロック)」と「inode」は別カウンタ という点。どちらか一方でも尽きれば No space left on device になる。
削除済みファイルがディスクを占有しているか確認するには?
結論: lsof +L1 でリンク数 0(削除済み)かつ open 中のファイルを一覧でき、SIZE 列が大きいものが df と du の差を生んでいる正体。
ファイルは rm してもプロセスが開いている間は inode が解放されず、ブロックも回収されない。ログを rm したのにサービスが書き続けているケースが典型。
$ sudo lsof +L1
+L1 は「リンク数が 1 未満(= 0、削除済み)」のオープンファイルだけを表示する。NLINK 列が 0、SIZE が大きい行が原因。
deleted 文字列で探す方法も併用できる:
$ sudo lsof -nP / 2>/dev/null | grep '(deleted)'
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 1234 root 5w REG 259,1 8589934592 131 /var/log/nginx/access.log (deleted)
この例では nginx が 8 GB の削除済みログを開いたまま握っている。df は満杯、du /var/log は小さい、という状態になる。
PID と FD(上記なら PID 1234 / FD 5)を控えておく。次の解放手順で使う。
占有を解放するには?
結論: 原則は該当プロセスの再起動(または reload)。即時に空きが要る場合は /proc/PID/fd/N をリダイレクトで 0 バイトに切り詰めれば再起動なしで回収できる。
方法 A:プロセスを再起動 / reload(推奨)
開いている fd が閉じれば inode が解放され、ブロックが即座に戻る。
$ sudo systemctl restart nginx
ログ系なら full restart でなく reload で開き直すサービスも多い(systemctl reload nginx)。
方法 B:再起動できないとき fd を切り詰める
サービスを落とせない場合、開いたままのファイル実体を /proc/<PID>/fd/<FD> 経由で 0 バイトに切り詰める。
$ sudo truncate -s 0 /proc/1234/fd/5
: > /proc/1234/fd/5 でも同じ。データは失われるがプロセスは生きたまま、容量が即時回収される。
切り詰めるのは 削除済み(deleted)かつログ等の使い捨て ファイルに限る。生きたデータベースファイルや未削除の実ファイルに対して行うと破損する。lsof +L1 で deleted を確認してからにすること。
inode 枯渇を確認するには?
結論: df -i で IUse% が 100% なら容量が残っていても書けない。小さいファイルが大量にあるディレクトリを find で特定して削減する。
df -h に空きがあるのに書けないなら inode を疑う。
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 6553600 6553600 0 100% /
IUse% が 100% なら inode 枯渇。小さいファイルが大量にある場所を探す:
$ sudo find / -xdev -type f 2>/dev/null | cut -d/ -f1-3 | sort | uniq -c | sort -n | tail
セッションファイル・メールキュー・キャッシュの断片(/var/lib/php/sessions、/var/spool、大量の小ファイル)が候補。詳しい削減手順は inode 枯渇の対処 を参照。
df に約 5% の空きがあるのに書けないのはなぜ?
結論: ext4 は既定で 5% を root 専用の予約ブロックとして確保するため、非 root プロセスは df に空きが見えても書き込めない。tune2fs で予約率を確認・調整する。
ext4 は既定で全体の 5% を root 用に予約する(システム停止回避のため)。非 root のアプリは df に空きが見えても ENOSPC になる。
$ sudo tune2fs -l /dev/sda1 | grep -i 'reserved block'
Reserved block count: 6553600
データ専用パーティションなら予約率を下げて回収できる(root 領域では下げすぎ注意):
$ sudo tune2fs -m 1 /dev/sda1
-m 1 で予約を 1% に変更。/ では既定の 5% 維持を推奨。
再発を防ぐには?
結論: ログは logrotate の copytruncate か正しい reload 連携で開き直し、inode はファイル数を監視する。df だけでなく df -i と lsof +L1 を定期確認に組み込む。
- ログ肥大:
logrotateをcopytruncate、またはローテーション後に該当サービスへ正しくシグナルを送る設定にする(rm だけは禁物) - inode 監視:
df -iを監視対象に追加。容量(%)だけ見ていると inode 枯渇を見逃す - 削除前確認:大きなファイルを消す前に
lsof <file>で誰が開いているか確認
切り分けの定石は「df -h で容量、df -i で inode、lsof +L1 で削除済み占有」の 3 点を必ずセットで見ること。