df -h の出力を1列ずつ解剖(duと食い違う理由も)
ディスク逼迫の一報にも慌てず切り分けられる。df -hの6列をstatfsまで遡って読み解き、duと合わない・SizeがUsed+Availにならない・空きはあるのに書けない、という定番の3つを原因から解消する。
- dfはマウント情報を読み、各マウント点にstatfsを発行してスーパーブロックのカウンタを並べる。ファイルを開かないため数百万件でも速く、1行はディスクではなくマウント済みファイルシステム1つに対応する。
- dfとduの不一致は、削除済みでもプロセスが開くファイルが主因だ。fdが残る限りブロックは解放されず、dfは数えるがduは辿れない。lsof +L1で保持元を特定し、開き直すと解放される。
- SizeとUsed+Availの差はext4が既定で5%確保するroot用予約ブロックで、tune2fs -mで変えられる。Use%の分母はUsed+Avail。空きがあるのに書けないならinode枯渇を疑いdf -iを見る。
このコマンドは何を見せているか
df は disk free の略だが、見せているのは「マウント済みのファイルシステムの空き」であって、ディスクの空きではない。この一語の違いが、本稿のつまずきのほとんどを説明する。
動作は単純だ。カーネルが公開するマウント情報(Linuxでは /proc/self/mountinfo)を読み、並ぶマウント点それぞれに statfs(2) を発行する。返るのはスーパーブロックが持つ集計済みのカウンタ、すなわち総ブロック数・空きブロック数・inode数だ。dfはそれを並べているにすぎない。
つまりdfはファイルを1つも開かず、ディレクトリも歩かない。数百万ファイルでも一瞬で返るのはこのためだ。そして1行はディスクでもパーティションでもなく、マウント済みのファイルシステム1つを指す。
未マウントのパーティション、未フォーマットの領域、LVMの未割り当てエクステントは、dfに一切現れない。ブロックデバイス側の空きは lsblk や vgs で見る。
対比を先に置く。dfがカウンタを読むだけなのに対し、du はディレクトリを歩いてファイル1つずつの割り当てブロックを足し上げる。出どころが違う以上、両者がずれるのは異常ではない。
横にスクロール
1列ずつ解剖
題材は、メモリ8GB、ルートに50GiBのパーティションを切ったUbuntuのDockerホストである。
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 3.9G 0 3.9G 0% /dev
tmpfs 798M 1.1M 797M 1% /run
/dev/nvme0n1p2 49G 42G 4.5G 91% /
tmpfs 3.9G 0 3.9G 0% /dev/shm
/dev/nvme0n1p1 511M 6.1M 505M 2% /boot/efi
/dev/loop0 64M 64M 0 100% /snap/core20/2105
overlay 49G 42G 4.5G 91% /var/lib/docker/overlay2/3f9c1a.../merged
tmpfs 798M 16K 798M 1% /run/user/1000
6列は、ほぼそのままstatfsのフィールドに対応する。
| 列 | 出どころ | 意味 |
|---|---|---|
| Size | f_blocks | 総ブロック数。ext4はメタデータ分を除く |
| Used | f_blocks − f_bfree | 割り当て済みブロック |
| Avail | f_bavail | 非特権プロセスが使える空き |
| Use% | 計算値 | Used ÷ (Used + Avail) の切り上げ |
| Mounted on | マウント情報 | statfsではなくマウント情報から取る |
Filesystem — デバイス名ではなく、マウント元の文字列
ブロックデバイスのパスとは限らず、マウント情報の「マウント元」の文字列が出るだけだ。tmpfs や overlay のような型名も、nfs-server:/export のようなネットワーク上の場所も入る。上の出力では tmpfs が3行あり、どれがどれかはMounted onを見るまで分からない。同じdevtmpfsでも、RHEL系は devtmpfs、Debian系は udev と出る。実際の型は df -T のType列で確かめる。
df -Th # 型(Type)列を追加する
df -h -x squashfs -x tmpfs # 邪魔な行を除く
特殊なファイルシステムの行をどう読むか
tmpfsはRAM上のファイルシステムで、Sizeはマウントオプション size= の値(既定はRAMの半分)にすぎず、その分のメモリが確保済みという意味ではない。使った分だけメモリを食うので、/dev/shm を埋めれば最後はOOM Killerを呼ぶ。/run と /run/user/1000 はsystemd系がRAMの10%で作る798Mの領域だ。
/dev/loop0 はsnapのsquashfsで、Availが0、Use%が100%。読み取り専用の圧縮イメージは作った時点で隙間なく詰まっているのが正常な姿だ。監視では除外する。
overlayの行はコンテナのレイヤを重ねた統合ビューだ。SizeもAvailもルートと同じなのは、overlayfsが自前の容量を持たず、statfsを上位レイヤ(upperdir)が置かれたファイルシステムへ丸投げするからである。コンテナの中で df -h / を見て残り4.5Gと読んだなら、それはホストのルートの残りだ。原理はコンテナのオーバーレイファイルシステムで扱っている。
Size — パーティションの容量ではない
Sizeは f_blocks、ファイルシステムが管理する総ブロック数だ。50GiBのパーティションが49Gと出るのは丸め誤差ではない。ext4はmkfsの時点でinodeテーブル、ジャーナル、ビットマップ、スーパーブロックの複製といったメタデータ領域を確保し、statfsはその分を差し引いた値を返す。この領域は最初からSizeに入っていない。
Used — 見えているファイルの合計ではない
Usedは総ブロック数から空きブロック数を引いた値、つまり割り当て済みのブロック数だ。「ディレクトリから辿れるファイルの合計」ではない。ブロックが誰かに割り当てられていればUsedに乗る。名前がもう無くても、マウントの下に隠れていても、である。この定義がduとの食い違いを生む。
Avail — アプリが実際に書ける量
Availは f_bavail、非特権プロセスが使える空きブロック数だ。空きブロック総数の f_bfree とは別物で、その差が予約ブロックにあたる。ext4は既定でroot専用に総ブロックの5%を予約しており、手を出せるのはrootか、tune2fs -u や -g で指定したUID/GIDのプロセスだけだ。だからSizeからUsedとAvailを引くと、ちょうど予約分が残る。運用で見るべきはこの列である。
Use% — 分母はSizeではない
最も誤解される列だ。Use%は Used ÷ Size ではなく、Used ÷ (Used + Avail) を切り上げた値である。ルートの行なら 42 ÷ (42 + 4.5) = 90.3% で、切り上げて91%。Size(49G)を分母にすれば86%で、表示と合わない。帰結として、Availが0になった瞬間にUse%は100%になる。予約ブロックが残っていても、非特権プロセスから見れば1バイトも書けないのだから正しい表示だ。
Use%は分母がUsed+Availなので、tune2fs -m で予約を減らすだけで、1バイトも消していないのに値が下がる。100TBの1%と10GBの1%も意味がまったく違う。閾値監視はAvailの絶対値と増加の傾きで組むほうが確実だ。
Mounted on — 一致するのは最も深いマウント
マウント点のパスで、statfsではなくマウント情報から取る。押さえるべきは、あるパスへの書き込みがどの行に支配されるかだ。答えは、そのパスを含む最も深いマウント点である。/var/log がdfに現れないなら独立したファイルシステムではなく、ルートの行のAvailが/var/logの運命を握っている。目で探すより、パスを引数に渡すのが速い。
df -h /var/lib/postgresql/data # そのパスを含むFSの行だけが出る
つまずきやすい点
第一に、dfとduが合わない。まず作法から。duは既定でマウント境界を越えて歩くので、dfの1行と比べるなら -x で1つのファイルシステムに閉じ込め、読めないディレクトリで過少集計にならないようroot権限で実行する。
du -shx / # ルートと同じFSだけを合計する
df -h /
原因はほぼ2つだ。1つ目は、削除済みなのに開かれたままのファイル。unlink が消すのはディレクトリエントリ、つまり名前だけである。inodeには別に参照カウンタがあり、プロセスがfdを握る限りデータブロックは解放されない。名前が無いのでduは辿り着けず、ブロックは割り当てられたままなのでdfは数える。ローテートでログを消したのにアプリが古いfdを掴み続けている、が典型例だ(fdとinodeの関係はファイルディスクリプタとオープンファイルテーブルの構造に詳しい)。リンク数が1未満のファイルを一覧する lsof +L1 で犯人が分かる。
$ lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 1287 app 3w REG 259,2 8589934592 0 262147 /var/log/app/app.log (deleted)
NLINKが0、サイズは8GiB。これがdfにだけ見えていた正体だ。解放するにはプロセスにファイルを開き直させる。再起動できないなら、/proc経由でfdの実体を切り詰める手もある。
systemctl restart app # 本筋: 開き直させる
truncate -s 0 /proc/1287/fd/3 # 応急: fd経由で中身を捨てる
2つ目は、マウントで隠れたディレクトリ。/mnt/data にファイルを置いた後でそこにディスクをマウントすると、元のファイルはルート上に残り続けるのにパスからは見えなくなる。duはどう歩いても届かず、ルートのUsedだけが減らない。バインドマウントは再帰的でないため、重ねる前の素のツリーを覗ける。
mount --bind / /mnt/rootview # 重ねる前のルートが見える
du -shx /mnt/rootview/mnt
umount /mnt/rootview
第二に、SizeがUsed + Availと一致しない。ルートの行では 49 − 42 − 4.5 = 2.5G が行方不明に見える。これがext4の予約ブロックで、mke2fsが既定で総ブロックの5%をroot用に確保している。ディスクが埋まってもroot権限のデーモンは書き込めるようにして復旧手段を残すことと、アロケータに余地を残して断片化を抑えることが目的だ。変更にアンマウントも再フォーマットも要らない。
tune2fs -l /dev/nvme0n1p2 | grep -i reserved
tune2fs -m 1 /dev/nvme0n1p2 # 予約を1%へ下げる
4TBのボリュームなら5%は200GBに達するので、データ置き場と割り切れるなら1%まで下げる価値はある。ただしルートで0%にするのは避けたい。満杯の瞬間にrootでも書けなくなる。
第三に、容量は空いているのに書けない。Availは4.5Gあるのに、touchすら No space left on device で失敗する。ファイルシステムはブロックとinodeを別々に管理しており、ブロックが余っていてもinodeが尽きればファイルは作れないからだ。
$ df -i /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 3276800 3275712 1088 100% /
ext4のinode数はmkfsの時点で固定される。既定はおよそ16KBに1個の割合なので、50GiBなら約327万個。後から増やせず、ファイルシステムごと拡張する以外に道はない。極小ファイルを数百万個作る用途では、容量よりinodeが先に尽きる。XFSはinodeを動的に割り当てるため、この形の枯渇は基本的にext系の問題だ。
du --inodes -xd1 /var | sort -n | tail # 直下のどこがinodeを食っているか
厄介なのは、この状況でもerrnoが容量不足と同じENOSPCになる点だ。メッセージだけ見て容量を疑い、dfで空きを確認して混乱する。空きがあるのに書けないと感じたら反射的に df -i を打ちたい(ENOSPC(No space left on device)に逆引きをまとめてある)。
まとめ
dfは、マウント情報を読んで各ファイルシステムにstatfsを問い合わせ、カウンタを並べているだけのコマンドだ。この一点を押さえれば6列の癖は説明がつく。Sizeが小さいのはメタデータを引いているから、Used + Availにならないのは予約ブロックがあるから、Use%が100%でも空きが残るのは分母がSizeではないから、duと食い違うのは出どころが違うからである。
障害対応の型はこうだ。df -h で当たりを付け、空きがあるのに書けないなら df -i、duと合わないなら lsof +L1。この3手で、ディスク満杯の大半は原因まで辿り着ける。
コマンド出力の読み方の記事ガイド
df -h の出力を1列ずつ解剖(duと食い違う理由も)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
コマンド出力
比較で見る軸
難易度: advanced / カテゴリ: コマンド出力の読み方 / タグ数: 4
導入後に効く点
dfとduの不一致は、削除済みでもプロセスが開くファイルが主因だ。fdが残る限りブロックは解放されず、dfは数えるがduは辿れない。lsof +L1で保持元を特定し、開き直すと解放される。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- コマンド出力の読み方
- タグ数
- 4
判断チェックリスト
- 自社の用途が「コマンド出力 / Linux」に近いか確認する。
- 強みである「dfはマウント情報を読み、各マウント点にstatfsを発行してスーパーブロックのカウンタを並べる。ファイルを開かないため数百万件でも速く、1行はディスクではなくマウント済みファイルシステム1つに対応する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。