Red Hatの森若です。
systemctlコマンドでサービスを起動すると、予期しないエラーが出力されます。しかし操作は成功しているし、df等でファイルシステムを見ても余裕があります。 今回はこの状況で何が起きていたのか見てみます。
# systemctl start httpd.service Error: No space left on device
inotifyとは?
linuxにはinotifyという機能があり、ファイルやディレクトリ等への操作をイベントとして取得することができます。 inotifyではアプリケーションがファイルとして「inotify instance」を用意し、inotify instanceにイベントに対応する「inotify watch」を複数登録します。 inotify watchがイベントを検出するごとに、inotify instanceのキューにイベントを追記していきます。 アプリケーションがinotify instanceから読み込みを行うと、イベントの種類やファイル名などを取得できる仕組みです。
inotifyはlinux特有の仕組み(現在のUNIX系OSはそれぞれ同様の目的の仕組みを持っていますが標準化はされていません)ですが、systemctlを含む多数のソフトウェアでごく一般的に利用されています。
今回の現象の原因
一部のアプリケーションは非常に多くのinotify watchを利用するため、システム全体の利用上限に触れる場合があります。今回問題が発生したのはこのケースでした。 inotify はkernel内部でファイルシステムの枠組みを利用しているため、エラー時には "No space left on device"(ENOSPC) のようなファイルシステムに関連するエラーを返す場合があります。
linux/fs/notify/inotify/inotify_user.c より
static int inotify_new_watch(struct fsnotify_group *group, struct inode *inode, u32 arg) { (中略) ret = inotify_add_to_idr(idr, idr_lock, tmp_i_mark); # 新しいinotify watchを追加する if (ret) goto out_err; /* increment the number of watches the user has */ if (!inc_inotify_watches(group->inotify_data.ucounts)) { # inotify watchのカウンタを増やす inotify_remove_from_idr(group, tmp_i_mark); ret = -ENOSPC; # 失敗するとENOSPC goto out_err; } (以下略)
inotifyの上限設定
「inotify instance」と「watch」のどちらにもシステム全体とユーザ毎の上限があり、sysctlで設定・読み出しできます。inotify watch の上限数はシステムの搭載メモリから自動的に計算されます。
例:
$ sudo sysctl -a |grep inotify fs.inotify.max_queued_events = 16384 fs.inotify.max_user_instances = 128 fs.inotify.max_user_watches = 28587 user.max_inotify_instances = 128 user.max_inotify_watches = 28587
それぞれは以下のような意味です。
項目 | 意味 |
---|---|
fs.inotify.max_queued_events | 1つのinotify instanceで保持するイベント数の上限 |
fs.inotify.max_user_instances | (システム全体での) inotify instance数の上限 |
fs.inotify.max_user_watches | (システム全体での) inotify watch数の上限 |
user.max_inotify_instances | (1 UIDあたりの) inotify instance数の上限 |
user.max_inotify_watches | (1 UIDあたりの) inotify watch数の上限 |
inotifyの利用状況
inotify を利用しているプログラムには対応するファイルディスクリプタがあり、lsofで見つけられます。
例: inotify instanceに対応するファイルディスクリプタをみつける
# lsof -p 1|grep inotify systemd 1 root 6r a_inode 0,14 0 10890 inotify systemd 1 root 11r a_inode 0,14 0 10890 inotify systemd 1 root 13r a_inode 0,14 0 10890 inotify systemd 1 root 20r a_inode 0,14 0 10890 inotify systemd 1 root 21r a_inode 0,14 0 10890 inotify systemd 1 root 22r a_inode 0,14 0 10890 inotify
inotify watchの利用状況を見るには、 /proc/PID/fdinfo/FD 以下を見ます。 inotify instanceに対応するファイルディスクリプタの/proc/PID/fdinfo/FD には、"inotify"で初まる行が含まれていて、inotify watchの情報が含まれています。
例: inotify instanceに対応するfdinfoには対応するinotify watchの情報が含まれる
# cat /proc/1/fdinfo/6 pos: 0 flags: 02004000 mnt_id: 15 ino: 10890 inotify wd:38c ino:850 sdev:1a mask:2 ignored_mask:0 fhandle-bytes:8 fhandle-type:fe f_handle:5008000000000000 inotify wd:38b ino:838 sdev:1a mask:2 ignored_mask:0 fhandle-bytes:8 fhandle-type:fe f_handle:3808000000000000 inotify wd:38a ino:bd1 sdev:1a mask:2 ignored_mask:0 fhandle-bytes:8 fhandle-type:fe f_handle:d10b000000000000 inotify wd:389 ino:bb9 sdev:1a mask:2 ignored_mask:0 fhandle-bytes:8 fhandle-type:fe f_handle:b90b000000000000 inotify wd:388 ino:d9f sdev:1a mask:2 ignored_mask:0 fhandle-bytes:8 fhandle-type:fe f_handle:9f0d000000000000 inotify wd:387 ino:d87 sdev:1a mask:2 ignored_mask:0 fhandle-bytes:8 fhandle-type:fe f_handle:870d000000000000 (以下略)
全プロセスから参照されているinotify watchを確認するには cat /proc/*/fdinfo/*|grep inotify|sort|uniq|wc -l
のようにします。
実行中にもファイルのopen/closeやプロセスの作成・終了などが行われていますので多少エラーがでますがおおまかな
現在の利用数を取得できます。
# cat /proc/*/fdinfo/*|grep inotify|sort|uniq|wc -l cat: /proc/39155/fdinfo/255: No such file or directory (中略) cat: /proc/thread-self/fdinfo/3: No such file or directory 369
この例は最小限+GUI環境のRHEL 9環境で取得したので369件と少ないですが、 問題が発生するような場合は先に fs.inotify.max_user_watches で確認した数に近い利用数を確認できます。
利用件数が多い場合、以下のようなコマンドでinotify watchの利用数が多いプロセスを搾り込めます。
# grep inotify /proc/*/fdinfo/*| cut -d/ -f3|uniq -c|sort -n (中略) 104 4880 138 3972 1888 216722 3875 988006 8362 4804 25398 4294 # PID 4294が25398件(重複込み)のinotify watchを利用
対策
inotify watchが不足する場合、単純に fs.inotify.max_user_watches の値を大きくすることで対応します。 設定変更自体でメモリ消費等が増えることはなく、実際にinotify watchが利用された場合にだけ消費されます。