年末年始のサーバー停止を防ぐCPU・メモリ監視スクリプト自作ガイド|閾値超過Slack通知とプロセス特定自動化

スクリプト設計

年末年始やゴールデンウィークといった長期休暇中、エンジニアを最も凍りつかせるトラブルが「原因不明のサーバー無応答」や「OOM Killer(Out of Memory Killer)によるWebサーバー・DBサーバーのサイレント停止」です。

休暇中にサーバーが落ち、障害調査のために慌ててSSH接続したものの、すでに負荷が下がっていて原因プロセスが分からなかったり、ログを延々と遡って初動が大幅に遅れたりした経験はないでしょうか。あるいは、「休み中も心配だから」と手作業で毎日何度も top や free を叩いてリソースを確認していませんか。

断言しますが、「手作業は負け」です。

中小規模のWebサービスやVPS(さくらのVPS、ConoHa、Linode等)、社内検証環境では、高機能な監視SaaSや重量級の監視エージェントを導入するコストや運用リソースを割けないケースが多々あります。しかし、Linux標準のBashスクリプトが1本あれば、CPUとメモリの使用率を定期監視し、閾値(80%など)を超えた瞬間に「原因となっている上位プロセス一覧」を自動抽出してSlackへ即座に通知する仕組みを極めて軽量かつ堅牢に構築できます。

本記事では、案内役のBash玄(ばっしゅげん)が、現場で培った「誤検知しないリソース算出ロジック」「通知スパム・共連れダウンを防ぐ安全機構」「コピペで即稼働する完成版シェルスクリプト」を徹底解説します。手作業による監視から脱却し、安心して休暇を過ごせる自動運用体制を整えましょう。


  1. 監視スクリプトの全体設計と処理フロー
    1. リソース監視からSlack通知までの処理パイプライン
    2. 障害調査を劇的に短縮するプロセス特定機能の重要性
  2. コピペで即動く!完成版監視シェルスクリプト
    1. スクリプトの主要設定パラメータ一覧
    2. 完成版監視スクリプト(Bash)のコードブロック
    3. 各処理ブロックの詳細解説(リソース計算・プロセス抽出・Slack連携)
      1. 1. メモリ計算ロジック
      2. 2. CPU計算ロジック
      3. 3. プロセス抽出とSlack連携
  3. 実務で役立つカスタマイズと安全設計のポイント
    1. アラートの多重送信(通知スパム)を防ぐクーリングタイム(flockやタイムスタンプ判定)
    2. cronへの登録手順とログ出力設定
      1. 1. 実行権限の付与
      2. 2. crontabの編集
      3. 3. ログローテーションの設定(必須)
    3. メモリ枯渇時の最終防衛ライン(スワップ領域の追加とswappiness設定の注意点)
      1. スワップファイルの作成手順
      2. swappiness設定の最適化
  4. よくある落とし穴とトラブルシューティング
    1. freeのfreeではなくavailableを見ないと誤検知する罠(バッファ/キャッシュの扱い)
    2. cron実行時に環境変数(PATH等)が通らずcurlやpsが失敗する問題とフルパス指定
      1. 対策
    3. Slack通知のペイロード内特殊文字(改行やクォート)のJSONエスケープ対策(jqやセーフ展開)
      1. 対策1: jqコマンドを活用した安全なペイロード生成(推奨)
      2. 対策2: jqがインストールされていない軽量環境でのセーフ展開
  5. まとめ:自作スクリプトで安心の無人運用体制を整える
  6. 関連記事(内部リンク)
  7. 関連記事

監視スクリプトの全体設計と処理フロー

自作スクリプトでサーバー監視を行う場合、単に「CPUが80%を超えた」「メモリが90%に達した」という数値だけを通知しても現場では役に立ちません。実務で真に求められるのは、「何がリソースを食い潰しているのか」を一目で判別できる仕組みです。

まずは、本スクリプトがどのようなパイプラインで動作し、なぜプロセス特定が重要なのか、その設計思想を解説します。

リソース監視からSlack通知までの処理パイプライン

本スクリプトは、Linux標準のcronによって定期実行(例: 5分ごと)されることを前提としています。軽量性を最優先としつつ、誤報やシステムへの二次被害を防ぐため、以下の5段階のパイプラインで処理を実行します。

[1. 二重起動防止 (flock)]
        │ 既に前回の処理が稼働中の場合は即座に安全終了
        ▼
[2. リソース正確計測]
        │ ・メモリ: free -m から available を加味して真の枯渇率を算出
        │ ・CPU   : top の2周目サンプリングから実効負荷を取得
        ▼
[3. 閾値判定 (CPU/MEM)]
        │ 閾値(初期値: 各80%)を超過していなければ何事もなく終了
        ▼
[4. 原因プロセスの即時特定]
        │ psコマンドで負荷上位5件(PID、ユーザー、消費率、コマンド名)を捕捉
        ▼
[5. クーリングタイム判定 & Slack通知]
        │ 前回の発報から指定時間(例: 30分)以内なら通知を抑制
        └─ 新規アラートであれば整形したJSONペイロードをWebhook送信

このように、閾値を検知した瞬間にプロセス状態のスナップショットを撮り、通知スロットリング(クーリングタイム)を経てSlackへ配信します。

障害調査を劇的に短縮するプロセス特定機能の重要性

監視スクリプトにおいて最も重要な付加価値が、「閾値超過時のプロセス特定」です。

深夜や休日にスマートフォンへ「CPU使用率 92% 超過」という無機質なアラートだけが届いた場面を想像してください。管理者はノートPCを開き、VPNを繋ぎ、SSHログインして top や ps を叩く必要があります。しかし、突発的なバッチ処理やクロール負荷の場合、ログインした頃にはCPU使用率が平常に復帰しており、「原因不明の突発負荷」として片付けられてしまうことが少なくありません。

本スクリプトでは、閾値を超えたそのミリ秒単位のタイミングで以下のコマンドを実行し、負荷上位5件のプロセス情報を通知メッセージに自動添付します。

  • メモリ超過時: ps aux --sort=-%mem | head -n 6
  • CPU超過時: ps aux --sort=-%cpu | head -n 6

通知を受け取った時点で「PHP-FPMの特定プロセスがメモリリークしているのか」「Nginxへの不正アクセスでCPUが逼迫しているのか」「MySQLの重い集計クエリが暴走しているのか」が即座に判明します。調査の初動をゼロ秒に短縮できることこそ、現場仕様の自動化がもたらす最大の恩恵です。


コピペで即動く!完成版監視シェルスクリプト

それでは、本番環境のLinux(Ubuntu、Debian、AlmaLinux、Rocky Linuxなど)でそのまま動作する完成版スクリプトを提示します。

スクリプトの主要設定パラメータ一覧

スクリプト冒頭に定義している設定値の意味は以下のとおりです。環境に合わせて調整してください。

パラメータ名 初期値 解説
WEBHOOK_URL https://hooks.slack.com/... SlackのIncoming Webhook URLを設定します。
MEM_THRESHOLD 80 メモリ使用率の監視閾値(パーセント・整数値)。
CPU_THRESHOLD 80 CPU使用率の監視閾値(パーセント・整数値)。
COOL_DOWN_MINUTES 30 同一障害による通知スパムを防ぐクールダウン時間(分)。
LOCK_FILE /tmp/res_alert.lock flock で利用する排他制御用ロックファイル。
COOL_DOWN_FILE /tmp/res_alert_last_sent 最終通知時刻(UNIX秒)を記録するファイル。
LOG_FILE /var/log/server_monitor.log スクリプトの動作状況を記録するログファイル。

完成版監視スクリプト(Bash)のコードブロック

サーバー上の任意の場所(例: /usr/local/bin/server_monitor.sh)に以下の内容を保存してください。

#!/usr/bin/env bash
set -euo pipefail

# ==============================================================================
# サーバーリソース監視&Slack通知スクリプト
# 案内役: Bash玄(ばっしゅげん) - "手作業は負け"
# ==============================================================================

# cron実行時の環境変数不足を防ぐため、安全なPATHを明示的に定義
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# ロケール差異によるtopやawkの数値フォーマット崩れ(小数点のカンマ区切り等)を防止
export LC_ALL=C

# ------------------------------------------------------------------------------
# 1. ユーザー設定パラメータ
# ------------------------------------------------------------------------------
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
MEM_THRESHOLD=80
CPU_THRESHOLD=80
COOL_DOWN_MINUTES=30
LOCK_FILE="/tmp/res_alert.lock"
COOL_DOWN_FILE="/tmp/res_alert_last_sent"
LOG_FILE="/var/log/server_monitor.log"

# ------------------------------------------------------------------------------
# 2. ログ出力用共通関数
# ------------------------------------------------------------------------------
log_msg() {
  local level="$1"
  local message="$2"
  local timestamp
  timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
  echo "${timestamp} [${level}] ${message}" >> "$LOG_FILE"
}

# ------------------------------------------------------------------------------
# 3. 排他制御(二重起動防止)
# ------------------------------------------------------------------------------
# ファイルディスクリプタ200を用いてロックファイルを取得
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
  log_msg "WARN" "前回の監視スクリプトがまだ実行中のため、多重起動を防止しスキップしました。"
  exit 0
fi

# ------------------------------------------------------------------------------
# 4. クーリングタイム判定関数
# ------------------------------------------------------------------------------
is_cooled_down() {
  if [[ ! -f "$COOL_DOWN_FILE" ]]; then
    return 0
  fi

  local current_time
  local last_sent_time
  local elapsed_seconds
  local cool_down_seconds

  current_time="$(date +%s)"
  last_sent_time="$(cat "$COOL_DOWN_FILE" 2>/dev/null || echo 0)"
  elapsed_seconds=$(( current_time - last_sent_time ))
  cool_down_seconds=$(( COOL_DOWN_MINUTES * 60 ))

  if (( elapsed_seconds < cool_down_seconds )); then
    return 1 # クールダウン中(通知スキップ)
  fi
  return 0 # 通知可能
}

# ------------------------------------------------------------------------------
# 5. リソース使用率の計測
# ------------------------------------------------------------------------------

# --- [メモリ計算] ---
# free -m の Mem 行から total と available を取得
# 計算式: (total - available) * 100 / total
read -r mem_total mem_available < <(free -m | awk '/^Mem:/{print $2, $7}')

if [[ -z "$mem_total" || -z "$mem_available" || "$mem_total" -eq 0 ]]; then
  log_msg "ERROR" "freeコマンドからのメモリ情報取得に失敗しました。"
  exit 1
fi

mem_used_pct=$(( (mem_total - mem_available) * 100 / mem_total ))

# --- [CPU計算] ---
# topの1周目はOS起動通算平均になるため、-n 2 で2周目のidle値を取得して実効負荷を算出
# 環境差異を吸収するため、awkで %Cpu\(s\) の idle カラムを抽出
cpu_idle="$(top -b -n 2 -d 1 | awk '/%Cpu\(s\)/ {for(i=1;i<=NF;i++) if($i ~ /id/) {gsub(/[^0-9.]/, "", $i); print $i}}' | tail -n 1)"

if [[ -z "$cpu_idle" ]]; then
  # topで取得できなかった場合のフォールバック(vmstatの2行目からid値を取得)
  cpu_idle="$(vmstat 1 2 | tail -n 1 | awk '{print $15}')"
fi

cpu_idle_int="${cpu_idle%.*}"
cpu_used_pct=$(( 100 - cpu_idle_int ))

log_msg "INFO" "リソースチェック完了: CPU=${cpu_used_pct}% (閾値:${CPU_THRESHOLD}%), MEM=${mem_used_pct}% (閾値:${MEM_THRESHOLD}%)"

# ------------------------------------------------------------------------------
# 6. 閾値判定とアラートメッセージ構築
# ------------------------------------------------------------------------------
alert_reasons=()
process_info=""

if (( mem_used_pct >= MEM_THRESHOLD )); then
  alert_reasons+=("【警告】メモリ使用率が閾値を超過しました: ${mem_used_pct}% (閾値: ${MEM_THRESHOLD}%)")
  process_info+="\n[メモリ消費上位プロセス (Top 5)]\n"
  process_info+="$(ps aux --sort=-%mem | head -n 6)\n"
fi

if (( cpu_used_pct >= CPU_THRESHOLD )); then
  alert_reasons+=("【警告】CPU使用率が閾値を超過しました: ${cpu_used_pct}% (閾値: ${CPU_THRESHOLD}%)")
  process_info+="\n[CPU消費上位プロセス (Top 5)]\n"
  process_info+="$(ps aux --sort=-%cpu | head -n 6)\n"
fi

# 閾値超過がない場合は正常終了
if [[ ${#alert_reasons[@]} -eq 0 ]]; then
  exit 0
fi

# ------------------------------------------------------------------------------
# 7. クールダウン判定 & Slack通知送信
# ------------------------------------------------------------------------------
if ! is_cooled_down; then
  log_msg "WARN" "リソース超過を検知しましたが、クーリングタイム(${COOL_DOWN_MINUTES}分以内)のためSlack通知を抑制しました。"
  exit 0
fi

hostname_str="$(hostname)"
server_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || echo "N/A")"

alert_header="*🚨 サーバーリソース警告発報 (${hostname_str} / ${server_ip})*"
alert_body=""
for reason in "${alert_reasons[@]}"; do
  alert_body+="${reason}\n"
done

# Slack送信用ペイロードの生成(特殊文字・改行をJSON安全形式にエスケープ)
raw_text="${alert_header}\n\n${alert_body}\n\`\`\`${process_info}\`\`\`"

if command -v jq >/dev/null 2>&1; then
  json_payload="$(jq -n --arg text "$raw_text" '{"text": $text}')"
else
  # jqコマンドが存在しない場合のセーフ置換
  escaped_text="$(echo -e "$raw_text" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')"
  json_payload="{\"text\": \"${escaped_text}\"}"
fi

# Slack WebhookへPOST送信
http_code="$(curl -s -o /dev/null -w "%{http_code}" -X POST -H 'Content-type: application/json' --data "$json_payload" "$WEBHOOK_URL")"

if [[ "$http_code" == "200" ]]; then
  log_msg "INFO" "Slackへのアラート通知送信に成功しました (HTTP 200)。"
  date +%s > "$COOL_DOWN_FILE"
else
  log_msg "ERROR" "Slackへのアラート通知送信に失敗しました (HTTP Status: ${http_code})。"
fi

exit 0

各処理ブロックの詳細解説(リソース計算・プロセス抽出・Slack連携)

スクリプト内で採用している核となる3つの処理ブロックについて、なぜこの書き方をしているのかを解説します。

1. メモリ計算ロジック

read -r mem_total mem_available < <(free -m | awk '/^Mem:/{print $2, $7}')
mem_used_pct=$(( (mem_total - mem_available) * 100 / mem_total ))

Linuxのメモリ管理では、空きメモリがディスクキャッシュ(Buffers/Cached)として活用されます。そのため、単純に「free」列だけを見ると誤検知を起こします(詳細は後述)。本スクリプトでは、カーネルが算出した「アプリケーションが実際に利用可能な空き容量」である available($7)を抽出し、(total - available) * 100 / total の整数計算を行っています。外部ツールに依存せず、Bashの算術展開だけで高速かつ確実にパーセンテージを導出できる設計です。

2. CPU計算ロジック

cpu_idle="$(top -b -n 2 -d 1 | awk '/%Cpu\(s\)/ {for(i=1;i<=NF;i++) if($i ~ /id/) {gsub(/[^0-9.]/, "", $i); print $i}}' | tail -n 1)"
cpu_used_pct=$(( 100 - cpu_idle_int ))

バッチモード(-b)の top コマンドは、1回目のサンプリング(-n 1)では「OSが起動してからの累積平均CPU使用率」を出力してしまうという仕様上の罠があります。直近のリアルタイム負荷を正確に把握するには、必ず 2回サンプリング(-n 2 -d 1)を行い、2回目の結果を採用 する必要があります。

3. プロセス抽出とSlack連携

ps aux --sort=-%mem | head -n 6
ps aux --sort=-%cpu | head -n 6

--sort オプションに負号(-)を付与することで降順ソートし、ヘッダー行+上位5行の計6行を head -n 6 で切り出しています。これを3連バッククォートで囲んでSlackへ送信することで、スマートフォンの画面でも等幅フォントで崩れずにPIDやコマンド名を閲覧できます。


実務で役立つカスタマイズと安全設計のポイント

運用現場で監視スクリプトを実戦配備する際、絶対に軽視してはならないのが「安全性と継続性」です。スクリプト自身がサーバーを追い詰める凶器にならないための必須ポイントを押さえましょう。

アラートの多重送信(通知スパム)を防ぐクーリングタイム(flockやタイムスタンプ判定)

サーバー監視で初心者が最も陥りがちな失敗が「通知スパムによるサーバー共連れダウン」です。

例えば、深夜にアクセス集中でCPUが95%に達したとします。cronが5分ごとにスクリプトを叩く設定になっている場合、何もしなければ5分おきにSlackへ警告が飛び続けます。担当者が起きて対応するまで、Slackチャンネルは通知で埋め尽くされます。

さらに深刻なのは、「重篤な高負荷状態のサーバー上で、5分ごとにプロセス生成・curl通信・JSON組み立てが走り、サーバーの負荷をさらに押し上げてトドメを刺してしまう」という二次災害です。

これを防ぐのが、本スクリプトに組み込まれた「クーリングタイム判定」と「flock排他制御」です。

  1. クーリングタイム(スロットリング):
    アラート送信成功時に /tmp/res_alert_last_sent へ現在時刻のUNIX秒を書き込みます。次回の実行時、現在時刻との差分が COOL_DOWN_MINUTES(例: 30分)未満であれば、ログに記録を残した上でSlack通知の処理を安全にスキップします。
  2. flockによる多重起動防止:
    万が一、システム負荷が高すぎてスクリプト自体の処理が5分以上かかった場合、次のcronジョブが重複して走り、プロセスの二重起動から雪だるま式にサーバーがハングアップします。exec 200>"$LOCK_FILE" と flock -n 200 を先頭に記述することで、先行プロセスが存在する場合は後続プロセスが待たずに即時終了(ノンブロッキング)します。

cronへの登録手順とログ出力設定

作成したスクリプトを自動実行するため、cronに登録します。

1. 実行権限の付与

まずはスクリプトに実行権限を付与し、rootユーザー(または監視用管理者)で直接テスト実行できることを確認します。

sudo chmod +x /usr/local/bin/server_monitor.sh

2. crontabの編集

rootユーザーのcrontabを編集します。

sudo crontab -e

以下の行を末尾に追加します。5分間隔で定期実行し、標準出力および標準エラー出力をスクリプト内で定義したログファイルとは別に補助ログ(cron実行ログ)へ追記します。

# 5分ごとにリソース監視スクリプトを実行
*/5 * * * * /usr/local/bin/server_monitor.sh >> /var/log/server_monitor_cron.log 2>&1

3. ログローテーションの設定(必須)

監視スクリプトを定期実行し続けると、ログファイル(/var/log/server_monitor.log)が少しずつ肥大化します。「監視ログのせいでディスクが溢れてサーバーが落ちた」となっては本末転倒です。

/etc/logrotate.d/server_monitor を作成し、定期的にローテーションさせましょう。

/var/log/server_monitor*.log {
    weekly
    missingok
    rotate 4
    compress
    delaycompress
    notifempty
    create 0640 root root
}

メモリ枯渇時の最終防衛ライン(スワップ領域の追加とswappiness設定の注意点)

メモリ使用率が急上昇した際、即座にOOM Killerが発動してMySQLやNginxなどの重要デーモンを強制終了してしまう事態を防ぐため、「スワップ(Swap)領域」をセーフティネットとして確保しておくことが鉄則です。

格安VPSやクラウドの初期設定では、スワップが 0MB(無効)になっている環境が少なくありません。物理メモリが枯渇した瞬間に猶予なくプロセスが強制終了されるのを防ぐため、最低でも 2GB〜4GB のスワップファイル を確保しておきましょう。

スワップファイルの作成手順

# 2GBのスワップファイルを作成
sudo fallocate -l 2G /swapfile

# パーミッションをroot専用(600)に制限
sudo chmod 600 /swapfile

# スワップ領域としてフォーマット
sudo mkswap /swapfile

# スワップを有効化
sudo swapon /swapfile

# 再起動後も自動で有効化されるよう /etc/fstab に追記
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

swappiness設定の最適化

スワップを作成した際、注意すべきカーネルパラメータが vm.swappiness です。

vm.swappiness は、Linuxカーネルが物理メモリからスワップ領域へどの程度積極的にページを追い出すかを制御する値(0〜100)です。デフォルト値が 60 に設定されているディストリビューションが多いですが、Webサーバー環境で 60 のままだと、物理メモリにまだ余裕がある段階からディスクへのスワップアウトが発生し、I/O負荷が跳ね上がって応答が遅延します。

Webサーバー・DBサーバーでは、vm.swappiness を 10 〜 20 程度に引き下げておくことを強く推奨します。

# 現在の設定値を確認
cat /proc/sys/vm/swappiness

# 一時的に10に変更
sudo sysctl vm.swappiness=10

# 恒久化するために設定ファイルへ追記
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system

これにより、「通常時は極力物理メモリをフル活用し、真のメモリ枯渇危機に陥ったときだけスワップを使ってOOM Killerを回避する」という理想的な防衛ラインが形成されます。


よくある落とし穴とトラブルシューティング

自作の監視スクリプトが「テスト時は動いていたのに本番で動かない」「夜中に無駄なアラートを連発する」といったトラブルの多くは、Linux特有の仕様やcronの環境差異に起因しています。現場で直面する3大落とし穴を事前に把握しておきましょう。

freeのfreeではなくavailableを見ないと誤検知する罠(バッファ/キャッシュの扱い)

古典的な監視スクリプトの入門記事などで、以下のような計算式を見かけることがあります。

# ⚠️ 誤検知を引き起こす危険な書き方(アンチパターン)
free_mem=$(free -m | awk '/^Mem:/{print $4}') # free列を取得
total_mem=$(free -m | awk '/^Mem:/{print $2}')
# (total - free) / total で使用率を算出...

これは致命的な誤りです。

Linuxカーネルは、ディスクI/Oの高速化のために「使われていないメモリ」をバッファやページキャッシュ(buff/cache)として貪欲に割り当てます。このキャッシュは、アプリケーションがメモリを要求すればカーネルによって一瞬で即座に解放される領域です。

つまり、「free」列に表示されている数値は「カーネルが完全に何の手も付けていない未割り当て領域」に過ぎず、サーバーが正常に稼働してキャッシュが十分に効いている状態であれば、freeの値は自然と数%程度まで低下します。

free列を基準に使用率を計算すると、「負荷もトラブルもないのに、日常的にメモリ使用率95%のアラートが鳴り続ける」という最悪のオオカミ少年状態に陥ります。

必ず free -m の第7カラムに存在する available(実質利用可能容量) を参照してください。available は、キャッシュの中で即時回収可能な容量をカーネルが計算して含めた「真の空き容量」です。

cron実行時に環境変数(PATH等)が通らずcurlやpsが失敗する問題とフルパス指定

「手動で ./server_monitor.sh を叩いたときは完璧にSlack通知が届くのに、cronに仕掛けると全く通知が来ない」

この原因の99%は「cron実行時の環境変数(特に $PATH)の欠落」です。

SSHログイン時の対話シェルでは、~/.bashrc や /etc/profile 等によって /usr/local/bin や /usr/bin などが豊富に $PATH へ追加されています。しかし、cronデーモンがスクリプトを実行する環境は、極めて制限された最小限の環境(例: PATH=/usr/bin:/bin のみ)しか持っていません。

これにより、/usr/local/bin/curl やディストリビューション固有のコマンドが見つからず、curl: command not found で人知れず異常終了します。

対策

スクリプトの先頭部分で、必ず以下のように広範囲の $PATH を明示的に再定義してください。

#!/usr/bin/env bash
set -euo pipefail

# cron実行環境でもコマンドを確実に発見できるようPATHを宣言
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# ロケール差異によるtopやawkの数値フォーマット崩れを防止
export LC_ALL=C

これらをスクリプトの冒頭に記述しておくだけで、cron環境特有のコマンド探索失敗や、ロケール差異による数値パースの崩れを完全に排除できます。

Slack通知のペイロード内特殊文字(改行やクォート)のJSONエスケープ対策(jqやセーフ展開)

ps aux コマンドの出力結果には、コマンドライン引数に含まれるダブルクォート(")、シングルクォート(')、バックスラッシュ(\)、そして大量の改行コードが含まれます。

これを安易にヒアドキュメントや文字列展開でJSON文字列に埋め込もうとすると、JSONの構文エラー(Syntax Error)を引き起こし、SlackのAPIサーバーから invalid_payload や 400 Bad Request が返されて通知が握りつぶされます。

# ⚠️ JSON構文破壊を引き起こす失敗例
payload="{\"text\": \"警告!\n$process_info\"}" # 改行やクォートでJSONが壊れる

対策1: jqコマンドを活用した安全なペイロード生成(推奨)

最も確実で安全な方法は、コマンドラインJSONプロセッサである jq の --arg オプションを使用することです。--arg は渡された文字列内の改行や特殊文字を完全に規格準拠のJSON文字列へと自動エスケープします。

json_payload="$(jq -n --arg text "$raw_text" '{"text": $text}')"

対策2: jqがインストールされていない軽量環境でのセーフ展開

ミニマルなVPS等で jq がインストールされていない(インストールを許可されていない)環境では、sed による置換パイプラインで特殊文字をエスケープします。

# バックスラッシュ、ダブルクォート、改行を順次エスケープ
escaped_text="$(echo -e "$raw_text" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')"
json_payload="{\"text\": \"${escaped_text}\"}"

本記事の完成版スクリプトでは、command -v jq で jq の存在を自動判定し、利用可能なら jq を使い、存在しなければ sed のエスケープ処理へ自動でフォールバックする二重防御構造を採用しています。


まとめ:自作スクリプトで安心の無人運用体制を整える

本記事で解説した自作監視スクリプトの要点は以下の3点です。

  • 要点1: カーネルのメモリ仕様を正しく把握する
    単なる free ではなく available を参照して実質利用可能容量から使用率を算出すること。CPU監視では top -b -n 2 の2周目サンプリングを用いることで、累積平均の罠を避けて誤報ゼロの正確な監視を実現します。
  • 要点2: 閾値超過時のプロセス特定を自動化する
    障害発生の瞬間に ps aux --sort=-%cpu および ps aux --sort=-%mem で上位5プロセスを捕捉しSlackへ届けることで、休暇中や深夜の調査工数をゼロ秒化し、初動対応を劇的に効率化します。
  • 要点3: flock排他制御とクーリングタイムで二次災害を防ぐ
    多重起動防止と発報スロットリングを実装し、通知スパムとスクリプト自身によるサーバー共連れダウンを確実に遮断します。

休日のたびに「サーバーは無事だろうか」と不安になり、コンソールを開いて手動でコマンドを叩くのは今日で終わりにしましょう。

「手作業は負け」です。

信頼性の高いシェルスクリプトにサーバーの見守りを任せ、エンジニア本来の平穏な休日と長期休暇を手に入れてください。


関連記事(内部リンク)

無人サーバー運用とBash自動化を極めるための実践ガイドです。あわせて確認し、堅牢な運用体制を盤石にしてください。

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント