一言で言うと Prometheus が異常を検知したら、対応表で調査スクリプトを選んで SSH 実行し、結果を要約して Telegram に通知する。AI はコマンドを生成せず、スクリプトも選ばない(スクリプト選択は対応表=表引き。AI が担うのは「要約」だけ)。
ホームラボ(Linux / OpenWrt / Windows 混在)向けの、LLM 補助つきアラート自動診断エージェント。 当初はあるホームラボの Windows サーバのメモリ/SSD リーク監視が目的で、現在は複数台の監視に拡張済み。
[各ノード node_exporter / windows_exporter]
↓ メトリクス収集
[Prometheus :9090]
↓ アラート発火(閾値超過)
[Alertmanager :9093]
↓ Webhook POST → /alert
[alert_handler.py :5001] ← Flask
↓ キューに追加(即 200 を返す)
─── 5 分ごとにバックグラウンドスレッドがまとめて処理 ───
↓
① Prometheus API で直近 1h のメトリクス取得
② alertname × OS の対応表で調査スクリプトを 1 つ選択(ALERT_TO_SCRIPT 表引き・LLM 不使用)
③ SSH で対象ホスト上にスクリプトを流し込んで実行
④ LLM が出力を SRE 風に 2〜4 文へ要約(失敗時は生出力を添付)
↓
[Telegram Bot → 全アラートを 1 通にまとめて通知]
LLM が担うのは ④ の要約だけ(スクリプト選択は表引き=決定的・クォータ消費ゼロ)。なぜ選択から LLM を外したかは docs/DESIGN.md。
LLM は ollama(ローカル推論)と Gemini API を LLM_PROVIDER で切替可能。どちらが落ちても例外を投げず、フォールバックして通知だけは必ず出す設計。
closecraw/
├── .env ← トークン / APIキー / パス(gitignore 対象)
├── system_status.sh ← 全体ヘルスチェック(ワンショット)
├── src/
│ ├── alert_handler.py ← Flask Webhook サーバ (port 5001)・キュー・LLM 抽象層
│ └── notifier.py ← Telegram 送信
├── scripts/
│ ├── host_loader.py ← インベントリ JSON を OS 種別に分類
│ ├── generate_prometheus_targets.py ← JSON から Prometheus static_configs を生成
│ ├── script_tester.sh
│ ├── linux_memory_pressure.sh ┐
│ ├── linux_disk_inode.sh │
│ ├── linux_journal_anomaly.sh ├ 許可スクリプト(ホワイトリスト)
│ ├── win_process_delta.sh │
│ ├── win_eventlog_anomaly.sh │
│ └── win_disk_anomaly.sh ┘
├── docs/
│ ├── DESIGN.md ← 設計メモ(なぜ LLM を選択から外したか+教訓)
│ └── inventory.example.json ← ホストインベントリのサンプル
├── assets/logo/ ← ロゴ
└── logs/ ← ログ出力先(gitignore 対象)
Prometheus / Alertmanager / Grafana などの Docker スタックや Alertmanager 設定は 別ディレクトリで管理しており、このリポには含まれない。
python3 -m venv venv
source venv/bin/activate
pip install flask requests python-dotenv
# .env を作成し、下表の変数を埋める
touch .env| 変数 | 説明 |
|---|---|
TELEGRAM_TOKEN |
Telegram Bot トークン(必須) |
TELEGRAM_CHAT_ID |
通知先チャット ID(必須) |
LLM_PROVIDER |
ollama または gemini(デフォルト ollama) |
LLM_MODEL |
使用モデル名(デフォルト gemini-2.5-flash-lite。無印 flash は無料枠が小さいことがある) |
GEMINI_API_KEY |
Gemini 利用時のみ必須 |
CLOSECRAW_SPEC_FILE |
ホストインベントリ JSON のパス(デフォルト /opt/closecraw/inventory.json) |
CLOSECRAW_SCRIPTS_DIR |
許可スクリプトの配置ディレクトリ(デフォルト /opt/closecraw/scripts) |
ホストインベントリの形式は
docs/inventory.example.jsonを参照。 ollama 利用時の接続先・モデルはsrc/alert_handler.pyのOLLAMA_URL/OLLAMA_MODEL(envOLLAMA_MODEL)で設定。
# 直接起動(開発用)
python src/alert_handler.py # → 0.0.0.0:5001
# systemd ユーザーサービス(常駐運用)
systemctl --user status alert-handler.service
systemctl --user restart alert-handler.service
journalctl --user -u alert-handler.service -n 50gunicorn 等で
__main__を経由せず import される場合に備え、スケジューラスレッドは モジュール読み込み時にも起動するようになっている。
| パス | メソッド | 用途 |
|---|---|---|
/alert |
POST | Alertmanager Webhook 受け口(キューに追加して即 200) |
/health |
GET | 死活確認(queue_length も返す) |
| やること | やらないこと |
|---|---|
| スクリプト出力を人間向けに 2〜4 文へ要約する | コマンドを自分で生成する |
| SSH 先で自由にシェル操作する | |
調査スクリプトを選ぶ(対応表 ALERT_TO_SCRIPT で決定・LLM 不使用) |
AI に与える自由度は「要約」だけ。実行できるコマンドは事前に許可した読み取り専用スクリプトに限られ、どれを走らせるかも表引きで機械的に決まる。
| スクリプト | 対象 OS | 内容 |
|---|---|---|
linux_memory_pressure.sh |
Linux | PSI / vmstat OOM / dmesg OOM |
linux_disk_inode.sh |
Linux | df / inode / 削除済み未解放ファイル |
linux_journal_anomaly.sh |
Linux | 直近のエラーログを頻度順集計 |
win_process_delta.sh |
Windows | 数秒間のワーキングセット増加 TOP10 |
win_eventlog_anomaly.sh |
Windows | イベントログのエラー種類別集計 |
win_disk_anomaly.sh |
Windows | 直近 24h で更新された大きいファイル |
各スクリプトは script <hostname> 形式で呼ぶと、内部で ssh <hostname> して対象機上で自身を実行する(localhost ならローカル実行)。
./system_status.sh # Docker / Prometheus / Alertmanager / handler / ollama / SSH を一括確認通知が来ない
systemctl --user status alert-handler.serviceが active かjournalctl --user -u alert-handler.service -n 50でエラー確認curl http://localhost:5001/healthでキュー長を確認(溜まったままなら処理スレッドが死んでいる)curl http://localhost:9090/api/v1/alertsでアラートが発火しているか- Alertmanager の
repeat_intervalを確認(長すぎると来ない)
通知がスパムになる
- Alertmanager の
repeat_intervalを確認(短すぎると即スパム) - アラートルールに instance フィルタが入っているか(特に OpenWrt の Load 系は全 Linux に誤爆しやすい)
handler が起動しない
pkill -f alert_handler.py
systemctl --user restart alert-handler.service
journalctl --user -u alert-handler.service -n 20- アラートは即処理せず 5 分間キューに溜めてバッチ処理 → 通知を 1 通にまとめ、スパムと LLM 呼び出し回数を抑制。1 回の掃き出し上限は
MAX_ALERTS_PER_FLUSH(超過分は次サイクルへ繰り越し)。 alertname→ PromQL はALERT_TO_QUERYで対応付け(未定義ならupをフォールバック取得)。- 診断スクリプトの選択は
ALERT_TO_SCRIPTの表引き(alertname 部分一致 × OS)。該当なしはログ全般調査に落とす。LLM は使わない(クォータ消費ゼロ・決定的)。経緯は docs/DESIGN.md。 - LLM 要約は失敗を許容(None 返却)し、生出力添付でフォールバック(通知は必ず出す)。LLM 呼び出しにはハード上限があり、ハングでスケジューラを止めない。
- 実行できるのは
WHITELIST内のスクリプトのみ。 - Gemini 利用時は 429 応答の body で日次枠(RPD)/毎分枠(RPM)超過を判別し、RPD 超過はリトライしない(翌日まで回復しないものに連打してもクォータを消費するだけ)。API キーはヘッダで渡す(クエリパラメータだと例外メッセージ経由でログに漏れる)。
