この記事は Zenn にも投稿しています。内容は同じです。
結論
「その瞬間、生きているか」を見る監視だけでは、定期ジョブの死を検知できません。
HTTPで叩いて200が返るか、ポートが開いているか、pingが返るか。これらは全部「今」しか見ていません。
そして、バックアップが取れていないことは、失われて初めて気づきます。 その「初めて」は最悪のタイミングで来ます。復元が必要になったときです。
対処は Push型のデッドマンスイッチ です。
# ジョブが成功したときだけ、ハートビートを送る
30 4 * * * root /opt/job.py >> /var/log/job.log 2>&1 \
&& curl -fsS https://uptime.example.com/api/push/aBcD1234EfGh
以下は、そこに至った経緯と、実装するときに踏んだ罠の記録です。
何が起きたか
自宅サーバーで、台帳(NetBox)とその自動化が 7時間停止していました。
私は監視を25件入れていました。それでも気づけませんでした。
原因は、Docker Compose に restart ポリシーが書かれていなかったことです。ホストを再起動したとき、コンテナが Exited (255) のまま復帰しませんでした。
厄介なのは、Dockerサービス自体は enabled かつ active だったことです。一見すると正常に見えます。
これ自体も罠なのですが、本題はここではありません。「もし鳴らないタイプの死に方だったら、永久に気づけなかった」 という点です。
25件すべてが「今」しか見ていなかった
自分の監視設定を見直して、気づきました。
- HTTPで叩いて200が返るか
- TCPポートが開いているか
- pingが返るか
全部「今この瞬間、生きているか」です。
これで検知できないものがあります。
| 死に方 | 検知できるか |
|---|---|
| サービスが落ちた | ✅ できる |
| ホストが落ちた | ✅ できる |
| 定期ジョブが失敗し続けている | ❌ できない |
| バックアップが取れていない | ❌ できない |
定期ジョブは、動いていなくても何も落ちません。
Webサービスは元気にHTTP 200を返し続けます。監視は全部グリーンです。ただ、バックアップだけが取られていない。
そして次にバックアップが必要になったとき——ディスクが飛んだとき、操作を間違えたとき——そこで初めて気づきます。
デッドマンスイッチ
考え方を逆転させます。
Pull型(従来): 監視側が「生きてるか?」と聞きに行く
Push型 : 監視される側が「生きてます」と定期的に報告する
→ 一定時間報告が来なければ異常と判断する
デッドマンスイッチは、鉄道の運転士が握り続けていないと自動的にブレーキがかかる装置のことです。「何もしない」ことが異常の合図になります。
監視に置き換えると、ジョブが成功したときだけハートビートを送り、途絶えたら鳴らすという形になります。
実装
Uptime Kuma には Push型のモニターがあります。指定されたURLを叩くとハートビートとして記録され、一定時間来なければDOWN判定になります。
cronの末尾に足すだけです。
# /etc/cron.d/example
30 4 * * * root /opt/nb2kuma/sync.py >> /var/log/sync.log 2>&1 && /opt/push.sh nb2kuma-sync
ポイントは && です。
成功 → ハートビート送信 → 監視側は正常と判断
失敗 → ハートビート無し → 一定時間後にDOWN判定 → 通知
コマンドが失敗すれば、ハートビートは飛びません。「失敗を検知する」のではなく 成功が報告されないことを検知する という設計です。
⚠️ curl はHTTPエラーでも成功を返す
ここで踏みました。
# ❌ これだと失敗しても && が成立する
... && curl -sS https://uptime.example.com/api/push/aBcD1234EfGh
デフォルトの curl は、HTTPが500を返しても終了コード0(成功)を返します。
つまり、Uptime Kuma 側が落ちていても、ハートビートを送ったつもりになります。 さらに悪いことに、ジョブ本体が成功していれば && は成立するので、壊れていることに気づけません。
# ✅ -f を付けるとHTTPエラーを失敗として扱う
... && curl -fsS https://uptime.example.com/api/push/aBcD1234EfGh
監視の仕組み自体が壊れていても気づけない、というのが最も危険な状態です。
デッドマンスイッチを入れるときは、その通知経路が本当に生きているかを一度は確かめてください。私は意図的にジョブを失敗させて、通知が来ることを確認しました。
許容時間は「周期の2倍強」にする
「何秒ハートビートが来なければ鳴らすか」の設定が重要です。
| ジョブ | 周期 | 許容時間 |
|---|---|---|
| 新規端末の検知 | 30分 | 4,200秒(1.2時間) |
| ログの書き出し | 3時間 | 23,400秒(6.5時間) |
| DBダンプ | 24時間 | 100,800秒(28時間) |
1回コケた程度では鳴らさず、2回連続で落ちたら鳴るという設計です。
理由は単純で、ネットワークの一時的な不調で毎回鳴ると、通知そのものを無視するようになるからです。
鳴ったら必ず何かある、という状態を保つこと。
これが監視で一番大事だと思っています。オオカミ少年になった監視は、無いのと同じです。
「バックアップがある」と「使える」は違う
デッドマンスイッチを入れると、ジョブが走ったことは保証されます。しかし成果物が正しいことは保証されません。
実際に踏みました。DBダンプのスクリプトが、失敗して0バイトのファイルを作り、それが前回の正常なダンプを上書きしていました。
pg_dump -Fc ... > "$DUMP" # 失敗しても空ファイルはできる
cp "$DUMP" "$NAS/" # 空ファイルで上書き
バックアップは存在します。中身がないだけです。
対処
pg_dump -Fc ... > "$DUMP"
# サイズが小さすぎたら破棄して、ジョブごと失敗させる
if [ "$(stat -c%s "$DUMP")" -lt 10000 ]; then
rm -f "$DUMP"
exit 1 # ← ハートビートも送られない
fi
cp "$DUMP" "$NAS/"
失敗したときに、前の成果物を壊さない。 これがバックアップスクリプトの最低条件です。
そして exit 1 にすることで、デッドマンスイッチが検知してくれます。成果物の検証とハートビートを繋げるのがポイントです。
運用ルールにした
この仕組みを作ったあと、ルールを決めました。
cronを1つ足したら、必ず3点セットで用意する。
- ジョブ本体(失敗時に前の成果物を壊さない作り)
- Push型の監視(
&& push.sh <名前>)- logrotate(新しくログを吐くなら)
3つ目も実際に必要でした。放置すると、気づいたときにはディスクが埋まっています。しかもログでディスクフルになると、他のサービスまで巻き込みます。
ジョブを追加した日に設定するのが一番安いタイミングです。後から「そういえばログが」となる頃には、既に肥大化しています。
実際に何を監視対象にしたか
新規端末の検知 30分ごと
攻撃アラートの差分通知 30分ごと
ルーターログの書き出し 3時間ごと
台帳のDBダンプ 1日1回
台帳→監視の同期 1日1回
台帳の乖離検出 1日1回
syslogのNAS転送 1日1回
バックアップと攻撃検知を最優先で入れました。 どちらも「失われて初めて気づく」類だからです。
逆に、Webサービスの死活は従来のPull型のままです。落ちれば即座に分かるので、Push型にする必要がありません。
すべてをPush型にする必要はありません。
「落ちたら分かるもの」はPull型のまま、「黙って死ぬもの」だけPush型にする、という使い分けです。
監視ツールを2つ入れるなら、役割を分ける
余談ですが、関連するので書いておきます。
Uptime Kuma のあとに Prometheus + Grafana も入れました。リソースの傾向を見たかったからです。
ここで問題になるのが、両方ともアラート機能を持っていることです。
| ツール | 役割 |
|---|---|
| Uptime Kuma | 通知専任。すべてのアラートはここから出す |
| Grafana | 可視化専任。アラートは一切持たせない |
両方にアラートを持たせると、こうなります。
- 同じ障害で通知が2回来る
- どちらの設定を直せばいいか分からなくなる
- 片方だけ設定して、もう片方を忘れる
通知の入口は1つにする。 規模が小さいうちに決めておかないと、後から統合するのが非常に面倒です。
Grafanaのアラート機能は強力ですが、強力だから使うという判断は危険です。「この機能が無いと困るか?」を先に問うと、私の場合は「Kumaで足りている」でした。
まとめ
| 問題 | 「その瞬間の生死」を見る監視では、定期ジョブの死を検知できない |
| 対処 | Push型のデッドマンスイッチ。成功時だけハートビートを送り、途絶えたら鳴らす |
| 注意① |
curl に -f を付ける(HTTPエラーで成功を返してしまう) |
| 注意② | 許容時間は周期の2倍強(オオカミ少年にしない) |
| 注意③ | 成果物の検証と exit 1 を繋げる(「ある」と「使える」は別) |
| 注意④ | 全部をPush型にしない。黙って死ぬものだけ |
「監視を入れている」ことと「異常に気づける」ことは、別の話でした。
同じような失敗を57件ぶん書きました
この件も含めて、自宅ラボを組むなかで踏んだ地雷を57件、症状から逆引きできる形で本にまとめました。
📕 自宅ラボ 0→100 ── Proxmoxで作る、止まらない自宅インフラ
この記事の話は、本の付録では 型②「エラーにならないのに動いていない」 に分類しています。他にも「型①設定済みに見えるのに機能しない」「型③半分だけ動く」「型⑥検証方法そのものが間違っていた」といった分け方で整理しました。
第0章(設計の5原則)と更新履歴は無料で読めます。