Prometheus 指標の使用

このページでは、可用性とレイテンシ SLI の Prometheus 指標を作成する基本的事項について説明します。また、Prometheus 指標を使用して SLO を定義する方法の実装例も紹介します。

Prometheus の基礎

Prometheus は、指標とアラート用の主要なオープンソース モニタリング ソリューションの一つです。

Prometheus は、指標の Key-Value 識別子を持つディメンション データをサポートして、PromQL クエリ言語を提供し、他のプロダクトのエクスポータを提供することで、多くの統合をサポートします。

Prometheus は、Stackdriver コレクタを使用して Cloud Monitoring と統合されます。

指標

Prometheus では、次のタイプの指標をサポートしています。

  • カウンタ: 単調に増加するか、再起動時に 0 にリセットできる単一の値。
  • ゲージ: 任意の値に設定可能な単一の数値。
  • ヒストグラム: 観測結果のサンプリングと範囲内の値を記録するための、構成可能なバケットのグループ。観測されたすべての値の合計値も提供します。
  • 概要: ヒストグラムに似ていますが、スライディング タイム ウィンドウに対して構成可能な分位数も計算されます。

詳細については、指標タイプをご覧ください。

インストルメンテーション

Prometheus がアプリケーションから指標を受け取るには、アプリケーションが使用できる指標値を含む専用のエンドポイント(多くの場合、/metrics)を公開する必要があります。このようなエンドポイントを公開するには、Prometheus クライアント ライブラリを使用します。

アプリケーションでデータベースやファイルなどの別の宛先に指標を書き込む場合、Prometheus エクスポータを作成してデータを読み取り、公開できます。詳細については、エクスポータをご覧ください。

最終的には、指標が保存される Prometheus サーバーと、サーバーから Cloud Monitoring に指標を取り込む手段の両方が必要になります。Stackdriver コレクタは、この目的にかなうものです。

SLI の指標の作成

アプリケーションでは、Cloud Monitoring で SLI として使用できる Prometheus 指標を作成する必要があります。

  • リクエスト数とエラー数に関する可用性 SLI については、Promethus カウンタ指標から始めます。
  • レイテンシ SLI には、Prometheus ヒストグラムまたはサマリー指標を使用できます。

可用性 SLI の指標

Cloud Monitoring API でリクエスト ベースの可用性 SLI を表現するには、TimeSeriesRatio 構造体を使用し、リクエスト全体に対する「良い」リクエストと「悪い」リクエストの比率を設定します。この比率は、RequestBasedSli 構造体の goodTotalRatio フィールドで使用されます。

アプリケーションでは、この比率の構成に使用できる Prometheus 指標を作成する必要があります。アプリケーションでは、次の中から少なくとも 2 つを作成する必要があります。

  1. 合計イベントをカウントする指標。この指標は、比率の totalServiceFilter で使用します。

    Prometheus カウンタを作成できます。このカウンタはイベントごとに増加します。

  2. 「悪い」イベントをカウントする指標。この指標は、比率の badServiceFilter で使用します。

    Prometheus カウンタは、エラーやその他の「悪い」イベントごとにインクリメントできます。

  3. 「良好」イベントをカウントする指標。この指標は、比率の goodServiceFilter で使用します。

    成功または他の「良好」なイベントごとに増加する Prometheus カウンタを作成できます。

実装例の例では、リクエストの合計数 nodeRequestsCounter と、失敗したリクエスト数のカウンタ(nodeFailedRequestsCounter)を作成しています。

レイテンシ SLI の指標

Cloud Monitoring API でリクエスト ベースのレイテンシ SLI を表現するには、DistributionCut 構造体を作成します。この構造体は、RequestBasedSli 構造体の distributionCut フィールドで使用されます。

アプリケーションでは、分布カット値の作成に使用できる Prometheus 指標を作成する必要があります。この目的には、Prometheus のヒストグラムまたはサマリーを使用できます。レスポンスが SLO 内にあるかどうかを正確に測定できるようにバケットを定義する方法については、Prometheus ドキュメントの指標タイプをご覧ください。

実装例の例では、パス(nodeLatenciesHistogram)別にレスポンス レイテンシのヒストグラムを作成します。

実装例

このセクションでは、Node.js で Prometheus を使用して、基本的な可用性とレイテンシの SLI の指標を実装する例を示します。

インストルメンテーション

Prometheus 指標を公開するようにサービスをインストルメント化するには、次のようにします。

  1. Prometheus クライアントをインクルードまたはインポートします。

    Go

    import (
    	"fmt"
    	"log"
    	"math/rand"
    	"net/http"
    	"time"
    
    	"github.com/prometheus/client_golang/prometheus"
    	"github.com/prometheus/client_golang/prometheus/promauto"
    	"github.com/prometheus/client_golang/prometheus/promhttp"
    )
    

    Node.js

    const prometheus = require('prom-client');
    const collectDefaultMetrics = prometheus.collectDefaultMetrics;
    const Registry = prometheus.Registry;
    const register = new Registry();
    collectDefaultMetrics({register});

    Python

    import random
    import time
    
    from flask import Flask
    
    from prometheus_client import (
        Counter,
        generate_latest,
        Histogram,
        REGISTRY,
    )
    
  2. クライアントを使用して指標を定義します。

    Go

    // Sets up metrics.
    var (
    	requestCount = promauto.NewCounter(prometheus.CounterOpts{
    		Name: "go_request_count",
    		Help: "total request count",
    	})
    	failedRequestCount = promauto.NewCounter(prometheus.CounterOpts{
    		Name: "go_failed_request_count",
    		Help: "failed request count",
    	})
    	responseLatency = promauto.NewHistogram(prometheus.HistogramOpts{
    		Name: "go_response_latency",
    		Help: "response latencies",
    	})
    )
    

    Node.js

    // total requests - counter
    const nodeRequestsCounter = new prometheus.Counter({
      name: 'node_requests',
      help: 'total requests',
    });
    
    // failed requests - counter
    const nodeFailedRequestsCounter = new prometheus.Counter({
      name: 'node_failed_requests',
      help: 'failed requests',
    });
    
    // latency - histogram
    const nodeLatenciesHistogram = new prometheus.Histogram({
      name: 'node_request_latency',
      help: 'request latency by path',
      labelNames: ['route'],
      buckets: [100, 400],
    });

    Python

    PYTHON_REQUESTS_COUNTER = Counter("python_requests", "total requests")
    PYTHON_FAILED_REQUESTS_COUNTER = Counter("python_failed_requests", "failed requests")
    PYTHON_LATENCIES_HISTOGRAM = Histogram(
        "python_request_latency", "request latency by path"
    )
  3. Prometheus 指標を公開するエンドポイントを定義します(Express を使用)。

    Go

    http.Handle("/metrics", promhttp.Handler())

    Node.js

    app.get('/metrics', async (req, res) => {
      try {
        res.set('Content-Type', register.contentType);
        res.end(await register.metrics());
      } catch (ex) {
        res.status(500).end(ex);
      }
    });

    Python

    @app.route("/metrics", methods=["GET"])
    def stats():
        return generate_latest(REGISTRY), 200
    
    
  4. カウンタ指標を適切に増分します。

    Go

    requestCount.Inc()
    
    // Fails 10% of the time.
    if rand.Intn(100) >= 90 {
    	log.Printf("intentional failure encountered")
    	failedRequestCount.Inc()
    	http.Error(w, "intentional error!", http.StatusInternalServerError)
    	return
    }

    Node.js

    // increment total requests counter
    nodeRequestsCounter.inc();
    // return an error 10% of the time
    if (Math.floor(Math.random() * 100) > 90) {
      // increment error counter
      nodeFailedRequestsCounter.inc();

    Python

    PYTHON_REQUESTS_COUNTER.inc()
    # fail 10% of the time
    if random.randint(0, 100) > 90:
        PYTHON_FAILED_REQUESTS_COUNTER.inc()
  5. レイテンシ指標を適切に追跡します。

    Go

    requestReceived := time.Now()
    defer func() {
    	responseLatency.Observe(time.Since(requestReceived).Seconds())
    }()

    Node.js

    // start latency timer
    const requestReceived = new Date().getTime();
    console.log('request made');
    // increment total requests counter
    nodeRequestsCounter.inc();
    // return an error 10% of the time
    if (Math.floor(Math.random() * 100) > 90) {
      // increment error counter
      nodeFailedRequestsCounter.inc();
      // return error code
      res.send('error!', 500);
    } else {
      // delay for a bit
      sleep.msleep(Math.floor(Math.random() * 1000));
      // record response latency
      const responseLatency = new Date().getTime() - requestReceived;
      nodeLatenciesHistogram.labels(req.route.path).observe(responseLatency);

    Python

    @PYTHON_LATENCIES_HISTOGRAM.time()

取り込みを構成する

サービスが実行されてエンドポイントに指標が出力されたら、Prometheus のスクレイピングStackdriver コレクタに対する適切な設定を構成し、Cloud Monitoring に指標を取り込みます。

この構成によって、Prometheus 指標が Monitoring にどのように表示されるかが決まります。この例では、Prometheus の指標は次のようにマッピングされています。

  • nodeRequestCounterexternal.googleapis.com/prometheus/total_request_count になります。
  • nodeFailedRequestCounterexternal.googleapis.com/prometheus/error_count になります。
  • nodeLatenciesHistogramexternal.googleapis.com/prometheus/reponse_latency になります。

関連付けられているモニタリング対象リソースのタイプは k8s_container です。

取り込まれた指標を使用して SLI を定義します。

可用性 SLI

Cloud Monitoring では、TimeSeriesRatio 構造体を使用してリクエスト ベースの可用性 SLI を表現します。次の例は、取り込んだ Prometheus 指標を使用する SLO を示しています。28 日間の移動枠内で、サービスが 98% の可用性を持つことが、リクエスト総数に対する悪いリクエストの割合によって計算されます。

{
 "serviceLevelIndicator": {
   "requestBased": {
     "goodTotalRatio": {
       "totalServiceFilter":
         "metric.type=\"external.googleapis.com/prometheus/total_request_count\"
          resource.type=\"k8s_container\"",
       "badServiceFilter":
         "metric.type=\"external.googleapis.com/prometheus/error_count\"
          resource.type=\"k8s_container\""
     }
   }
 },
 "goal": 0.98,
 "rollingPeriod": "2419200s",
 "displayName": "98% Availability, rolling 28 days"
}

レイテンシ SLI

Cloud Monitoring では、DistributionCut 構造体を使用してリクエスト ベースのレイテンシ SLI を表現します。次の例は、取り込まれた Prometheus レイテンシ指標を使用する SLO を示しています。リクエストの 98% が 1 日の移動枠内のウィンドウで 500 ミリ秒以内に完了すると期待されています。

{
  "serviceLevelIndicator": {
    "requestBased": {
      "distributionCut": {
        "distributionFilter":
          "metric.type=\"external.googleapis.com/prometheus/response_latency\"
           resource.type=\"k8s_container\"",
        "range": {
          "min": 0,
          "max": 500
        }
      }
    }
  },
  "goal": 0.98,
  "rollingPeriod": "86400s",
  "displayName": "98% requests under 500 ms"
}