2026年9月26日

2026年9月26日

ディスク容量アラートのシェルスクリプト|df・inode監視とcronで通知する

ディスクの空きが無くなると、WordPress は画像のアップロードやプラグイン更新に失敗し、MySQL が書き込めずにサイトごと止まることもあります。この記事では、df で使用率と inode を調べ、しきい値を超えたらメールや Slack に通知するシェルスクリプトを作り、cron で定期実行するところまでを説明します。通知が鳴ったときにどこを調べればよいかも最後にまとめます。

監視する値は「使用率」と「inode」の2つ

ディスク監視というと容量(GB)だけを見がちですが、実際には次の2つを見る必要があります。

  • ブロックの使用率(df -P の Capacity 列): いわゆる空き容量です。
  • inode の使用率(df -Pi の IUse% 列): ファイルの個数の上限です。容量に余裕があっても、キャッシュファイルや PHP のセッションファイルが大量にできると inode が尽き、「No space left on device」になります。

もうひとつ知っておきたいのが、ext4 では既定で一定割合のブロックが root 用に予約されている点です。一般ユーザーから見ると「まだ数 GB 空いているのに書けない」状況が起こるため、しきい値は満杯の手前、80〜90% 程度に置き、余裕をもって気づけるようにします。

スクリプト本体

次のスクリプトは、ファイルシステムごとに使用率と inode を調べ、しきい値以上なら通知します。同じ警告を10分ごとに送り続けないよう、状態ファイルで「通知済み」を記録し、6時間経ったら再通知、回復したら回復通知を送ります。

#!/usr/bin/env bash
# /usr/local/bin/disk-alert.sh
set -u

# 通知先などの設定は別ファイルに置く(権限 600)
[ -r /etc/default/disk-alert ] && . /etc/default/disk-alert

THRESHOLD="${DISK_ALERT_THRESHOLD:-85}"        # 使用率(%)がこの値以上で警告
INODE_THRESHOLD="${DISK_ALERT_INODE_THRESHOLD:-85}"
REMIND_MIN="${DISK_ALERT_REMIND_MIN:-360}"      # 警告が続くとき再通知する間隔(分)
STATE_DIR=/var/lib/disk-alert
WEBHOOK_URL="${DISK_ALERT_WEBHOOK:-}"
MAIL_TO="${DISK_ALERT_MAIL:-}"
HOST=$(hostname -f 2>/dev/null || hostname)

mkdir -p "$STATE_DIR"

notify() {
  local msg="$1"
  logger -t disk-alert "$msg"
  if [ -n "$WEBHOOK_URL" ]; then
    curl -fsS -m 10 -X POST -H 'Content-Type: application/json' \
      --data "$(printf '{"text":"%s"}' "$msg")" "$WEBHOOK_URL" >/dev/null \
      || logger -t disk-alert "webhook send failed"
  fi
  if [ -n "$MAIL_TO" ] && command -v mail >/dev/null 2>&1; then
    printf '%s\n' "$msg" | mail -s "[disk-alert] $HOST" "$MAIL_TO" \
      || logger -t disk-alert "mail send failed"
  fi
}

# $1=種別(disk/inode) $2=しきい値 $3以降=df に渡す追加オプション
check() {
  local kind="$1" limit="$2"
  shift 2
  df -P "$@" -x tmpfs -x devtmpfs -x squashfs -x overlay | tail -n +2 |
  while read -r fs size used avail pcent mount; do
    p="${pcent%\%}"
    [ "$p" = "-" ] && continue            # inode の概念が無いファイルシステム
    key="$STATE_DIR/${kind}_$(printf '%s' "$mount" | tr '/ ' '__')"
    if [ "$p" -ge "$limit" ]; then
      if [ ! -e "$key" ] || [ -n "$(find "$key" -mmin +"$REMIND_MIN" 2>/dev/null)" ]; then
        notify "WARN ${HOST} ${kind} ${mount} ${p}% (threshold ${limit}%)"
        touch "$key"
      fi
    elif [ -e "$key" ]; then
      notify "OK ${HOST} ${kind} ${mount} recovered to ${p}%"
      rm -f "$key"
    fi
  done
}

check disk "$THRESHOLD"
check inode "$INODE_THRESHOLD" -i

各部分の意図

  • **df -P**: POSIX 形式を指定すると、デバイス名が長くても1ファイルシステム1行に収まります。指定しないと行が折り返され、read で列がずれることがあります。
  • **-x tmpfs など**: メモリ上の一時領域や Docker の overlay は監視対象から外しています。Docker ホストで overlay も見たい場合は外してください。-x は GNU coreutils の df のオプションなので、BSD 系や BusyBox では書き換えが必要です。
  • **read -r ... mount**: 最後の変数に行の残りがすべて入るため、マウントポイント名に空白があっても崩れません。
  • 状態ファイル: /var/lib/disk-alert/ に「通知済み」の印を置き、find -mmin で経過時間を見て再通知を判断します。これが無いと、満杯のあいだ cron の間隔ごとに通知が届き、肝心の通知が埋もれます。
  • 通知の失敗: curl や mail が失敗してもスクリプトを止めず、logger で syslog(journal)に記録します。あとで journalctl -t disk-alert で確認できます。

メッセージは英数字だけにしてあります。JSON を printf で組み立てているため、メッセージに " や改行を入れると壊れるからです。日本語や複数行を送りたい場合は、jq -n --arg t "$msg" '{text:$t}' のように jq で JSON を作るほうが安全です。

通知先の設定ファイル

Webhook の URL は、それ自体が投稿権限を持つ秘密情報です。スクリプトに直書きせず、root だけが読める設定ファイルに分けます。

sudo install -m 600 -o root -g root /dev/null /etc/default/disk-alert
sudoedit /etc/default/disk-alert
# /etc/default/disk-alert
DISK_ALERT_WEBHOOK="https://hooks.slack.com/services/your-webhook-path"
DISK_ALERT_MAIL="ops@example.com"

Slack の Incoming Webhook はアプリを作成して発行します(2026年10月現在)。作成手順や画面は変わることがあるので、Slack の公式ドキュメントに従ってください。メールで送る場合は、サーバーから外部へメールが届く状態か(mail コマンドの有無、SPF などで迷惑メール扱いされないか)を先に確認します。

動作確認

設置したら、しきい値を一時的に下げて通知が届くか試します。

sudo install -m 755 disk-alert.sh /usr/local/bin/disk-alert.sh
sudo env DISK_ALERT_THRESHOLD=1 /usr/local/bin/disk-alert.sh
journalctl -t disk-alert -n 20
sudo rm -f /var/lib/disk-alert/*     # テストの「通知済み」印を消す

sudo は既定で環境変数を引き継がないため、env を挟んで渡しています。また、/etc/default/disk-alert に DISK_ALERT_THRESHOLD を書くと、読み込み順の関係でコマンドラインの値より設定ファイルが優先されます。しきい値を変えたいときは設定ファイルに書いてかまいませんが、テストの際はその行を一時的にコメントアウトしてください。

最後の rm を忘れると、本番のしきい値に戻したときに「回復」通知が来ます。それ自体は回復通知の動作確認にもなるので、一度見ておくと安心です。

cron で定期実行する

/etc/cron.d/disk-alert を作り、10分ごとに実行します。

# /etc/cron.d/disk-alert
*/10 * * * * root /usr/bin/flock -n /run/disk-alert.lock /usr/local/bin/disk-alert.sh

flock -n は、前回の実行がネットワークマウントの応答待ちなどで終わっていないとき、次の実行を重ねずにスキップするためのものです。NFS がハングすると df 自体が固まることがあり、重複実行が積み重なると負荷の原因になります。

/etc/cron.d/ のファイルは、ユーザー名の列が必要な点と、ファイル名にドットを含めると実行されないディストリビューションがある点でつまずきやすいです。動いているかは journalctl -u cron(RHEL 系では crond)で確認できます。cron 自体が止まっていると監視も止まるので、ジョブが動いていることを外から見張る仕組み(ハートビート監視)を併用すると安心です。

systemd timer を使う構成でも、同じスクリプトをそのまま ExecStart に指定できます。

アラートが鳴ったら何を見るか

通知が来たら、まず何が増えたのかを特定します。消してよいかの判断はその後です。消す前にバックアップの有無も確認してください。

大きいディレクトリを探す

sudo du -xh --max-depth=1 / 2>/dev/null | sort -h | tail -n 15
sudo du -xh --max-depth=1 /var 2>/dev/null | sort -h | tail -n 15

-x で別のファイルシステムに降りないようにしているので、どのマウントポイントが圧迫されているかと対応させて見られます。WordPress のサーバーで増えやすいのは次の場所です。

  • ログ: /var/log/ の Web サーバーログや、WP_DEBUG_LOG を有効にしたまま肥大化した wp-content/debug.log。journal は journalctl --disk-usage で確認し、journalctl --vacuum-size=500M のように上限まで縮められます。ローテーション設定の見直しが根本対策です。
  • バックアップ: バックアップ系プラグインがサーバー内に作るアーカイブ。保存先を外部ストレージに変え、世代数を絞ります。
  • アップロード画像: wp-content/uploads/ の生成済みサムネイルが積み重なっているケース。
  • パッケージキャッシュや Docker: apt clean、docker system df で確認できます。

削除したのに空かない

ファイルを消しても使用率が下がらない場合、プロセスがそのファイルを開いたままになっています。

sudo lsof +L1

ここに出たプロセス(多くはログを書いている Web サーバーや PHP-FPM)を再起動するか、ログならローテーションの仕組みで開き直させると領域が解放されます。

inode が尽きている

sudo du --inodes -x --max-depth=1 /var 2>/dev/null | sort -n | tail -n 15

PHP のセッションディレクトリ、キャッシュプラグインのファイル、メールキューなど、小さいファイルが大量にできる場所を探します。削除しても再発する場合は、そのファイルを作っている側の設定(セッションのガベージコレクション、キャッシュの有効期限)を直します。

ディスクそのものが足りないと分かった場合は、容量の追加やボリュームの拡張を検討します。拡張の前にスナップショットを取っておくと、作業ミスがあっても戻れます。

まとめ

  • ディスク監視は使用率と inode の両方を見る。しきい値は 80〜90% 程度に置く。
  • df -P で行を固定し、状態ファイルで通知の連発を防ぎ、回復通知も送る。
  • Webhook URL などの秘密情報は権限 600 の設定ファイルに分ける。
  • cron では flock -n で重複実行を防ぎ、しきい値を下げて通知をテストしてから運用する。
  • 通知が来たら du -x・lsof +L1・du --inodes で原因を特定してから対処する。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲