TL;DR - 記事を読んでられない人向けの要約
df
と du
/ ls
両者でディスクの使用量が一致しない場合、テンポラリファイル(プロセスが掴んでいる削除済ファイル)のサイズが du
側でカウントされていないので、 lsof +L1
(リンク数0のファイルを羅列) コマンドを使うと削除済未解放のファイルの一覧が出る。
$ lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
python3 1731 root 3w REG 8,3 0 0 7344873 /root/foobar.txt (deleted)
# → /root/foobar.txt というファイルは削除済みだが解放されていない
本文
みなさんが日々Linuxを運用している中で、Linuxの監視等から「ディスクが足りないよ〜」とアラートが出る場合、まずは事象確認のために df
コマンドを使うと思います。(下記)
$ df -h
Filesystem Used Available Use% Mounted on
/dev/sda1 124G 0 100% /
うわあ! ディスクが100%埋まっていますね。大事なサーバなら超冷や汗ものです。
(ログやローカルバックアップ、Dockerのゴミとかが詰まったのか?)
普通の管理者ならちゃんとディスクが埋まった原因を把握するために du
コマンドを使うと思います。
# ルート以下のディレクトリごとの使用量を表示
$ du -h --max-depth 1 /
...
3.0G /
普通なら、ここで原因を特定できるのですが、上記の様子だとディスク全体の 124GB
のうち 3GB
しか使われていないようです。 これはおかしい。
下記の両者で使用量の値が大きく異なってしまっています。
- ファイルシステム上で合算した場合:
du
- ディスク側で統計とった場合:
df
こんなことがあっていいのか? Linuxのファイルシステムにもダークマターはあるのか?
原因はプロセスが掴んでいる unlink
(削除)済みのテンポラリファイル
前述のような現象になる原因は、 プロセスが削除済みのファイルをそのままオープンしたまま保持しているのが原因 (かもしれない)です。
こういったファイルは下記のようなコードで簡単に作成できます。
import os
# ファイルを作成&中身を書く
print("Create my_temp.txt")
f = open("my_temp.txt", "w")
f.write("Hello from FILE!")
f.flush()
print("Waiting for input...")
input()
# 該当ファイルを削除
print("Delete my_temp.txt")
os.remove("my_temp.txt")
print("Waiting for input...")
input()
# ファイルを閉じる
print("Close my_temp.txt")
f.close()
print("Waiting for input...")
input()
上記を動かして動作を見てみましょう。
$ python3 create_missing_file.py
Create my_temp.txt
Waiting for input...
上記を動かしているシェルとは別のシェル上の ls
でファイルを見てみます。
$ ls -al | grep my_temp.txt
-rw-r--r-- 1 root root 16 Dec 5 09:24 my_temp.txt
作成されたファイルがいますね。Python側で処理を進めて削除させてみましょう。
Delete my_temp.txt
Waiting for input...
$ ls -al | grep my_temp.txt
(何も表示されない)
ls
に何も表示されなくなりました。プログラム側でファイルを削除したから当然なのですが。
ただ、 ls
から辿れなくなっただけで、ディスク上のファイルはまだ消えていません。 このようなファイルは lsof +L1
コマンドで出てきます。
$ lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
python3 1978 root 3w REG 8,3 16 0 7344872 /root/my_temp.txt (deleted)
該当のPID (1978) の procfs を見てもファイルが実在していることがわかります。(/fd/3
)
$ ls -al /proc/1978/fd/
lrwx------ 1 root root 64 Dec 5 09:27 0 -> /dev/pts/0
lrwx------ 1 root root 64 Dec 5 09:27 1 -> /dev/pts/0
lrwx------ 1 root root 64 Dec 5 09:27 2 -> /dev/pts/0
l-wx------ 1 root root 64 Dec 5 09:27 3 -> '/root/my_temp.txt (deleted)'
ではプログラム側の処理を更に進めてファイルをクローズしてみましょう。
Close my_temp.txt
Waiting for input...
この状態で先程表示された削除済ファイルを確認すると無事ちゃんと消えています。この状態になってようやく df
= du
となります。
$ lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
python3 1978 root 3w REG 8,3 16 0 7344872 /root/my_temp.txt (deleted)
$ ls -al /proc/1978/fd/
lrwx------ 1 root root 64 Dec 5 09:27 0 -> /dev/pts/0
lrwx------ 1 root root 64 Dec 5 09:27 1 -> /dev/pts/0
lrwx------ 1 root root 64 Dec 5 09:27 2 -> /dev/pts/0
どういう場合にこれが起こるのか?
プロセスが削除済みのファイルを使う真っ当な理由はただ一つ、プロセスが終了した時に自動解放されるファイル = テンポラリファイルを作成したい場合です。
たとえば、データベース(MySQLなど)の一時表領域を利用すると発生するのを確認しています。要するに、クソデカ重いSELECT文を発行するとこういったファイルができます。そしてそのクエリが大量に詰まると巨大テンポラリファイルでファイルシステムが詰まります。(そして障害発生します)
あとは長時間走るプログラムでファイルをクローズし忘れても発生するかもしれません。(遭遇したことはないですが)
lsof +L1
の +L1
とはどういう意味か?
$ man lsof
NAME
lsof - list open files
...
+|-L [l] enables (`+') or disables (`-') the listing of file link counts, where they are available - e.g., they aren't available for
sockets, or most FIFOs and pipes.
When +L is specified without a following number, all link counts will be listed. When -L is specified (the default), no link
counts will be listed.
When +L is followed by a number, only files having a link count less than that number will be listed. (No number may follow
-L.) A specification of the form ``+L1'' will select open files that have been unlinked. A specification of the form
``+aL1 <file_system>'' will select unlinked open files on the specified file system.
For other link count comparisons, use field output (-F) and a post-processing script or program.
When +L is followed by a number, only files having a link count less than that number will be listed.
A specification of the form ``+L1'' will select open files that have been unlinked.
+L1
だとリンク数1未満(=0)のファイル、要するに削除済みのファイルが表示されるという意味だそうです。
まとめ
- ディスクが何故か埋まっている時は、削除済だがプロセスが掴んだままのファイルを
lsof +L1
でチェックしよう - プロセスが巨大テンポラリファイルを作成する原因がないか考えよう