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を組むときは、そこを一番先に疑ったほうがいい。