こんにちは、インフラエンジニアのryuです。
深夜にアラートが鳴って、サーバーにログインしたらこれが出ていた、という経験はないでしょうか。
No space left on device
とりあえず df -h を叩いたら、確かに使用率100%。それで、大きいファイルを探して消そうと du を走らせたら、合計しても半分くらいにしかならない。
消えたはずの容量は、どこにいったのでしょうか。
この「df と du が合わない」現象、初めて遭遇するとかなり混乱します。しかも、原因が分からないまま適当なファイルを消すと、消してはいけないものを消してしまう。
障害対応で本当に怖いのは、原因が分からないことそのものより、分からないまま手を動かしてしまうことです。容量が逼迫している場面は特に焦るので、余計にやりがちです。
今日は、Linuxの容量トラブルを見る順番の話をします。個々のコマンドの使い方というより、どういう順で疑って、どこで分岐するか、という切り分けの流れの話です。
そもそも df と du は、違うものを数えている¶
いきなり本題ですが、この2つは仲が悪いのではなく、そもそも見ている場所が違います。ここを押さえると、あとの話が全部つながります。
du は、ディレクトリをたどって、そこにあるファイルのサイズを足し算します。要するに、家の中を歩き回って荷物を数えている状態です。
いっぽう df は、ファイルシステムそのものに「いま何ブロック使ってる?」と聞いています。歩き回るのではなく、管理台帳を見に行っている。
duはディレクトリをたどって積み上げた実測値、dfはファイルシステムが管理している使用量。同じ対象を別の方法で数えているので、条件がそろえば食い違います。
つまり、台帳には載っているのに、家の中を歩いても見つからない荷物があると、2つの数字がずれます。それが何なのかを、これから見ていきます。
最初にやるのは、容量とinodeの両方を見ること¶
切り分けの一歩目は固定です。次の2つを、必ずセットで確認します。
df -h # 容量(ブロック)の使用状況
df -i # inode の使用状況
なぜ2つ見るのかというと、Linuxでは容量が空いていても書き込めなくなることがあるからです。
ファイルを1つ作るには、中身を置く場所と、ファイルの情報を記録する inode という枠の両方が要ります。この枠はファイルシステムを作った時点で数が決まっていて、あとから増やせません。
だから、小さいファイルを大量に作るとどうなるか。容量はガラガラなのに、枠だけが先に尽きます。これが「空いているのに No space left on device」の正体のひとつです。
セッションのファイルやキャッシュ、メールのキューなどを大量に置いているサーバーでよく起きます。df -h だけ見て「容量は余ってるのにおかしい」と悩んでいる人は、だいたいここを見落としています。
ちなみに、この枠が固定なのは ext4 などの場合です。XFS では必要に応じて後から作られるので、枯渇はしにくくなっています。ただし上限が無いわけではないので、絶対に起きないと思い込むのは危険です。
自分のサーバーがどちらなのかは、意外と把握していないことがあります。トラブルが起きてから調べるのではなく、平常時に一度確認しておくと、当日の判断がだいぶ速くなります。
数字の読み方で、次の分岐が決まる¶
2つの結果を見比べると、進む方向が決まります。
df -h |
df -i |
疑うもの |
|---|---|---|
| 100% | 余裕あり | 大きいファイル、または解放されていない削除済みファイル |
| 余裕あり | 100% | 小さいファイルの大量発生(inode枯渇) |
| 100% | 100% | 中身のないファイルが大量に増えている |
ここまで来たら、あとはそれぞれの筋を追うだけです。順番に見ていきます。
容量が100%のとき、まず du で当たりをつける¶
df -h が100%で df -i に余裕があるなら、素直に大きいファイルを探します。
# ルート直下を1階層だけ、大きい順に
du -x -d1 -h / | sort -h | tail -20
-x を付けているのがポイントです。これを付けないと、別のファイルシステムまで数えに行ってしまい、いま調べたいディスクの話ではなくなります。
これで大きいディレクトリが分かったら、そこへ降りて同じことを繰り返します。上から順に絞り込んでいくイメージです。
たいていはここで犯人が見つかります。ログが回っていない、バックアップが溜まっている、コアダンプが出ている。この3つで大半です。
ひとつ注意点があります。ファイル数の多いサーバーでこれを実行すると、それなりに時間がかかりますし、ディスクへの読み込みも発生します。障害対応中に負荷をかけたくない場面では、いきなり全体を数えず、怪しいディレクトリから当たっていくほうが安全です。
たとえば、経験上あやしい場所は決まっています。/var/log と /var/lib、それからアプリケーションが一時ファイルを書く場所。ここを先に見るだけで、多くの場合は用が足ります。
問題は、それでも見つからなかったときです。
du の合計が df に届かないなら、削除済みのファイルを疑う¶
ここが今日いちばん伝えたいところです。
Linuxでは、rm はファイルの実体を消していません。消しているのは、ディレクトリに登録された「名前」のほうです。
宅配ボックスにたとえると分かりやすいかもしれません。rm は、ボックスの扉に貼ってある宛名シールを剥がす操作です。中身はまだ入っている。荷物が本当に処分されるのは、その荷物を使っている人が全員いなくなったときです。
Linuxの場合、この「使っている人」がプロセスです。あるプロセスがファイルを開いたままなら、名前を消しても実体は残り続けます。そして df はその実体を使用中として数え、du は名前がないので数えない。
これが、df と du が食い違ういちばん多い原因です。
犯人はこう探します。
sudo lsof +L1
+L1 は、リンク数が1未満のファイル、つまり名前がもう無いのに開かれているファイルだけを表示するという指定です。ここに巨大なログファイルが並んでいたら、原因はこれで確定です。
表示された行には、そのファイルを開いているコマンド名とプロセスIDも出ます。つまり、どのサービスが握っているのかまで、この一発で分かります。犯人だけでなく、犯人が誰の指示で動いているかまで見えるわけです。
なお lsof が入っていないサーバーもあります。その場合は、プロセスが開いているファイルの一覧が置かれている場所を直接のぞく方法があります。プロセスIDを指定して、そこにぶら下がっているファイルを見ると、末尾に削除済みである旨が付いたものが見つかります。
いちばん多いのは、ログを消したのに減らないパターン¶
具体的な場面を挙げます。ディスクが逼迫したので、とりあえず大きなログファイルを rm した。でも df の数字が1バイトも減らない。
これは、そのログを書いているプロセスが、消したファイルを開いたまま書き続けているからです。宛名シールは剥がしたけれど、配達員はそのボックスに荷物を入れ続けている状態。
対処は、そのプロセスにファイルを開き直させることです。サービスを再起動するのがいちばん確実で、再起動した瞬間に df の数字がストンと落ちます。あの瞬間の気持ちよさは、一度味わうと忘れません。
再起動できない事情があるなら、ファイルの中身だけを空にする手もあります。
# ファイルを消さずに、中身だけを空にする
: > /var/log/app/access.log
こちらは inode を消さないので、プロセスは同じファイルを開いたまま書き続けられます。急場をしのぐならこちらのほうが安全です。
同じ理屈は、ログの世代管理でも起きます。ログを回すときに、古いファイルの名前を変えて新しいファイルを作る方式だと、書いている側が古いほうを開いたままになることがあります。だから設定には、回したあとにプロセスへ開き直しを知らせる手順が入っている。ここが抜けている設定を見かけたら、容量が減らない未来が待っています。
そもそも rm でログを消す運用自体が事故のもとなので、ログの扱いを見直すきっかけにしてください。ログの読み方そのものに不安がある方は、未経験インフラエンジニアのためのログの読み方入門もあわせてどうぞ。
見落としやすい、その他の原因¶
上の2つで解決しないときに疑う場所も挙げておきます。頻度は落ちますが、知らないと永遠に見つかりません。
systemdのジャーナルが太っている¶
systemd を使っているサーバーでは、journal が /var/log/journal の下でじわじわ育ちます。設定によっては、想像よりずっと大きくなっています。
journalctl --disk-usage # いまの使用量
sudo journalctl --vacuum-size=500M # 500MBまで削る
そのうえで、/etc/systemd/journald.conf の SystemMaxUse に上限を書いておけば、二度と同じことは起きません。応急処置で終わらせず、ここまでやるのがおすすめです。
上限を決めるときは、そのディスクの総容量から逆算します。ログのために全体の何割を差し出せるのかを決めて、そこから配分する。なんとなく削るのではなく、先に上限を決めておくという発想が大事です。
マウントの下に、ファイルが埋まっている¶
これはかなり厄介なやつです。
/data というディレクトリにファイルを書いたあとで、その /data に別のディスクをマウントしたとします。すると、先に書いたファイルはマウントされたディスクの陰に隠れて、見えなくなります。
見えないのに、親のファイルシステムの容量は使い続けています。du で歩き回っても、隠れているので見つかりません。
確認するには、別の場所にルートを重ねて覗きます。
sudo mkdir -p /mnt/root
sudo mount --bind / /mnt/root
sudo du -x -d1 -h /mnt/root
こうすると、マウントに隠された本来の中身が見えます。確認が終わったら umount を忘れずに。
この状態を作らないためには、マウントする前にそのディレクトリが空であることを確認する、という一手間を習慣にしておくことです。運用に入ってから気づくと、隠れているファイルを消してよいのかどうかの判断からやり直しになります。
ext4の予約ブロックにぶつかっている¶
ext4 では、既定でファイルシステムの一部(多くは5%)が管理者用に予約されています。一般ユーザーはその予約分を使えません。
なので、df -h の使用率が100%と出ていても、実際にはまだ予約分が残っていて、root だけは書けるという状態になります。アプリケーションだけが書き込みに失敗するのに、管理者としてログインすると普通にファイルが作れてしまう。この非対称さが、切り分けを混乱させます。
この予約は無駄な仕組みではありません。一般の書き込みで埋め尽くされても、管理者がログを書いたり復旧作業をしたりできる余地を残すためのものです。数テラバイトのデータ用ディスクで5%というのは大きすぎますが、システム用の領域では素直に残しておくほうが安全です。
もし予約が明らかに過大なら、後から割合を変えることもできます。ただし、これは容量が足りない根本原因を解決する操作ではないので、時間を稼ぐための手段だと割り切ってください。
応急処置で終わらせず、再発を止める¶
ここまでで容量は空くはずですが、空いただけで放置すると、同じアラートがまた鳴ります。
原因が「増え続けるファイル」なら、対処は増やさない仕組みを入れることです。ログなら logrotate、一時ファイルなら定期的な削除。定期実行の設定に慣れていなければ、crontabで定期的にジョブを実行する方法が参考になります。
そして、しきい値の監視は容量だけでなく inode にも入れておきます。容量だけ監視していると、inode枯渇のときに何も鳴りません。書き込みが失敗して初めて気づく、というのがいちばん困る壊れ方です。
もうひとつ、しきい値の置き方にもコツがあります。90%で鳴らすだけだと、気づいた時点で対処の時間がほとんど残っていません。増え方の傾きが見えるように、80%あたりで一度目を鳴らしておくと、落ち着いて手を打てます。
障害対応の力は、こうやって一度でも自分で切り分けた経験からしか育ちません。障害の切り分けを独学で鍛える方法でも書きましたが、大事なのは手順を暗記することではなく、疑う順番を持っておくことです。
なお、この手の練習は本番でやるものではありません。壊していい検証環境を用意する話は研修用サンドボックスの設計とコストの抑え方にまとめたので、環境から整えたい方はそちらをどうぞ。
コマンドそのものに不慣れな段階であれば、初心者向けLinuxコマンド入門から順に押さえるのが早道です。体系立てて学び直したい場合は、InfraAcademy のLinuxの学習ロードマップに沿って、手を動かしながら進められます。
まとめ¶
容量トラブルは、闇雲に大きいファイルを探すと遠回りになります。順番を決めておくと、迷いません。
最初に df -h と df -i を両方見て、容量とinodeのどちらが尽きたのかを判定する。容量なら du -x で絞り込み、それでも合わなければ削除済みで開かれたままのファイルを lsof +L1 で探す。見つからなければ、ジャーナルの肥大、マウントに隠れたファイル、予約ブロックを順に疑う。
そして、空けたら必ず再発防止まで手を付ける。ここまでがワンセットです。
df と du の食い違いは、故障ではなく仕様です。 仕組みを知っていれば、深夜のアラートも落ち着いて処理できます。次に同じ画面を見たときは、きっと手が動くはずです。



