夜間バッチや定期インポート処理を運用していて、ジョブが「エラーも出さずに動かなかった」という経験はないでしょうか。
小規模なSaaSや社内システムの運用担当者、1人情シスやスタートアップのバックエンド担当者に向けて、この落とし穴と対処法を整理します。
ログを集めているのに障害に気づけない、という状態はよくある盲点です。
この記事では、その原因を段階的に分解し、自分のプロジェクトが該当するかの確認方法と、具体的な対策手順をまとめます。
何が起きるか: ログがあるのに障害に気づけない
典型的な事象はこうです。
顧客データの定期インポートジョブが夜間に動くはずなのに、ある日まったく起動していませんでした。
翌朝、サポートチームに「データが反映されていない」という問い合わせが来て初めて気づく、というケースです。
ログ基盤を導入していても、このパターンは防げません。
なぜなら、ログは「何かが起きたこと」を記録する仕組みであり、「何も起きなかったこと」は記録できないからです。
ジョブが起動さえしなければ、ログには1行も出力されません。
検索しても何も出てこないため、障害調査の起点すら作れない状態になります。
影響範囲は意外と広く、スケジューラの設定ミス、デプロイ時のcron設定漏れ、コンテナの再起動タイミングのずれなど、原因はさまざまです。
共通しているのは「ログ検索だけに頼った監視設計」という構造的な弱点です。
なぜ起きるか: ログ監視と死活監視の役割の違い
原因を分解すると、ログ検索(Elastic CloudやLoki、各種ホスト型ログAPIなど)と、ハートビート監視(heartbeat monitor、定期的に「生きている」信号を送る仕組み)の役割が混同されていることに行き着きます。
ログ検索は「起きた事象を後から掘り返す」ための道具です。
自己ホスト型のLoki(Grafana Labs製のログ集約システム)や、Elastic Cloud(Elasticsearchのマネージドサービス)は、複雑な検索やアラートパイプラインを組める反面、運用負荷が高めです。
一方、Better StackやホストされたログAPI(REST APIで直接ログを送受信するサービス)は、運用負荷を抑えつつ基本的な検索機能を提供します。
しかしこれらはすべて「ログが届いた後」の話であり、「ログが届かなかった」ことを検知する機能ではありません。
ここで整理が必要なのが、障害の2パターンです。
ジョブが「実行されて失敗した」場合と、「そもそも実行されなかった」場合は、根本的に別の問題として扱う必要があります。
前者はログに記録が残るため、ステータスやエラーメッセージから原因分析ができます。
後者は記録自体が存在しないため、ログ検索ツールをどれだけ高機能にしても検知できません。
この区別を怠ると、「ログ基盤を入れたから監視は万全」という誤った安心感につながります。
これが今回の落とし穴の核心です。
自分のプロジェクトが該当するか確認する方法
該当するかどうかは、以下の観点で確認できます。
- 定期ジョブ(cron、Kubernetes CronJob、クラウドのスケジューラ)が「実行されたこと」自体を記録しているか
- ジョブ実行結果の記録に、実行予定時刻・開始時刻・終了時刻・ステータス・処理件数が含まれているか
- ジョブが起動しなかった場合にアラートが飛ぶ仕組みが別途あるか
- ログ検索ツールの障害調査フローが「ログを検索する」の1手順だけで完結しているか
Kubernetes環境であれば、以下のコマンドでCronJobの直近の実行履歴を確認できます。
kubectl get cronjob <job-name> -o jsonpath='{.status.lastScheduleTime}'
kubectl get jobs --selector=job-name=<job-name>lastScheduleTimeが更新されていない、あるいは期待する時刻から大きくずれている場合、スケジューラ側の問題を疑う必要があります。
Linux上でcronを使っている場合は、/var/log/syslogや/var/log/cronに実行ログが残っているか確認します。
grep CRON /var/log/syslog | tail -n 50この出力に対象ジョブのエントリが見当たらない場合、cron自体が起動していないか、設定ファイル(/etc/crontabやユーザーのcrontab)の記述ミスが疑われます。
上記のいずれかに「ノー」が付く場合、今回の落とし穴に該当する可能性が高い状態です。
対策の手順
対策は、ログ検索とハートビート監視を分けて設計することに尽きます。
具体的な手順は以下のとおりです。
1. 各ジョブに一意の実行ID(run ID)を発行する。たとえばimp_20261009_0200_acmeのように、日時とアカウント名を含めた形式にすると検索が容易になります
2. 実行のたびに「予定時刻・開始時刻・終了時刻・ステータス・処理件数・トレースID」を1レコードとして記録する
3. 顧客のメールアドレスやアクセストークンなど個人情報・機密情報はログに含めない。GDPR第5条のデータ最小化原則は、EU圏外のプロジェクトでも設計指針として有効です
4. 別途ハートビート監視サービスを設定し、「猶予時間内に終端ステータス(成功・失敗いずれか)が届かない場合」にアラートを出す
5. 障害調査時は、実行IDとアカウントIDでログを検索し、ジョブが「未起動」「途中停止」「0件で完了」のどれだったかを判定する
特に手順5の「0件で完了」と「未起動」の区別は重要です。
0件は処理結果としてのデータであり、未起動はアラートが必要な異常です。
この2つを同じ「失敗」として扱うと、原因分析の初動が遅れます。
ツール選定については、運用負荷と調査の深さのトレードオフで考えます。
| 選択肢 | 運用負荷 | 調査の深さ | 向いているケース |
|---|---|---|---|
| 自己ホスト型Loki | 高い | 深い | ストレージとインデックスを自前運用できるチーム |
| Elastic Cloud | 中程度 | 深い | 複雑な検索やガバナンス要件がある場合 |
| Better Stackなどマネージド型 | 低い | ホスト型相応 | 運用負荷を抑えつつ複数製品を比較したい場合 |
| ホスト型ログAPI | 低い | 基本的 | 小規模な障害調査と即時取り込みが目的の場合 |
小規模な顧客サポート系SaaSで、障害対応の目的が「スケジュールされたインポート結果の記録」と「インシデント発生時の検索」であれば、まずホスト型ログAPIで十分なケースが多いです。
REST APIで直接ログを送受信できるサービスであれば、SDKのインストールやクライアントライブラリのバージョン管理が不要になり、導入の初速が早くなります。
一方で、高度なアラートパイプライン、分散トレースのクエリ、エクスポート機能、ガバナンス統制が必要になった時点で、LokiやElastic Cloudのようなフルスタック構成への移行を検討すべきタイミングです。
まとめ
ログ基盤を導入しても、「実行されなかった」という障害は検知できません。
これはツールの機能不足ではなく、ログという仕組みそのものの構造的な限界です。
次に取れる一歩として、まず該当ジョブのkubectl get cronjobやcrontabログを確認し、直近の実行履歴が期待通りかをチェックしてみてください。
そのうえで、実行IDベースのログ記録とハートビート監視を分離して設計し直すことが、障害の早期発見につながります。
ツール選定はその後の話であり、運用負荷と調査の深さのバランスを見ながら、自己ホスト型かマネージド型かを判断すれば十分です。