HomeLabのバックアップが成功したか推測するのはもうやめましょう:Healthchecks.ioでCronジョブを監視する

HomeLab tutorial - IT technology blog
HomeLab tutorial - IT technology blog

サイレントエラーの悪夢

バックアップが必要になった時に、最後に成功したのが半年前だったと気づくほど絶望的なことはありません。HomeLabにおいて、サイレントエラーは最大の敵です。週末を費やして500GBの写真同期や毎晩のMariaDBダンプを完璧に設定し、初日は完璧に動作したとします。しかし、いずれファイルの権限変更やディスクフル、あるいは些細な構文エラーによってその連鎖は途切れます。通知システムがなければ、データ消失のシナリオに直面するまで、壊れていることにすら気づかないでしょう。

GrafanaやUptime Kumaのような標準的な監視ツールは、サーバーがオンラインかどうかを確認するには最適です。しかし、数秒間しか実行されない「断続的な」タスクの監視には向いていません。ここで「デッドマンズスイッチ(Dead Man’s Switch)」のロジックが役立ちます。外部モニターがタスクをチェックするのではなく、タスク側がモニターに「チェックイン」する必要があるのです。予定時刻までにモニターがハートビートを受信しなかった場合、アラームが作動します。

このプッシュ型の監視に移行することで、インフラ管理のあり方が変わることに気づきました。受動的なパニックから能動的なメンテナンスへとシフトできるのです。Healthchecks.ioはこの目的に最も信頼できるツールです。Dockerでホストすることで、監視データをローカルに保持し、無料版のマネージドサービスの制限を回避できます。

Docker ComposeによるHealthchecks.ioのデプロイ

ホスト版のHealthchecks.ioも優れていますが、セルフホストなら無制限のチェックと完全なプライバシーが手に入ります。私はSQLiteよりもPostgreSQL 16の使用をお勧めします。私のテストでは、SQLiteは高頻度のPingが同時に複数届いた際に、稀にデータベースのロックが発生することがありました。

1. 環境の準備

まずは構造化されたディレクトリを作成することから始めます。バックアップを容易にするため、すべての設定ファイルを中央のDockerフォルダにまとめるのが好みです。

mkdir -p ~/docker/healthchecks/data
cd ~/docker/healthchecks

2. Docker Composeの設定

この設定では、Webインターフェースとデータベースのバックエンドを定義します。一般的なホームネットワーク向けに環境変数を最適化しました。openssl rand -base64 32のようなコマンドを使用して、一意のシークレットキーを生成してください。

services:
  db:
    image: postgres:16-alpine
    container_name: healthchecks-db
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=healthchecks
      - POSTGRES_USER=hc_user
      - POSTGRES_PASSWORD=強力なパスワードを選択してください
    restart: always

  web:
    image: healthchecks/healthchecks:latest
    container_name: healthchecks-web
    depends_on:
      - db
    ports:
      - "8000:8000"
    volumes:
      - ./data/hc-config:/config
    environment:
      - DB=postgres
      - DB_HOST=db
      - DB_NAME=healthchecks
      - DB_USER=hc_user
      - DB_PASSWORD=強力なパスワードを選択してください
      - SECRET_KEY=生成したランダムな文字列
      - SITE_ROOT=http://192.168.1.50:8000
      - SITE_NAME=HomeLabモニター
      - ALLOWED_HOSTS=*
      - DEBUG=False
      - REGISTRATION_OPEN=True
    restart: always

3. 管理者アカウントの初期化

コマンド一つでコンテナを起動します:

docker-compose up -d

デフォルトではユーザーが登録されていません。コンテナ内で次のコマンドを実行して、最初のスーパーユーザーを手動で作成する必要があります:

docker exec -it healthchecks-web /opt/healthchecks/manage.py createsuperuser

メールアドレスとパスワードを設定したら、サーバーのIPアドレスのポート8000にアクセスしてダッシュボードを確認してください。

最初のハートビートの設定

UIはシンプルで目的が明確です。「Check」を作成すると、システムから固有のUUIDとPing URLが発行されます。スクリプトはこのURLを叩く(ヒットさせる)ことで成功を通知します。

スケジュールと猶予期間(Grace Period)

スケジュールの設定は簡単です。オフサイトバックアップが毎日午前3時に実行されるなら、期間を1日に設定します。しかし、Grace Period(猶予期間)こそが最も重要な設定です。タスクの実行時間は変動しがちです。バックアップは月曜日には10分で終わるかもしれませんが、大量のデータインポート後の金曜日には45分かかるかもしれません。私は通常、日次タスクには2時間の猶予期間を設定しています。これにより、単にネットワークが少し遅かっただけで午前4時に誤報のアラートを受け取るのを防げます。

通知チャネルの選択

アラートがどこにも届かなければ監視の意味がありません。「Integrations」タブに移動してアラートを設定しましょう。HomeLab愛好家には、DiscordTelegramが最も設定しやすく、スマホへの即時プッシュ通知が無料で利用できます。すべてを内部で完結させたい場合は、このセットアップと相性抜群なセルフホストの代替案としてGotifyが最適です。

実践的な統合例

実際にどうやってスクリプトから監視ツールに通知を送るのでしょうか? 単純な curl でも動作しますが、エラーハンドリングを考慮したスマートな方法を紹介します。

手軽なCronによる方法

crontabのエントリに直接Pingを追加できます。&& 演算子を使用することで、最初のコマンドが成功した場合のみPingが送信されます。

0 3 * * * /home/user/scripts/rsync_backup.sh && curl -fsS --retry 3 http://192.168.1.50:8000/ping/あなたのUUID

--retry 3 フラグに注目してください。これは非常に重要です。スクリプト終了時に一瞬だけWi-Fiが途切れたような場合の誤報を防げます。

「プロ」仕様のスクリプトによる方法

重要なタスクには、/start/fail エンドポイントを使用します。これにより、Healthchecks.ioでスクリプトの実行時間を計測できるようになります。

#!/bin/bash
URL="http://192.168.1.50:8000/ping/あなたのUUID"

# ジョブの開始を通知
curl -fsS --retry 3 "$URL/start"

# バックアップやメンテナンス用のタスクを実行
/usr/bin/python3 /home/user/scripts/db_cleanup.py

# 前のコマンドの終了コードが0(成功)か確認
if [ $? -eq 0 ]; then
    curl -fsS --retry 3 "$URL"
else
    curl -fsS --retry 3 "$URL/fail"
fi

経験から学んだ教訓

初めの頃、私はすべての項目を同じ緊急度で監視するというミスを犯しました。そうしてはいけません。「日次のメディアスクレイパー」の失敗で夜中に叩き起こされるべきではありませんが、「メインデータベース의バックアップ」の失敗は別です。Tags(タグ)を使用して、「critical(重要)」や「low-priority(低優先度)」のようにダッシュボードを整理してください。

また、監視ツール自体を監視することも忘れないでください。HealthchecksのDockerコンテナがログでディスク容量を使い果たしていないか、時々確認しましょう。このシステムを導入することで、自動化が機能していることを「願う」段階から卒業できます。「便りがないのは良い便り」であることを確信できる、心の平穏が手に入ります。

Share: