inotifywaitとincronによるLinuxリアルタイムファイルシステム監視:ファイルイベントでスクリプトを自動実行する

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

課題:すべてのファイルを手動で監視することはできない

こんな状況を想像してください。デプロイスクリプトが設定ファイルを/etc/app/に配置したのに、サービスがリロードされない。あるいはユーザーが/var/uploads/にファイルをアップロードしたのに、誰かが手動で処理ジョブを起動するまで何も起きない。結果として、毎分cronでポーリングするか(無駄でレスポンスも遅い)、ゼロからデーモンコードを書くかという選択を迫られます。

10台以上のLinux VPSを3年間管理してきた経験から言えば、ファイルが変更されたその瞬間に反応するリアクティブな自動化は、定期的なポーリングよりもはるかに信頼性が高いことがわかりました。Linuxカーネルはすでにすべてのファイルシステムイベントを追跡しています。あとは適切なツールを使ってその情報を引き出すだけです。

そこで登場するのがinotifyinotifywait、そしてincronです。デーモンコード不要で、ファイルイベントに即座に反応できます。

基本概念:Linuxがファイルイベントを追跡する仕組み

inotifyカーネルサブシステム

Linuxカーネル2.6.13で導入されたinotify(inode notify)は、ポーリングやタイマーを使わずに、プロセスがファイルやディレクトリの特定のイベントを監視できる仕組みです。何かが変更された瞬間に、カーネルが通知をプッシュします。

サポートされているイベントは以下のとおりです:

  • IN_CREATE — ファイルまたはディレクトリが作成された
  • IN_MODIFY — ファイルに書き込みがあった
  • IN_DELETE — ファイルまたはディレクトリが削除された
  • IN_MOVED_FROM / IN_MOVED_TO — ファイルがリネームまたは移動された
  • IN_CLOSE_WRITE — 書き込み可能なファイルがクローズされた(アップロード完了の検出に非常に便利)
  • IN_ATTRIB — メタデータが変更された(パーミッション、所有者、タイムスタンプ)

inotifywait:コマンドラインからinotifyを使う

inotifywaitinotify-toolsパッケージに含まれています。イベントが発生するまでブロックし、イベントを出力して終了します。-mフラグを使えば継続的にループします。whileループで囲めば、シンプルでスクリプト化しやすいファイル監視ツールの完成です。

incron:inotify + cron = incron

incronは、時刻ではなくファイルシステムイベントでトリガーされるcronだと考えてください。incrontabファイルにルールを定義すると、イベントが発生したときにincrondが自動的にコマンドを実行します。シェルループも手動再起動も不要で、バックグラウンドでデーモンとして動作します。

実践

ステップ1:ツールのインストール

Debian/Ubuntuの場合:

sudo apt update
sudo apt install inotify-tools incron

RHEL/AlmaLinux/Rockyの場合:

sudo dnf install inotify-tools incron

ステップ2:inotifywaitで動作確認

自動化を組み込む前に、必ずイベントを手動で確認しましょう。ターミナルを2つ開きます。

ターミナル1 — 監視を開始:

inotifywait -m -r -e create,modify,delete /tmp/watch-test/

ターミナル2 — イベントを発生させる:

mkdir -p /tmp/watch-test
touch /tmp/watch-test/hello.txt
echo "data" >> /tmp/watch-test/hello.txt
rm /tmp/watch-test/hello.txt

以下のような出力が表示されます:

/tmp/watch-test/ CREATE hello.txt
/tmp/watch-test/ MODIFY hello.txt
/tmp/watch-test/ DELETE hello.txt

これで、カーネルがそのディレクトリのイベントを正しく報告していることが確認できます。以前、inotifyが実際には監視していない場所を指すシンボリックリンクのせいで痛い目を見たことがあります。このクイックテストで、本番環境で問題が起きる前に気づけます。

ステップ3:inotifywaitでシェルループウォッチャーを書く

軽量な単発の自動化であれば、シェルスクリプトが最も手っ取り早い方法です:

#!/bin/bash
# watch-uploads.sh — ファイルが届いたらすぐに処理する

WATCH_DIR="/var/uploads"
PROCESS_SCRIPT="/opt/scripts/process-file.sh"

inotifywait -m -e close_write --format '%f' "$WATCH_DIR" | while read FILENAME; do
    echo "[$(date)] 検出: $FILENAME"
    "$PROCESS_SCRIPT" "$WATCH_DIR/$FILENAME"
done

重要なポイント:アップロードされたファイルにはmodifyではなくclose_writeを使いましょう。modifyイベントはwriteシステムコールのたびに発火します。500MBのファイルをコピーすると、転送中に何百ものイベントが発生します。close_writeは書き込み後にファイルハンドルがクローズされたとき、正確に1回だけ発火します。はるかにすっきりしています。

このスクリプトをsystemdサービスとして実行すれば、再起動後も継続します:

# /etc/systemd/system/watch-uploads.service
[Unit]
Description=/var/uploadsの新規ファイルを監視
After=network.target

[Service]
ExecStart=/opt/scripts/watch-uploads.sh
Restart=on-failure
User=www-data

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now watch-uploads

ステップ4:システムレベルのファイルトリガーにincronを設定する

incronは、設定のリロード、ログローテーションのトリガー、各ユーザーが自分のincrontabを管理するマルチユーザー環境など、永続的なデーモン管理のルールに威力を発揮します。

まず、ユーザー(またはroot)がincronを使えるようにします:

echo "root" | sudo tee -a /etc/incron.allow

rootのincrontabを編集します:

sudo incrontab -e

構文:<パス> <イベント> <コマンド>

# 設定が変更されたらnginxをリロード
/etc/nginx/conf.d IN_CLOSE_WRITE systemctl reload nginx

# /var/log/archive/に新しいログファイルが置かれたら圧縮
/var/log/archive IN_CREATE gzip $@/$#

# 機密ディレクトリからファイルが削除されたらアラート
/etc/cron.d IN_DELETE /opt/scripts/alert-cron-delete.sh $#

incronの特殊変数:

  • $@ — 監視ディレクトリのパス
  • $# — イベントをトリガーしたファイル名
  • $$ — リテラルの$記号
  • $% — 文字列形式のイベントフラグ

incrondを有効化して起動します:

sudo systemctl enable --now incrond
sudo systemctl status incrond

ステップ5:再帰的な監視とイベントのフィルタリング

よくある落とし穴:incronはデフォルトではサブディレクトリを監視しません。再帰的な監視にはinotifywait -rの方が適しています。incronの場合は、サブディレクトリごとにルールを追加するか、新しいウォッチを動的に登録するラッパーを書く必要があります。

inotifywaitでファイル名パターンによるフィルタリングgrepで簡単にできます:

inotifywait -m -e close_write --format '%f' /var/uploads | \
  grep --line-buffered '\.csv$' | \
  while read FILENAME; do
    /opt/scripts/import-csv.sh "/var/uploads/$FILENAME"
done

grepに--line-bufferedを忘れずに付けましょう。これがないと、grepは出力をチャンク単位でバッファリングするため、スクリプトが待機したまま止まったように見えます。inotifywaitの出力をgrepにパイプするとき、ほぼ全員が最初にこれでつまずきます。

ステップ6:連続イベントのデバウンス処理

エディタやツールの中には、ファイルを複数のステップで書き込むものがあります。一時ファイルへの書き込み、リネーム、元のファイルの削除といった具合です。論理的には1回の保存なのに、イベントが大量に発生します。bashでのシンプルなデバウンス処理を紹介します:

#!/bin/bash
WATCH_DIR="/etc/app"
DEBOUNCE=2  # 最後のイベントから待機する秒数

inotifywait -m -e close_write,moved_to --format '%f' "$WATCH_DIR" | \
while read FILENAME; do
    # キューに入っているリロードをキャンセル
    [ -n "$DEBOUNCE_PID" ] && kill "$DEBOUNCE_PID" 2>/dev/null
    (
        sleep $DEBOUNCE
        echo "[$(date)] 変更を検出してリロード: $FILENAME"
        systemctl reload myapp
    ) &
    DEBOUNCE_PID=$!
done

これは最後のイベントから2秒待ってからアクションを実行します。設定ファイルを4〜5回連続で書き換えるローリングデプロイ中でも、5回ではなく正確に1回だけリロードされます。

実践的なヒント:inotifyのウォッチ数制限を確認する

inotifyのウォッチはそれぞれカーネルリソースを消費します。デフォルトの制限はユーザーあたり8,192ウォッチです。監視ディレクトリが多いサーバーでは、すぐにこの上限に達することがあります:

# 現在の制限を確認
cat /proc/sys/fs/inotify/max_user_watches

# 一時的に増やす
sudo sysctl fs.inotify.max_user_watches=524288

# 永続的に設定する
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.d/99-inotify.conf
sudo sysctl -p /etc/sysctl.d/99-inotify.conf

この制限は見落としがちです。Node.jsアプリ(ホットリロードにinotifyを多用する)とincronルールを同時に動かしていたVPSで、気づかないうちに上限に達したことがありました。incronはエラー出力もなく静かにトリガーを停止するだけでしたdmesg | grep inotifyを実行してすぐに原因がわかりました。本番環境にデプロイする前に必ず確認しましょう。

どのツールをいつ使うか

  • ループ内のinotifywait:クイックスクリプト、開発の自動化、単一目的のウォッチャー。インタラクティブなテストが容易。
  • incron:デーモンとして管理したいシステムレベルのルール。サービスの自動リロードやログ圧縮などのOps作業に最適。すべてのルールが監査可能なincrontabで一元管理される。
  • PythonのwatchdogライブラリA:Pythonアプリケーション内でのクロスプラットフォームなファイル監視。inotifyをクリーンなAPIでラップし、プラットフォームの差異を透過的に処理する。

inotifyを実際に活用する

毎分cronによるポーリングからイベント駆動のファイル監視に切り替えると、レイテンシが最大60秒からミリ秒単位に短縮されます。また、不要なCPUウェイクアップも排除されます。カーネルからシグナルが来るまでプロセスはスリープしています。

小さく始めましょう。1つのディレクトリを選び、1日inotifywait -mで監視して、イベントを観察します。自動化できる機会はすぐに見えてきます。リロードが必要な設定ファイル、処理ジョブを待っているアップロード、静かに変更されてはいけない機密パスなど。

スクリプティング向けのinotifywaitとデーモン管理ルール向けのincronを組み合わせれば、Cを1行も書かず、重量級の監視エージェントをデプロイすることなく、実際のファイル監視ニーズの90%をカバーできます。

Share: