Prometheusのexporterのメトリックをcarbon-clickhouseに登録する
はじめに
Prometheusのexporterのメトリックをcarbon-clickhouseに登録しようとして調べてみた際のメモです。 実際に調べたのは、prometheus/node_exporter: Exporter for machine metricsだけなので、他のexporterだと違うことがあるかもしれません。
prometheus/prometheus: The Prometheus monitoring system and time series database.のソースも拾い読みしましたが、レポジトリがいろいろ分かれていて、正しい箇所を見れているかは不明です。
Prometheusのexporterが出力するメトリックの形式
テキスト形式
- Data model | Prometheusにメトリック名とラベルの形式について書かれている。
- 例:
metric_name{label1="value1",label2="value2"} 123
- メトリック名は
[a-zA-Z_:][a-zA-Z0-9_:]*
の正規表現にマッチすること。 - ラベル名は
[a-zA-Z_][a-zA-Z0-9_]*
の正規表現にマッチすること。__
で始まる名前は内部用で予約されているので使用禁止。
- ラベルの値は任意のUnicodeの文字を使用可。
- ラベルの値が空文字列の場合は、そのラベルを指定しないのと同じ扱いになる。
prompaser.nextToken()
でTABと半角空白は読み飛ばす。- 上記の例だと
metric_name { label1 = "value1" , label2 = "value2" } 123
のように空白を入れても同じ内容になる。
- 上記の例だと
Protocol Buffers形式
Protocol Buffersは以下protobufと略します。
Accept
ヘッダーにapplication/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited
と指定してhttp://${node_exporter_address}:9100/metrics
にリクエストを送ると、Protocol Buffers形式のレスポンスボディが返ってくる。- この値はgithub.com/prometheus/common/expfmtパッケージの定数の
FmtProtoDelim
に定義されているがdeprecated。- 代わりに
expfmt.NewFormat(expfmt.TypeProtoDelim)
を使えとのこと。 - 戻り値の型expfmt.Formatは
string
にtypedef
されているので、文字列に変換するにはstring(Formatの値)
で良い。
- 代わりに
- レスポンスボディはgithub.com/prometheus/client_model/go.MetricFamilyのインスタンスが複数並べられた形式になる。
- この値はgithub.com/prometheus/common/expfmtパッケージの定数の
- prometheus/client_model: Data model artifacts for Prometheus.
- Deprecation noteを以下に抜粋。
- Prometheus v2.0.0からPrometheusサーバーはprotobuf形式でのデータ取り込みはしなくなっていた。
- そのため、Go以外の言語用のライブラリではprotobufサポートは外された。
- しかしGo用のprometheus/client_golang: Prometheus instrumentation library for Go applicationsはprotobufサポートが消されずに残っている。
- v2.40.0からネイティブヒストグラムの実験的なサポートがprotobuf形式に追加されたため、設定で有効にすればPrometheusサーバーはprotobuf形式でのデータ取り込みが出来るようになった。
carbon-clickhouseへのメトリック登録
go-graphite/carbon-clickhouse: Graphite metrics receiver with ClickHouse as storage
Graphiteのplaintext形式でのデータ登録
- Feeding In Your Data — Graphite 1.2.0 documentation
メトリックのパス 値 タイムスタンプ\n
という形式でデータを送って登録(複数行指定可能)。
- メトリックパスはタグも使える。
- Graphite Tag Support — Graphite 1.2.0 documentation
- 例:
my.series_name;tag1=value1;tag2=value2
- タグ名は1文字以上で
;!^=
以外のASCII文字。 - タグの値は1文字以上で
;
以外のASCII文字。先頭に~
は禁止。 - タグ名と値にUTF-8文字は動くかもしれないが、あまりテストされていない。
- 少なくともplaintext形式でデータ登録する際は、タグ名はタグの値に半角空白は含められない。
- receiver/Base.PlainParseLineで行頭から半角空白を探しているため。
- なお、本家のGraphiteプロジェクトのcarbonでも https://github.com/graphite-project/carbon/blob/1.1.10/lib/carbon/protocols.py#L196 で行頭から半角空白で分割している。
- pickleプロトコルなら含められるかもしれない。
- carbon-clickhouseでもreceiver/pickle.goで実装されている。
- ただし、登録した値をクエリで参照する際に問題が起きる可能性もあるので、半角空白は含めないほうが無難。
- carbon-clickhouseへのデータ登録は設定例の
[udp]
か[tcp]
の設定で指定したアドレスに上記の形式でデータを送ればよい。- この設定例を見るとcarbon.protoのprotobuf形式で
[grpc]
の設定のアドレスに送るという手もある。
- この設定例を見るとcarbon.protoのprotobuf形式で
Prometheusの形式でのメトリック登録
- carbon-clickhouseへのデータ登録は設定例の
[prometheus]
の設定をenabled = true
に変更し、ここで指定したアドレスにデータを送ればよい。- carbon-clickhouseで受信する実装はreceiver/PrometheusRemoteWrite.ServeHTTPにある。
- リクエストボディをsnappyで解凍した後、helper/prompb/remote.protoのWriteRequestとして解釈している。
- データ送信側は
WriteRequest
のインスタンスを作りMarshalメソッドでバイト列にシリアライズし、snappyで圧縮したものを送ればよい。
- carbon-clickhouseで受信する実装はreceiver/PrometheusRemoteWrite.ServeHTTPにある。
ClickHouseでのデータ格納形式
- carbon-clickhouseではGraphiteMergeTreeテーブルエンジンを使用。
- Graphite形式で登録しても、Prometheus形式で登録しても、格納される先は同じ。
- テーブル定義例
Path
カラムに格納されるメトリック名の形式はGraphiteMergeTreeのrule_type
のコードブロックのtagged
に説明がある。- 具体的には
someName?tag1=value1&tag2=value2&tag3=value3
という形式になる。
- 具体的には
Goでcarbon-clickhouseのパッケージを使う際の注意
レポジトリのURLは https://github.com/go-graphite/carbon-clickhouse だが、go.mod#L1でmodule github.com/lomik/carbon-clickhouse
と指定されている。Goからこのパッケージを使う際にはこのmoduleのパスのほうを指定する必要があるので要注意。
データ参照はgrahite-clickhouseを使う
- go-graphite/graphite-clickhouse: Graphite cluster backend with ClickHouse support
- doc/config.mdの
[common]
のアドレスでGraphite形式、[prometheus]
のアドレスでPrometheus形式でデータが取得できる。- Graphite data sourceとPrometheus data sourceで設定すればGrafanaのデータソースとして使える。
- Graphiteデータソースでクエリを書く場合
- Graphite Tag SupportのQueryingの項を参照。
- まず
seriesByTag('name=metric_name')
やseriesByTag('tag1=value1')
などを使い、そこから必要に応じてさらに対象を絞ったり加工しつつ参照する。
試してみたコード
環境構築用Ansible playobok
hnakamur/carbon-clickhouse-ansible-playbook
- create_incus_containers.shでIncusのコンテナを作り、このplaybookを実行する。
- graphite-clickhouseのREADMEのTL;DRセクションのPreconfigured docker-composeを参考にした。
exporterからのデータをcarbon-clickhouseに登録するサーバー
https://github.com/hnakamur/prometheus-exporter-to-carbon-clickhouse-relay
- prometheus/node_exporterを稼働中に、これを動かすとnode_exporterからメトリックを読み取ってcarbon-clickhouseに登録する。
- 現状はとりあえず動いたレベル。今後いろいろ変更するかも。
- github.com/prometheus/client_model/go.MetricFamilyからWriteRequestのTimeSeriesへの変換はconvertOneMetricFamilyToTimeSeries関数で実装。
- Metric types | Prometheus(コードではclient_model/io/prometheus/client/metrics.protoのenum MetricType)に応じて変換。
COUNTER
、GAUGE
、SUMMARY
、UNTYPED
は実装済み。SUMMARY
はnode_exporter
のテキスト形式のメトリック出力を参考に、1つのSUMMARY
を複数のTimeSeries
に変換するようにした。
HISTOGRAM
、GAUGE_HISTOGRAM
は未実装。node_exporter
のデフォルト設定ではこれらのタイプのメトリックは出力されなかったため。
- 現状の実装ではどのMetric typesだったかの情報は、変換後は失われる。
metric_type
といったラベルを付与するようなオプションを付けるか要検討。
- Metric types | Prometheus(コードではclient_model/io/prometheus/client/metrics.protoのenum MetricType)に応じて変換。