エクスポーターの作成

独自のコードをインストルメントする場合は、Prometheus クライアントライブラリを使用してコードをインストルメントする方法に関する一般的なルールに従う必要があります。他の監視システムやインストルメンテーションシステムからメトリックを取得する場合は、必ずしも明確ではありません。

このドキュメントには、エクスポーターまたはカスタムコレクターを作成する際に考慮すべき事項が記載されています。ここで説明する理論は、直接インストルメンテーションを行う人にも役立ちます。

エクスポーターを作成していて、ここに記載されている内容が不明な場合は、IRC(libera の #prometheus)またはメーリングリストでお問い合わせください。

保守性と純粋性

エクスポーターを作成する際に、最も重要な決定事項は、完璧なメトリックを取得するためにどれだけ努力するかです。

対象のシステムが、めったに変更されない少数のメトリックしかない場合、すべてを完璧にするのは簡単な選択です。これの良い例はHAProxy エクスポーターです。

一方、新しいバージョンで頻繁に変更される数百のメトリックがあるシステムで完璧を目指そうとすると、継続的な作業が非常に多くなります。MySQL エクスポーターはこのスペクトルの反対側にあります。

Node エクスポーターはこれらの混合であり、モジュールによって複雑さが異なります。たとえば、`mdadm`コレクターはファイルを手で解析し、そのコレクター用に特別に作成されたメトリックを公開するため、メトリックを正しく取得することができます。`meminfo`コレクターでは、カーネルバージョンによって結果が異なるため、有効なメトリックを作成するための変換を最小限に行うことになります。

設定

アプリケーションを扱う場合は、アプリケーションの位置を指定する以外、ユーザーによるカスタム設定を必要としないエクスポーターを目指すべきです。また、メトリックが非常に詳細で、大規模な設定でコストがかかる可能性がある場合、特定のメトリックを除外する機能を提供する必要がある場合もあります。たとえば、HAProxy エクスポーターでは、サーバーごとの統計情報のフィルタリングが可能です。同様に、デフォルトで無効になっている高コストのメトリックが存在する場合があります。

他の監視システム、フレームワーク、プロトコルを扱う場合は、Prometheus に適したメトリックを生成するために、追加の設定またはカスタマイズが必要になることがよくあります。理想的なシナリオでは、監視システムのデータモデルは Prometheus と十分に類似しているため、メトリックを変換する方法を自動的に判断できます。これはCloudwatchSNMPcollectdの場合です。ほとんどの場合、ユーザーがどのメトリックを抽出したいかを選択できるようにする必要があります。

他のケースでは、システムのメトリックは完全に非標準であり、システムの使用状況と基盤となるアプリケーションによって異なります。その場合、ユーザーはメトリックを変換する方法を指定する必要があります。JMX エクスポーターは、この点で最も問題が多いですが、GraphiteおよびStatsDエクスポーターも、ラベルを抽出するために設定が必要です。

設定なしでエクスポーターがすぐに動作するようにし、必要に応じて変換の例の設定を提供することをお勧めします。

YAML は標準の Prometheus 設定形式であり、すべての設定はデフォルトで YAML を使用する必要があります。

メトリック

命名

メトリックの命名に関するベストプラクティスに従ってください。

一般的に、メトリック名は、Prometheus に精通しているが特定のシステムに精通していない人が、メトリックの意味をある程度推測できるようにする必要があります。`http_requests_total`というメトリック名はあまり役に立ちません。これは、受信時、何らかのフィルタリング時、またはユーザーのコードに到達したときに測定されているのでしょうか?そして`requests_total`はさらに悪く、どのような種類の要求でしょうか?

直接インストルメンテーションでは、特定のメトリックは正確に1つのファイル内に存在する必要があります。したがって、エクスポーターとコレクター内では、メトリックは正確に1つのサブシステムに適用され、それに応じて命名する必要があります。

カスタムコレクターまたはエクスポーターを作成する場合を除き、メトリック名は手続き的に生成されるべきではありません。

アプリケーションのメトリック名は、一般的にエクスポーター名で始まる必要があります(例:`haproxy_up`)。

メトリックは基本単位(例:秒、バイト)を使用し、グラフツールでより読みやすい単位に変換することを残します。使用する単位に関係なく、メトリック名内の単位は使用中の単位と一致する必要があります。同様に、パーセンテージではなく比率を公開します。さらに良いのは、比率の2つの構成要素それぞれに対してカウンターを指定することです。

メトリック名には、エクスポートされるラベル(例:`by_type`)を含めるべきではありません。これは、ラベルが集計されると意味がなくなります。

例外は、複数のメトリックを介して異なるラベルを使用して同じデータをエクスポートする場合です。その場合、通常はそれらを区別する最も安全な方法です。直接インストルメンテーションでは、すべてのラベルを含む単一のメトリックをエクスポートするとカーディナリティが高すぎる場合にのみ発生するはずです。

Prometheus メトリックとラベル名は、`snake_case`で記述されています。`camelCase`を`snake_case`に変換することは望ましいですが、自動的にそうすると`myTCPExample`や`isNaN`のようなものに対して常に良い結果が得られるわけではなく、場合によってはそのままにしておく方が最善です。

公開されたメトリックにはコロンを含めるべきではありません。これらは、集計時にユーザー定義のレコーディングルールが使用するもので予約されています。

メトリック名では`[a-zA-Z0-9:_]`のみが有効です。

`_sum`、`_count`、`_bucket`、`_total`のサフィックスは、サマリー、ヒストグラム、カウンターで使用されます。それらのいずれかを生成しない限り、これらのサフィックスは避けてください。

`_total`はカウンターの慣習であり、COUNTER型を使用する場合は使用する必要があります。

`process_`と`scrape_`プレフィックスは予約されています。対応するセマンティクスに従う場合、これらのプレフィックスに独自のプレフィックスを追加しても問題ありません。たとえば、Prometheusにはスクレイプにかかった時間を示す`scrape_duration_seconds`がありますが、特定のエクスポーターにかかった時間を示すエクスポーター中心のメトリック(例:`jmx_scrape_duration_seconds`)を持つことも良い習慣です。PIDにアクセスできるプロセスの統計情報については、GoとPythonの両方でこれを処理するコレクターを提供しています。これの良い例はHAProxy エクスポーターです。

成功したリクエスト数と失敗したリクエスト数がある場合、これらを公開する最良の方法は、合計リクエストに対する1つのメトリックと失敗したリクエストに対する別のメトリックを使用することです。これにより、失敗率を簡単に計算できます。失敗または成功のラベルを使用する1つのメトリックは使用しないでください。同様に、キャッシュのヒットまたはミスでは、合計に対する1つのメトリックとヒットに対する別のメトリックを使用する方が優れています。

モニタリングを使用するユーザーが、メトリック名についてコードまたはウェブ検索を行う可能性を考慮してください。メトリック名が確立されており、それらの名前に慣れている人々(SNMPやネットワークエンジニアなど)の範囲外では使用されない可能性が高い場合は、現状のままにしておくのが良い考えかもしれません。このロジックはすべてのエクスポーターに適用されるわけではありません。たとえば、MySQLエクスポーターのメトリックは、DBAだけでなく、さまざまな人々によって使用される可能性があります。元の名前を含むHELP文字列は、元の名前を使用することとほぼ同じ利点を提供できます。

ラベル

ラベルに関する一般的なアドバイスをお読みください。

typeをラベル名として使用することは避けてください。これは汎用的すぎるため、多くの場合意味がありません。また、可能な限り、ターゲットラベルと衝突する可能性のある名前(regionzoneclusteravailability_zoneazdatacenterdcownercustomerstageserviceenvironmentenvなど)を使用しないようにしてください。ただし、アプリケーションがリソースをこのように呼び出している場合は、名前を変更して混乱を招かないようにするのが最善です。

接頭辞を共有しているという理由だけで、複数の項目を1つのメトリックに入れるという誘惑を避けてください。何かが1つのメトリックとして意味をなすと確信できる場合を除き、複数のメトリックの方が安全です。

ラベルleはヒストグラムで、quantileはサマリーで特別な意味を持ちます。これらのラベルは一般的に避けてください。

読み書きと送受信は、ラベルではなく、個別のメトリックとして扱うのが最善です。これは通常、一度に1つだけを気にする場合が多く、その方が使いやすいためです。

経験則として、1つのメトリックは合計または平均化されたときに意味をなすはずです。エクスポーターではもう1つのケースがあり、データが基本的に表形式であり、そうでなければユーザーがメトリック名で正規表現を使用する必要がある場合です。マザーボード上の電圧センサーを考えてみてください。それらをまたがる計算は意味がありませんが、センサーごとに1つのメトリックを持つよりも、それらを1つのメトリックにまとめる方が理にかなっています。メトリック内のすべての値は(ほとんどの場合)常に同じ単位を持つ必要があります。たとえば、ファン速度と電圧が混在していて、自動的に分離する方法がない場合を考えてみてください。

これをしてはいけません

my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{label="total"} 7

またはこれ

my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{} 7

前者は、メトリックに対してsum()を実行するユーザーにとって問題を引き起こし、後者は合計を壊し、非常に扱いにくくなります。Goなどのクライアントライブラリの一部は、カスタムコレクターで後者を実行することを積極的に阻止しようとします。すべてのクライアントライブラリは、直接的な計装で後者を実行することを阻止する必要があります。これらはいずれも行わず、代わりにPrometheusの集計に依存してください。

モニタリングでこのような合計値を公開している場合は、合計値を削除してください。何らかの理由で保持する必要がある場合(たとえば、合計値には個別にカウントされないものが含まれている場合)、異なるメトリック名を使用してください。

計装ラベルは最小限にする必要があります。追加のラベルごとに、ユーザーはPromQLを作成する際に考慮する必要があるラベルが1つ増えます。したがって、時系列の一意性を損なうことなく削除できる計装ラベルは避けてください。メトリックに関する追加情報は、情報メトリックを使用して追加できます。バージョン番号の処理方法の例を以下に示します。

ただし、メトリックの事実上すべてのユーザーが追加情報を必要とする場合もあります。その場合は、情報メトリックではなく、一意ではないラベルを追加するのが適切な解決策です。たとえば、mysqld_exportermysqld_perf_schema_events_statements_totaldigestラベルは、完全なクエリパターンのハッシュであり、一意性には十分です。ただし、人間が読めるdigest_textラベルがないとほとんど役に立ちません。長いクエリでは、クエリパターンの先頭のみが含まれるため、一意ではありません。そのため、人間用のdigest_textラベルと一意性のためのdigestラベルの両方が使用されます。

ターゲットラベル、静的スクレイピングラベルではない

すべてのメトリックに同じラベルを適用したいと思うことがあれば、停止してください。

一般的に、この問題が発生するケースは2つあります。

1つ目は、ソフトウェアのバージョン番号など、メトリックに付加すると便利なラベルに関するものです。代わりに、https://www.robustperception.io/how-to-have-labels-for-machine-roles/で説明されているアプローチを使用してください。

2つ目のケースは、ラベルが実際にはターゲットラベルである場合です。これらは、アプリケーション自体ではなく、インフラストラクチャのセットアップから取得されるリージョン名、クラスタ名などです。アプリケーションがラベル分類でどこに適合するかをアプリケーションが決定することはありません。これは、Prometheusサーバーを実行している人が設定するものであり、同じアプリケーションを監視する人によって異なる名前が付けられる可能性があります。

したがって、これらのラベルは、使用しているサービス検出を通じて、Prometheusのスクレイプ設定に属します。ここでは、マシンロールの概念も適用できます。これは、それをスクレイピングする人にとって少なくとも役立つ情報である可能性が高いからです。

タイプ

メトリックのタイプをPrometheusのタイプに合わせるようにしてください。これは通常、カウンターとゲージを意味します。サマリーの_count_sumも比較的一般的であり、場合によっては分位数も見られます。ヒストグラムはまれです。ヒストグラムを見つけた場合は、エクスポジション形式が累積値を公開することを覚えておいてください。

特に、メトリックのセットを自動的に処理している場合は、メトリックの種類が明らかではないことがよくあります。一般的に、UNTYPEDは安全なデフォルトです。

カウンターは減少できないため、別の計装システムから取得した減らすことができるカウンタータイプ(たとえば、Dropwizardメトリック)がある場合は、カウンターではなくゲージです。カウンターとして使用されている場合は誤解を招く可能性があるため、UNTYPEDはそこで使用するのに最適なタイプです。

ヘルプ文字列

メトリックを変換する際には、ユーザーが元の状態と、その変換の原因となったルールを追跡できるようにすることが役立ちます。コレクターまたはエクスポーターの名前、適用されたルールのID、元のメトリックの名前と詳細をヘルプ文字列に入れることで、ユーザーを大幅に支援できます。

Prometheusは、1つのメトリックに異なるヘルプ文字列を持つことを好みません。複数のメトリックから1つのメトリックを作成する場合は、ヘルプ文字列に1つを選択してください。

これの例として、SNMPエクスポーターはOIDを使用し、JMXエクスポーターはサンプルのmBean名を入れます。HAProxyエクスポーターには手書きの文字列があります。ノードエクスポーターにもさまざまな例があります。

あまり役に立たない統計を削除する

一部の計装システムは、最小値、最大値、標準偏差に加えて、1分、5分、15分のレート、アプリケーションの開始からの平均レート(これらは、たとえばDropwizardメトリックではmeanと呼ばれます)を公開します。

これらはすべて削除する必要があります。あまり役に立たない上に、混乱を招くからです。Prometheusはレートを自分で、通常はより正確に計算できます。公開されている平均は通常、指数関数的に減衰するためです。最小値または最大値がどの時間間隔で計算されたかわからず、標準偏差は統計的に無意味であり、必要に応じて常に二乗和、_sum_countを公開できます。

分位数には関連する問題があります。削除するか、サマリーに入れることができます。

ドット付き文字列

多くのモニタリングシステムにはラベルがなく、my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3のようなことを行います。

GraphiteStatsDエクスポーターは、小さな構成言語を使用してこれらを変換する方法を共有しています。他のエクスポーターも同様の機能を実装する必要があります。変換は現在Goでのみ実装されており、個別のライブラリにファクタリングすることで利点が得られます。

コレクター

エクスポーターのコレクターを実装する際には、通常の直接計装アプローチを使用してから、各スクレイピングでメトリックを更新することは決してありません。

代わりに、毎回新しいメトリックを作成します。Goでは、これはCollect()メソッドでMustNewConstMetricを使用して行われます。Pythonの場合はhttps://github.com/prometheus/client_python#custom-collectorsを参照し、Javaの場合はcollectメソッドでList<MetricFamilySamples>を生成します。例については、StandardExports.javaを参照してください。

これには2つの理由があります。まず、2つのスクレイピングが同時に発生する可能性があり、直接計装は事実上ファイルレベルのグローバル変数を使用するため、競合状態が発生します。次に、ラベル値が消えると、それでもエクスポートされます。

直接計装を使用してエクスポーター自体を計装することは問題ありません(例:すべてのスクレイピングでエクスポーターによって転送された合計バイト数または実行された呼び出し数)。blackboxエクスポーターSNMPエクスポーターなど、単一のターゲットに結び付けられていないエクスポーターの場合、これらは特定のターゲットのスクレイピングではなく、プレーンな/metrics呼び出しでのみ公開する必要があります。

スクレイピング自体のメトリック

スクレイピングに関するメトリック(所要時間や処理されたレコード数など)をエクスポートしたい場合があります。

これらは、イベント(スクレイピング)に関するものであるため、ゲージとして公開し、メトリック名をエクスポーター名で接頭辞を付けます(例:jmx_scrape_duration_seconds)。通常、_exporterは除外され、エクスポーターをコレクターとして使用することも意味をなす場合は、確実に除外してください。

マシンとプロセスのメトリック

Elasticsearchなど多くのシステムは、CPU、メモリ、ファイルシステムの情報などのマシンメトリクスを公開しています。Node ExporterがPrometheusエコシステム内でこれらを提供するため、このようなメトリクスは削除する必要があります。

Javaの世界では、多くの計測フレームワークが、CPUやGCなど、プロセスレベルとJVMレベルの統計情報を公開しています。JavaクライアントとJMXエクスポーターは、DefaultExports.javaを介して、既にこれらの情報を推奨される形式で含んでいるため、これらも削除する必要があります。

他の言語やフレームワークについても同様です。

デプロイメント

各エクスポーターは、正確に1つのインスタンスアプリケーションを監視する必要があります。できれば、同じマシン上でアプリケーションのすぐ隣に配置します。つまり、実行するHAProxyごとに、haproxy_exporterプロセスを実行します。Mesosワーカーを持つマシンごとに、そのマシン上にMesosエクスポーターを実行し、マシンに両方がある場合はマスター用にもう一つ実行します。

この背後にある考え方は、直接計測を行う場合、これが行うことであり、他のレイアウトでも可能な限りそれに近づけようとしていることです。これは、すべてのサービス検出がエクスポーターではなくPrometheusで行われることを意味します。これには、Prometheusが必要なターゲット情報を持ち、ユーザーがBlackboxエクスポーターを使用してサービスをプローブできるようにするという利点もあります。

例外が2つあります。

1つ目は、監視対象のアプリケーションの隣で実行することが全く無意味な場合です。SNMP、Blackbox、IPMIエクスポーターが主な例です。IPMIおよびSNMPエクスポーターは、デバイスが多くの場合、コードを実行できないブラックボックスであるため(ただし、代わりにNodeエクスポーターを実行できればより良いでしょう)、BlackboxエクスポーターはDNS名などのものを監視する場合、実行するものが何もありません。この場合、Prometheusは依然としてサービス検出を行い、スクレイピングされるターゲットを渡す必要があります。BlackboxエクスポーターとSNMPエクスポーターを参照してください。

現在、このタイプのエクスポーターはGo、Python、Javaクライアントライブラリでのみ記述可能です。

2つ目の例外は、システムのランダムなインスタンスからいくつかの統計情報を取得し、どのインスタンスに話しかけているかは気にしない場合です。ビジネスクエリを実行してデータに対してエクスポートしたいMySQLレプリカのセットを考えてみましょう。通常のロードバランシングアプローチを使用して1つのレプリカに接続するエクスポーターを使用することが、最も理にかなったアプローチです。

これは、マスター選出を行うシステムを監視する場合には適用されません。その場合、各インスタンスを個別に監視し、Prometheusで「マスター状態」を処理する必要があります。常に正確に1つのマスターが存在するとは限らず、Prometheusの下でターゲットを変更すると、奇妙な現象が発生するためです。

スケジューリング

メトリクスは、Prometheusがスクレイピングする際にのみアプリケーションから取得する必要があります。エクスポーターは、独自のタイマーに基づいてスクレイピングを実行しないでください。つまり、すべてのスクレイピングは同期する必要があります。

したがって、公開するメトリクスにタイムスタンプを設定しないでください。Prometheusにそれを処理させましょう。タイムスタンプが必要だと考える場合は、おそらくPushgatewayが必要でしょう。

メトリクスの取得に特にコストがかかる場合(1分以上かかる場合)、キャッシュしてもかまいません。これはHELP文字列に記載する必要があります。

Prometheusのデフォルトのスクレイプタイムアウトは10秒です。エクスポーターがこの時間を超える可能性がある場合は、ユーザー向けドキュメントに明示的に記載する必要があります。

プッシュ

StatsD、Graphite、collectdなど、一部のアプリケーションと監視システムはメトリクスをプッシュするだけです。

ここでは、2つの考慮事項があります。

まず、いつメトリクスを期限切れにするかということです。CollectdとGraphiteに接続するものはどちらも定期的にエクスポートし、停止するとメトリクスのエクスポートも停止します。Collectdには有効期限が含まれているため、それを利用します。Graphiteには含まれていないため、エクスポーターのフラグになります。

StatsDは少し異なります。メトリクスではなくイベントを処理するためです。最適なモデルは、各アプリケーションの横に1つのエクスポーターを実行し、アプリケーションの再起動時に再起動して状態をクリアすることです。

次に、これらのシステムは、ユーザーがデルタまたは生のカウンタを送信できるようにする傾向があります。それが一般的なPrometheusモデルであるため、可能な限り生のカウンタに依存する必要があります。

サービスレベルのメトリクス(例:サービスレベルのバッチジョブ)の場合、エクスポーターをPushgatewayにプッシュし、状態を自分で処理するのではなく、イベント後に終了する必要があります。インスタンスレベルのバッチメトリクスには、まだ明確なパターンがありません。選択肢としては、Nodeエクスポーターのテキストファイルコレクターを悪用するか、インメモリ状態に依存するか(再起動後に保持する必要がない場合はおそらく最適です)、またはテキストファイルコレクターと同様の機能を実装することです。

スクレイプ失敗

現在、通信しているアプリケーションが応答しない、またはその他の問題がある場合のスクレイプ失敗には、2つのパターンがあります。

1つ目は、5xxエラーを返すことです。

2つ目は、スクレイプが成功したかどうかによって0または1の値を持つmyexporter_up(例:haproxy_up)変数を持つことです。

後者は、スクレイプに失敗しても、HAProxyエクスポーターがプロセス統計を提供するなど、依然として取得できる便利なメトリクスがある場合に適しています。upは通常の方法で動作するため、前者はユーザーにとって少し扱いやすくなっていますが、エクスポーターがダウンしているのか、アプリケーションがダウンしているのかを区別することはできません。

ランディングページ

http://yourexporter/にアクセスすると、エクスポーターの名前と/metricsページへのリンクを含むシンプルなHTMLページが表示される方が、ユーザーにとって便利です。

ポート番号

ユーザーは、同じマシン上に多くのエクスポーターとPrometheusコンポーネントを持つ可能性があるため、それを容易にするために、それぞれに一意のポート番号が割り当てられます。

https://github.com/prometheus/prometheus/wiki/Default-port-allocationsで追跡しています。これは公開して編集できます。

エクスポーターを開発する際には、公開する前に、できれば次の空きポート番号を取得してください。まだリリースの準備ができていない場合は、ユーザー名とWIPを記載しても構いません。

これは、ユーザーの利便性を高めるためのレジストリであり、特定のエクスポーターを開発するというコミットメントではありません。内部アプリケーション用のエクスポーターには、デフォルトのポート割り当て範囲外のポートを使用することをお勧めします。

発表

エクスポーターを世界に発表する準備ができたら、メーリングリストにメールを送信し、利用可能なエクスポーターのリストに追加するPRを送信してください。

このドキュメントはオープンソースです。問題やプルリクエストを送信して、改善にご協力ください。