深夜に自動実行されたはずのバッチ処理が失敗していたことに、翌朝ログを見て気づいた一人運用の保守担当者

cronバッチの失敗に気づく仕組み|静かに止まるジョブを見逃さない

月初の朝、経理から「先月の集計データが、まだ更新されていないみたいです」と連絡が来る。 調べてみると、毎晩動いているはずの集計バッチが、3日前から失敗したまま止まっていた。エラー自体は出ていた。でも、その出力は誰も見ていないメールボックスにたまっていただけだった——。

そんな経験、ありませんか。 定期バッチや夜間ジョブは、動いているうちは何も言ってきません。だからこそ、失敗しても画面には何も出ず、「静かに止まったまま」になりやすい。保守運用の現場で、いちばん見つけにくいトラブルのひとつです。

この記事では、なぜcronが失敗を教えてくれないのか、そして「失敗したら通知」「動いたことを確認」「ログを残す」の3つで気づける仕組みを、一人運用の目線で整理します。全部を一度に入れなくて大丈夫です。まずは、いちばん止まると困る1本から。

結論:バッチの見逃しは、①失敗したら通知(終了コードを見て、失敗時だけ知らせる)②動いたことを確認(成功の合図が来なかったら鳴らす=外側からの死活監視)③ログを残す(いつ・成否・所要時間)——の3つで防げます。まずは「いちばん止まると困る1本の出力を、届くメールに飛ばす」ところから。

コマンドはLinuxのcronを前提にしています。閾値や通知先は用途次第です。ここを出発点に、環境に合わせて調整してください。

なぜcronは失敗を教えてくれないのか

cron(指定した時刻に自動でコマンドを実行してくれる仕組み)は、とても素直な道具です。「決めた時間に、決めたコマンドを動かす」——それだけをします。うまくいったか、失敗したかを、こちらに知らせに来てくれるわけではありません。

見逃しが起きるのは、だいたい次のどれかです。

ポイントは、「エラーが出るか」ではなく「エラーに気づけるか」が問題だということ。仕組みがなければ、静かに止まったバッチには誰も気づけません。

気づくための3本柱

バッチの見逃しを防ぐ「失敗したら通知」「動いたことを確認」「ログを残す」の3本柱を並べた図
失敗通知・成功の確認・ログの3つを組み合わせると、どのパターンの見逃しも拾える

見逃しのパターンが分かれば、対策もシンプルになります。次の3つを、できるところから一段ずつ。

① 失敗したら通知する(終了コードを見る)

まず、失敗したときだけ知らせる仕組みを入れます。毎回通知が来ると読まなくなるので、「失敗時だけ」が大事です。

ラッパースクリプトで包むのがいちばん確実です。処理を実行し、終了コードが 0 以外なら通知する、という形です。

#!/bin/bash
set -o pipefail

LOG=/var/log/myapp/nightly.log
名前="nightly-batch"

# 本体を実行し、出力はログへ
/path/to/nightly_job.sh >>"$LOG" 2>&1
code=$?

if [ "$code" -ne 0 ]; then
    # 失敗時だけ通知(メール・Slack Webhookなど、届く経路に)
    tail -n 30 "$LOG" | mail -s "[FAILED] $名前 (exit=$code)" you@example.com
fi
exit "$code"

crontab 側では、宛先を「実際に読むアドレス」にしておきます。

MAILTO="you@example.com"
0 2 * * * /path/to/run_nightly.sh

set -o pipefail を付けると、パイプの途中で失敗したときにちゃんと失敗扱いになります。スクリプト内で複数コマンドを順に実行するなら、先頭に set -e を置いて「どこかで失敗したら止める」のも有効です(意図的に失敗を許す箇所だけ || true で逃がす)。

② 動いたことを確認する(成功の合図が来なかったら鳴らす)

①だけでは、「①のスクリプトごと動かなかった」「cronが止まっていた」ケースを拾えません。失敗の通知は、動いて初めて送られるからです。

そこで、成功したら“生きてます”の合図を送り、その合図が予定時刻を過ぎても来なかったら鳴らす、という外側からの監視を足します。いわゆるハートビート(死活監視)や、来なければ鳴る「デッドマンスイッチ」の考え方です。

外形監視サービス(Healthchecks系など)を使うと最小の手間で入りますが、自前でも「最終成功時刻をファイルやDBに記録し、別のジョブがそれを見て“何時間も更新がなければ鳴らす”」で代用できます。大事なのは、判定を処理の外側に置くこと。処理が丸ごと止まっても、外側は「来なかった」に気づけます。

③ ログを残す(いつ・成否・所要時間)

通知で気づけても、原因にたどり着けなければ復旧に時間がかかります。最低限、いつ動いて・成否は・どれくらいかかったかを1行でも残しておきます。

start=$(date +%s)
echo "[$(date '+%F %T')] start $名前"  >>"$LOG"
# …本体…
end=$(date +%s)
echo "[$(date '+%F %T')] end   $名前 exit=$code elapsed=$((end-start))s" >>"$LOG"

所要時間を残しておくと、「失敗はしていないけど、最近やけに遅い」という予兆にも気づけます。ログが増えるので、ためこまない設定(ログローテーション)とセットにしておくと安心です。

具体例:多重起動を防ぐひと工夫(flock)

「失敗ではないのに結果が壊れる」原因で多いのが、前回の処理が終わらないうちに次が動く多重起動です。flock を使うと、実行中は次の起動をスキップ(またはエラー)にできます。

# 実行中なら二重起動しない(-n = 待たずに即やめる)
0 2 * * * /usr/bin/flock -n /tmp/nightly.lock /path/to/run_nightly.sh

-n を付けると、前の処理がまだ動いていた場合はすぐ終了します(終了コードは非0になるので、①の通知にも乗ります)。「詰まって二重に動く」を、1行で静かに防げます。

なお、cronにこだわらなくてもよい環境なら、systemd のタイマー(systemd.timer)に載せ替えると、実行結果が systemctl statusjournalctl で追いやすくなり、失敗時の扱いも整理しやすくなります。今の仕組みを大きく変えられないうちは、まず①〜③をcronのまま足すので十分です。

影響:仕組みを整えると、何が変わるか

放置すると、バッチの失敗は「数日後に、業務側からの指摘で発覚する」形になりがちです。そうなると、止まっていた期間のデータ補正まで含めて対応が重くなります。静かに進むぶん、気づく仕組みさえあれば、ずいぶん軽くできます。

明日やること:いちばん止まると困る1本から

  1. バッチの一覧を出す(crontab -l/etc/cron.d//etc/cron.daily/ などを確認)。まず「止まると業務が止まる1本」を選ぶ。
  2. その1本の MAILTO を、実際に自分が読むアドレスに変える。まずはこれだけで「失敗が届く」状態になる。
  3. 余力があれば、その1本を①のラッパーで包み、失敗時だけ通知するようにする(成功時は静かに)。
  4. さらに余力があれば、②の「成功の合図が来なかったら鳴らす」を1本だけ入れる。
  5. ③として、開始・終了・成否・所要時間をログに1行ずつ残す。

この30分で、「いちばん止まると困るバッチ」だけは、静かに死なない状態になります。

バッチの見逃しを防ぐチェックリスト

最低ライン(優先順位つき:これだけで回る) 1) 届く宛先:重要バッチの MAILTO が「実際に読むアドレス」になっている 2) 失敗を拾う:終了コードを見て、失敗時だけ通知するラッパーで包む 3) 沈黙を拾う:成功の合図が来なかったら鳴らす監視を、重要な1本に入れる

余力が出たら拡張

免除条件(省略可)

確認項目

よければ、こちらも

バッチの死活監視は、障害を「言われてから」ではなく「起きたその朝に」拾うための備えのひとつです。鳴った後の段取りや、原因にたどり着くログの見方も、あわせて型にしておくと落ち着けます。

夜間バッチが正常に完了した通知を朝に確認して、静かに安心している保守運用の担当者

バッチの失敗は、画面に何も出ないぶん、気づいたときには時間がたっていることが多いトラブルです。でも、静かに進むからこそ、気づく仕組みを一度だけ作っておけば、あとはずっと守ってくれます。 今日は、いちばん止まると困る1本の宛先を、自分に届くアドレスに変えるだけで十分です。その小さなひと手間が、「数日後に指摘されて青ざめる朝」を、静かに遠ざけてくれます。

ほかの実務ヒントは記事一覧からどうぞ。保守運用の小さな備えを、メールでも少しずつお届けしています。

関連用語