記事一覧はこちら
背景・モチベーション
仕事でexporterを書くときにスッと書けるようにという練習がてら。
オンボーディングの中で自分でマイクロサービスをデプロイしてみるというタスクもあり、そこでも使える気がしたので一石二鳥を狙っている。
プロダクトバックログとかの量とかタスクの見積もりとかをaggregateしたりで可視化出来るのは悪くないような気も。GitHubのAPIも利用するとLabelでいろんな属性を付けられるので良さげなんだけれども、とりあえず今回はZenHubのAPIのみを使っている。
ちなみにGrafanaのorgの下には、ほとんど手が付けられていないzenhub-exporterがあったりする。
本編
Exporterの書き方はここに書いてある。公式でツールセットだったりも提供してくれていて非常に手厚い。
exporterのデフォルトのポート番号を選ぶ
Prometheusのexporterは1つのホスト上で複数動くことが充分に考えられるため、デフォルトのポート番号がwikiで整理されている(詳細)。使われていないポート番号を選ぶことを始めた。今回はポート番号9861
を利用することにした。実際に広く使われるexporterを作成する場合には、このwikiを編集する必要がある。
Exporterを実装する上で気をつけたいこと
前述のWriting Exporterというページを参考に一部抜粋してみる。
📝 のマークが付いているのは自分の感想でドキュメントに書いてあった内容ではありません。
Configuration
YAMLがPrometheusの標準的な設定記述なので、もし設定ファイルを必要とする場合にはYAMLを利用すること。
Metrics
Naming
メトリクスの名前は特定のシステムに詳しくない人でも類推できるものする必要がある。そのため、メトリクス名は、通常 haproxy_up
のように、exporter名をprefixに付ける必要がある。
メトリクスはsecondsやbyteなどのbase unitsを使用して、Grafanaなどのgraphing toolsで読みやすいようにしておく。同様に、パーセンテージではなくratioを利用する。
📝 これは自分の中でも印象が強く、node_exporterがいつだったかのバージョンアップの時に今までなかった単位がメトリクスのsuffixに足されたのを覚えている。
Metrics名にはexportされる際のラベルを含めるべきではない。ただし、同じデータを複数のメトリクスで異なるラベルを付けてエクスポートしている場合は例外だ。単一のメトリクスにすべてのラベルを付けてエクスポートするとカーディナリティが高くなりすぎる場合にのみこの例外対応が必要になる。
📝 ここでのカーディナリティはRDBでなく時系列DBの用語を指すので注意(fukabori.fm #53)
Prometheus のメトリクスとラベル名はsnake_case
になる。
_sum
_count
_bucket
_total
といったsuffixはSummaryやHistograms、Countersに使われるため、それら以外であればこれらのsuffixは利用しないこと。_total
はcounter用に予約されているのでCOUNTERタイプのメトリクスであれば利用するべきsuffixである。
process_
とscrape_
というprefixは予約されているので利用しないこと。ただし、これらに独自のprefixを追加した場合には利用可能となる。たとえば、jmx_scrape_duration_seconds
は問題ない。
成功リクエストと失敗リクエストの数がある時に、これを公開する一番良い方法は一つのメトリクスではトータルのリクエスト数、もう一つのメトリクスでは失敗したリクエストを扱うこと。 1つのメトリクスに失敗または成功のラベルを付けないように注意する!
同様に、キャッシュのヒットまたはミスについても、1つのメトリクスを総計に、もう1つのメトリクスをヒット数にするのがよい。
📝 ついついメトリクスを分けるのではなくLabelで対応しがちなので特に気をつけたい項目
Labels
ラベルには関してはこちらのドキュメントにおいても言及されています。
一般的なガイドラインとしてメトリクスのカーディナリティを10以下にするようにして超えるものは一握りとする。
📝 ここではLabelの取りうる値のことについて言っていると自分は解釈しました。保存するデータ量はLabelの取りうる値の掛け算によって決まるので非常にパフォーマンスに影響が出やすい部分のはずです。cardinality-is-keyというブログを参考にしました。
なにか事象が起こるまで存在しない時系列データは扱いが難しくなるため、事前に存在する可能性があるとわかっている時系列データに関しては0などのデフォルト値を書き出しておくこと。
ターゲットラベルと衝突するラベル名は避けることが推奨されている。例えば、region
, zone
, cluster
, availability_zone
, az
, datacenter
, dc
, owner
, customer
, stage
, service
, environment
, env
など。
📝 ターゲットラベルと言っているのはおそらくExporterごとに付与されるラベルのことと思います。例えば、ec2のService Discoveryを利用されている場合にはmetaラベルでAZが取得できるので、それをavailability_zone
にrelabelするのはよくあるかなあと思います。
Read/writeとsend/receiveはラベルとしてではなく、別々のメトリクスとして使用するのが最適です。これは通常、一度に1つだけを気にするからであり、その方が使いやすいからです。
ラベルの付与は慎重に。ラベルが増えると、ユーザがPromQLを作成するときに考慮しなければならないことが増えます。メトリックに関する追加情報は、infoメトリックを介して追加することができます。例えば、kube_pod_info
とかがそれに当たります。
Types
MetricsのTypeは通常CounterかGaugeだけれども、もしデクリメントのあるCounterの場合には誤解を招かないようにGAUGEではなく、UNTYPEDを利用する。
Help Strings
ヘルプをメトリクスそれぞれを付けておく、メトリクスのSourceが分かると良い。
Collector
ExporterのCollectorを実装する場合には、scrapeごとにメトリクスを更新するという方法を取るべきではない。
毎回新しいメトリクスを作成してください。GoではCollect()メソッドでMustNewConstMetric()
を使ってメトリクスの作成を行います。
📝 作成されるメトリクスの構造体は変更できないようにフィールドがunexportedになっています。
新しいメトリクスを毎回作成する理由は2つあります。
- 2つのscrapeが同時に発生する可能性があり、direct instrumentationの場合にはグローバル変数が競合する可能性がある
- ラベルの値が消失したときにもexportされてしまう
Elasticsearch などの多くのシステムでは、CPU、メモリ、ファイルシステム情報などのマシン・メトリクスが公開されています。Prometheusのエコシステムではnode_exporterがこれらを提供しているので、このようなメトリクスは削除すべきです。
Deployment
それぞれのexporterは1つのinstanceのアプリケーションを監視すべきで、できれば同じマシン上で隣り合っているのが望ましい。
ただし2つの例外があります。
- 監視しているアプリケーションの横で実行することが全く無意味な場合
- 📝 今回作った
zenhub_exporter
やblackbox_exporter
はこのケースに当てはまる
- 📝 今回作った
- システムのランダムなインスタンスからいくつかの統計情報を引き出し、どのインスタンスと話しているかを気にしない場合
Scheduling
メトリクスは、Prometheus がscrapeするときにのみアプリケーションから引き出されるべきであり、exporterは独自のタイマーに基づいてscrateを実行すべきでない。
公開するメトリクスにタイムスタンプを設定すべきではなく、Prometheusに任せたほうが良い。
メトリクスの取得に1分以上かかるなど、特にコストがかかる場合はキャッシュしても構いわないが、HELP Stringsにその旨を記載する。
Prometheusのデフォルトのscrape timeoutは10秒です。もしexporterがこれを超えることが予想される場合は、ドキュメントの中で明示的にその旨を伝える必要がある。
Failed scrapes
Scrapeに失敗したときの処理はパターン主に2つ。2つ目はexporterのダウンとアプリケーションのダウンの見分けが付く点で優れる。
- 5xxエラーを返す
- myexporter_up(例:haproxy_up)という変数を用意して、スクレイプが成功したかどうかに応じて0または1の値を持つ
Landing page
http://yourexporter/
にユーザがアクセスした時に見せるシンプルなHTMLを用意して、そこにはexporterの名称と/metrics
ページへのリンクを貼るべし。
上記踏まえて ZenHub Exporter 作った
上記を踏まえて、今回作ったアプリケーションのリポジトリはこちらです。
ビルドやリリースのツールもPrometheusが提供していて、haproxy_exporter
やnode_exporter
などのPrometheus
のorgで管理されているリポジトリはだいたいpromuというツールを使ってリリースなどが行われているそうです。実際には、promu
を利用したCircleCIのOrbが作られており、それを各リポジトリから利用されているようです。ドラフトのリリースを作ってくれます。
今回はそのpromu
というツールでビルドやリリースを行うようにしてみました。便利ではありますし、Officialのexporterとリリース形式が同じになるため良いですが、必ずしも利用する必要性が無さそうなところも見えましたので普通にgoreleaserを使っても良いのではと思います。ただしその際には、ldflags
でgithub.com/prometheus/common/version.Version
などにバージョン設定をしないと、--version
実行時に歯抜けで表示されるので注意です。
まとめ(雑記?)
OpenMetricsを有効化したけど、どこからUnitを設定するのかよく分からなかった。スペックを読む限りだとTypeやHelpと同じところに表示されそうな気がしてるんだけど、Prometheusのclientライブラリからは読み取れなかった…。