PythonでカスタムPrometheus Exporterを構築する:内部APIを「徹底的」に監視

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

ダッシュボードは「正常」なのに、システムが「ダウン」している時

深夜2時、PagerDutyの通知音が鳴り響きました。目をこすりながらGrafanaのダッシュボードを確認すると、CPU使用率は20%、メモリも十分な空きがあり、数十台のサーバーを監視しているNode Exporterはすべてが正常であることを示していました。しかし、カスタマーサポートのグループチャットでは、500人以上のユーザーが決済できないという苦情で溢れかえっていました。

30分間の調査の結果、内部APIが500エラーを返していることが判明しました。1,200件以上の注文が「保留中(Pending)」のまま停滞していたのです。Prometheusはインフラの監視しか行っておらず、内部のビジネスロジックには関与していなかったため、この異常を完全に検知できていませんでした。

以前の私は、サーバーにSSHでログインしてtail -fでログを確認したり、手動でSQLを実行してエラー数をカウントしたりしていました。しかし、この方法は非常に手間がかかります。例えば、Prometheus SQL Exporterでデータベースからビジネスメトリクスを直接監視する方法もありますが、ビジネスデータをPrometheusに「露出(expose)」させる独自の方法を見つけない限り、今後も眠れない夜が続くことになります。

なぜNode Exporterだけでは不十分なのか?

node_exportermysql_exporterのようなツールは非常に強力ですが、一つの限界があります。それは、「あなたのビジネスロジックを理解していない」ということです。

例えば、10年前から稼働している古いコアバンキングシステムを想像してみてください。それには/metricsエンドポイントなんてありません。JSONを返すか、ファイルにログを記録するだけです。Prometheusは特定のテキスト形式のデータを必要とします。システムが同じ「言語」で話さない限り、Prometheusは何も把握できません。

以下のような場合、カスタムソリューションが必要になります:

  • データが未加工のJSONを返す内部API内にある場合。
  • データベース内で5分以上停滞している注文数をカウントする必要がある場合。
  • システムに実際にログインしているユーザー数を追跡する場合。

3つの一般的なアプローチ

この課題を解決するために、私は3つの方法を検討しました:

  1. Textfile Collector: cronジョブでスクリプトを実行し、.promファイルを出力する方法。シンプルですが、システムが大規模になると管理が難しくなります。
  2. Pushgateway: データを中間ステーションにプッシュする方法。これは短時間のジョブ(バッチジョブ)に適しています。ただし、スクリプトが停止してもPushgatewayがそれを検知できず、「古いデータ」が残り続けるリスクがあります。
  3. Custom Exporter(推奨): Pythonで小さなサービス(サイドカー)を構築する方法。データを取得してPrometheus標準に変換し、Prometheusからのプル(pull)を待ちます。これが本番環境において最も安定した方法です。

実践:PythonでカスタムExporterを作成する

Pythonは、非常に使いやすいprometheus_clientライブラリがあるため、第一の選択肢となります(Node.js環境であれば、prom-clientでNode.jsアプリを監視するのも良いでしょう)。今回は、シミュレーションAPIからデータを取得するExporterを作成します。

ステップ 1: ツールのインストール

仮想環境を作成し、必要なライブラリをインストールします:

pip install prometheus_client requests

ステップ 2: ソースコードの記述

API의 /statuspending_orders: 42active_users: 150を返すと仮定します。これらを変換するコードは以下の通りです:

import time
import requests
from prometheus_client import start_http_server, Gauge

# メトリクスの初期化:Gaugeは値の増減を許可する
PENDING_ORDERS = Gauge('myapp_pending_orders', '待機中の注文数')
ACTIVE_USERS = Gauge('myapp_active_users', 'オンラインユーザー数')

def fetch_metrics():
    try:
        # 実際のAPI呼び出しをシミュレート
        # resp = requests.get("http://api.internal/status", timeout=5)
        # data = resp.json()
        data = {"pending_orders": 42, "active_users": 150}

        # Prometheusクライアントにデータをセット
        PENDING_ORDERS.set(data['pending_orders'])
        ACTIVE_USERS.set(data['active_users'])
        print(f"更新完了: {data['pending_orders']} 件の注文")
    except Exception as e:
        print(f"データ取得エラー: {e}")

if __name__ == '__main__':
    # Prometheusがデータを取得できるようにポート8000を開放
    start_http_server(8000)
    while True:
        fetch_metrics()
        time.sleep(15) # 15秒ごとに更新

ステップ 3: Prometheusの設定

接続するために、prometheus.ymlファイルに以下の数行を追加します:

scrape_configs:
  - job_name: 'python_exporter'
    static_configs:
      - targets: ['localhost:8000']

Prometheusを再起動すると、すぐにGrafanaでグラフを描画できるようになります。

運用における「手痛い教訓」から得た経験則

Exporterが原因でシステムをダウンさせないために、以下の3点に注意してください:

  • 常にTimeoutを使用する: requestsでAPIを呼び出す際は、必ずtimeout=5などを設定してください。APIがハングアップすると、Exporterも一緒に停止してしまいます。
  • Scrape頻度を合わせる: Prometheusが15秒ごとにデータを取得する場合、スクリプトも15秒ごとに実行すべきです。必要以上に頻繁にAPIを叩いてリソースを無駄にしないでください。
  • Systemdで管理する: スクリプトを手動で実行しないでください。Systemdを使用して、サーバーの再起動時やスクリプトのクラッシュ時に自動再起動するように設定しましょう。PrometheusでSystemdを監視することで、Exporter自体の稼働を常に把握できます。

Systemd設定ファイルの例 (/etc/systemd/system/my_exporter.service):

[Service]
ExecStart=/usr/bin/python3 /opt/exporter.py
Restart=always
User=prometheus

今では、徹夜する代わりにGrafanaでアラートを設定しています。もしmyapp_pending_orders > 100の状態が2分間続けば、すぐにTelegramに通知が届きます。本番環境のアラートノイズを削減する設定を組み合わせて、ノートPCを開くことなく、リモートでトラブル対応の判断ができるようになりました。これこそが、ニーズに合わせた「オーダーメイド」監視の真の価値です。

Share: