エクスポーターの作成

独自のコードを計測する場合、Prometheusクライアントライブラリでコードを計測する方法に関する一般的なルールに従う必要があります。別の監視または計測システムからメトリックを取得する場合、物事はそれほど明確ではありません。

このドキュメントには、エクスポーターまたはカスタムコレクターを作成する際に考慮すべき事項が含まれています。対象となる理論は、直接計測を行う方々にも興味深いでしょう。

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

保守性と純粋性

エクスポーターを作成する際に下す主要な決定は、完璧なメトリックを得るためにどれだけの労力を費やすかです。

もし対象のシステムがごく少数のメトリックしか持たず、ほとんど変化しないのであれば、すべてを完璧にすることは簡単な選択です。良い例がHAProxyエクスポーターです。

一方、システムが何百ものメトリックを持ち、新しいバージョンで頻繁に変更される場合でも、完璧にしようとすると、継続的な作業が大量に発生します。MySQLエクスポーターはこのスペクトルの端に位置します。

ノードエクスポーターはこれらの混合で、モジュールによって複雑さが異なります。例えば、`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`がありますが、特定のexporterがその処理を行うのにかかった時間を示すexporter中心のメトリック(例: `jmx_scrape_duration_seconds`)を持つことも良い習慣です。PIDにアクセスできるプロセス統計の場合、GoとPythonの両方でこれを処理するコレクターが提供されています。これの良い例はHAProxyエクスポーターです。

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

監視を使用する人がメトリック名をコード検索またはウェブ検索する可能性を考慮してください。もし名前が非常に定着しており、その名前に慣れている人々の領域以外では使用される可能性が低い場合(例えばSNMPやネットワークエンジニア)、そのままにしておくのが良い考えかもしれません。この論理はすべてのエクスポーターに当てはまるわけではありません。例えば、MySQLエクスポーターのメトリックは、DBAだけでなく、さまざまな人々によって使用される可能性があります。元の名前を持つ`HELP`文字列は、元の名前を使用するのとほとんど同じ利点を提供できます。

ラベル

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

`type`をラベル名として避けてください。これはあまりにも一般的で、しばしば意味がありません。また、`region`、`zone`、`cluster`、`availability_zone`、`az`、`datacenter`、`dc`、`owner`、`customer`、`stage`、`service`、`environment`、`env`など、ターゲットラベルと衝突する可能性のある名前を可能な限り避けるようにしてください。ただし、アプリケーションがそのリソースをそのように呼んでいる場合は、名前を変更して混乱を招かないのが最善です。

プレフィックスを共有しているからといって、すべてを1つのメトリックにまとめる誘惑に駆られないでください。あるものが1つのメトリックとして意味をなすと確信できない限り、複数のメトリックの方が安全です。

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

読み書きと送受信は、ラベルとしてではなく、別々のメトリックとして扱うのが最善です。これは通常、一度に片方しか気にしないため、その方が使いやすいためです。

経験則として、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()`する人にとって機能せず、後者はsumを壊し、扱いが非常に困難です。Goなどの一部のクライアントライブラリは、カスタムコレクターで後者を行うことを積極的に阻止しようとし、すべてのクライアントライブラリは、直接計測で後者を行うことを阻止するはずです。これらのいずれも決して行わず、代わりにPrometheusの集計に頼ってください。

もしあなたの監視がこのような合計を公開するなら、その合計は削除してください。もし何らかの理由でそれを維持しなければならない場合(例えば、合計には個別に数えられないものが含まれる場合)、異なるメトリック名を使用してください。

計測ラベルは最小限に抑えるべきです。余分なラベルは、ユーザーがPromQLを作成する際に考慮しなければならない要素が増えることになります。したがって、時系列の一意性に影響を与えることなく削除できる計測ラベルは避けるべきです。メトリックに関する追加情報は、情報メトリックを介して追加できます。バージョン番号の扱い方については以下を参照してください。

しかし、メトリックのほぼすべてのユーザーが追加情報を必要とすると予想される場合があります。その場合、情報メトリックではなく、非ユニークなラベルを追加するのが正しい解決策です。例えば、mysqld_exporterの`mysqld_perf_schema_events_statements_total`の`digest`ラベルは、完全なクエリパターンのハッシュであり、一意性を確保するには十分です。しかし、人間が読める`digest_text`ラベルがなければほとんど役に立ちません。長いクエリの場合、`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 metricsのように)減少する可能性がある場合、それはカウンターではなくゲージです。その場合、`UNTYPED`が最適なタイプでしょう。なぜなら、カウンターとして使用されているのに`GAUGE`は誤解を招く可能性があるからです。

ヘルプ文字列

メトリックを変換する場合、ユーザーが元のメトリックと、その変換を引き起こしたルールを追跡できると便利です。ヘルプ文字列にコレクターまたはエクスポーターの名前、適用されたルールのID、および元のメトリックの名前と詳細を含めることは、ユーザーに大いに役立ちます。

Prometheusは、1つのメトリックに異なるヘルプ文字列があることを好みません。多くのメトリックから1つのメトリックを作成する場合、そのうちの1つをヘルプ文字列に入れるようにしてください。

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

役に立たない統計を破棄する

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

これらはすべて破棄すべきです。あまり有用ではなく、ノイズを増やすだけだからです。Prometheusはレートを独自に、そして通常はより正確に計算できます。なぜなら、公開される平均値は通常、指数関数的に減衰するからです。最小値または最大値が計算された時間を知ることはできず、標準偏差は統計的に無用であり、必要であればいつでも二乗和、`_sum`、`_count`を公開して計算できます。

Quantilesにも同様の問題があり、それらを破棄するか、サマリーに入れるかを選択できます。

ドット付き文字列

多くの監視システムはラベルを持たず、代わりに`my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3`のような形式を使用します。

GraphiteエクスポーターとStatsDエクスポーターは、小さな設定言語でこれらを変換する方法を共有しています。他のエクスポーターも同様の実装をするべきです。この変換は現在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は除外され、エクスポーターがコレクターとしても意味がある場合は、必ず除外してください。

その他のスクレイプの「メタ」メトリクスは避けるべきです。例えば、スクレイプ回数のカウンターやスクレイプ時間のヒストグラムなどです。エクスポーターがこれらのメトリクスを追跡すると、Prometheus自体が自動的に生成するメトリクスと重複してしまいます。これは、すべてのエクスポーターインスタンスのストレージコストを増加させます。

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

Elasticsearchなどの多くのシステムでは、CPU、メモリ、ファイルシステム情報といったマシンメトリクスを公開しています。node exporterがPrometheusエコシステムでこれらを提供しているため、このようなメトリクスはドロップされるべきです。

Javaの世界では、多くの計測フレームワークがCPUやGCなどのプロセスレベルおよびJVMレベルの統計を公開しています。JavaクライアントとJMXエクスポーターは、DefaultExports.javaを通じて、これらの情報をすでに好ましい形式で含んでいるため、これらもドロップされるべきです。

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

デプロイメント

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

この背後にある理論は、直接計測の場合にはこれを行うことになり、他のレイアウトでも可能な限りそれに近づけようとしているということです。これは、すべてのサービスディスカバリがPrometheusで行われ、エクスポーターでは行われないことを意味します。これにより、Prometheusがblackbox exporterでサービスをプローブできるようにするために必要なターゲット情報を持つという利点もあります。

2つの例外があります

1つ目は、監視対象アプリケーションの隣で実行することが全く意味をなさない場合です。SNMP、ブラックボックス、IPMIのエクスポーターがその主な例です。IPMIとSNMPのエクスポーターは、多くの場合、コードを実行できないブラックボックスであるため(代わりにノードエクスポーターを実行できればより良いですが)、ブラックボックスエクスポーターはDNS名のようなものを監視する場合で、そこにも実行するものはありません。この場合でもPrometheusがサービスディスカバリを行い、スクレイプするターゲットを渡すべきです。ブラックボックスエクスポーターと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 exporterのtextfileコレクターを悪用するか、インメモリ状態に頼るか(再起動後も永続化する必要がない場合はおそらくこれが最善)、またはtextfileコレクターと同様の機能を実装するかのいずれかです。

スクレイプの失敗

現在、通信しているアプリケーションが応答しない、または他の問題がある場合のスクレイプ失敗には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を送信してください。

このページの内容