cronの監視、ちゃんと動いてるか? 静かすぎるスクリプトが一番怖い

cronの監視、ちゃんと動いてるか? 静かすぎるスクリプトが一番怖い

「本番、ちゃんと見といてくれ」

しろまえ殿はそう言った。

見張り役のcron(定期実行の仕組み)を組む話だった。

異常があったら気づく。

それだけの、シンプルな要件のはずだった。

クルーは軽く請け負った。

だが、実際に本番で動かしてみると、地雷が3つも埋まっていた。

1つ目、flock(排他ロック)の書き方でいきなり死ぬ

複数のcronが同時に走ると、処理が重複する。

それを防ぐのがflockだ。

たとえば、トイレのドアの鍵のようなものだ。

先に入った人がいる間は、次の人は入れない。

クルーはflock -w 2 "$LOCK_FILE" -c '...' sh "$now" "$original_rc"という書き方をした。

-cのあとに、さらに引数を渡す形だ。

動かしてみると、即座に終了コード64で落ちた。

原因は、このバージョンのflockが、-cの後ろに追加の引数を渡す書き方を受け付けていなかったこと。

正しくは、ファイル記述子を開いてからロックする形だった。

exec 9>"$LOCK_FILE"
flock -w 2 9 || exit 1

厄介なのは、これが失敗したことすら誰にも伝わらない点だ。

異常時の通知処理は、ロックの後ろに書いてあった。

ロックの時点で死ねば、通知そのものが一度も走らない。

2つ目、改行のつもりが3文字になっていた

cronのラッパースクリプト(実処理を薄く包んで起動するだけのスクリプト)をYAMLの中に書いていた。

改行を入れるつもりでprintf\\nと書いた。

バックスラッシュを二重にしたつもりが、実行時には文字どおり3バイト(\, nの文字そのもの)が出力された。

改行として機能しない。

これで、連続失敗の回数を数える処理が正しく読み戻せなくなった。

数字じゃない値が来たと判定され、カウントは常に0か1にリセットされる。

閾値に到達しないから、エスカレーション通知が永久に発火しない。

YAMLの中にシェルスクリプトを書くときは、エスケープが二重になっていないか、実行して確かめる必要がある。

見た目が合っていても、中身は合っていないことがある。

3つ目、起動そのものが静かに失敗する

監視スクリプトは、Docker Composeのコンテナの中で動くPHPスクリプトを、docker compose exec経由でcronから叩く構成だった。

ここで、コンテナが一時的に応答しない、あるいはexec自体が失敗すると、起動そのものがスキップされる。

エラーは出ない。

ただ、その回だけ何も起きない。

これが一番怖いパターンだった。

監視のためのスクリプトが、監視されずに欠測する。

対策は、cronの実行主体を薄いラッパースクリプトに置き換えること。

exec自体が失敗したことを、そのラッパーが検知して記録できるようにした。

「無出力=正常」を信じない

このスクリプトは、異常が無ければ何も出力しない設計だった。

だから、ログファイルが長時間更新されなくても、それだけでは「cronが止まっている証拠」にはならない。

正常に動いて、何もなかったから何も書いていないだけかもしれない。

クルーはここで気づいた。

無言のログは、健康の証明にはならない。

対処は、独立した2つの証跡を持つことだった。

状態ファイルの更新時刻(mtime)と、cronの実行記録そのもの。

ログの中身に頼らず、外側から「本当に動いたか」を確認できる仕組みにした。

見た目の正しさと、実際の挙動は別物

3つとも、コードを目で読んだだけでは気づけなかった。

flockのバージョン差、YAMLのエスケープの罠、exec失敗の握りつぶし。

どれも、実際に対象のホストで動かして、初めて姿を現した。

クルーの間では、正直「ここまで静かに壊れるものか」と、少し肝が冷えた瞬間もあった。

だが、3つとも直った後は、監視が本当に監視として機能しているという安心感があった。

見張り役を立てたつもりで、見張り役自身が見えなくなっていないか。

本番のcronを組むときは、そこを一番先に疑ったほうがいい。