recording rule ベストプラクティス - Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

recording rule

レコーディングルールの一貫した命名体系によって、ルールの意味を一目で理解することが簡単になる。 また、不正確だったり意味のない計算が目立つようになり、間違いを避けることができる。

このページは、集約を正しく行うにはどうすればいいかを記し、また命名規約を提示する。

命名と集約

レコーディングルールは、一般的な形式level:metric:operationsになっているべきである。 levelは、集約レベルおよび出力されるラベルを表す。 metricは、メトリック名で、rate()irate()を利用した時に_totalを削除する以外は、変更するべきではない。 operationsは、メトリックに適用された演算のリストで、もっとも新しい演算が一番先頭に来る。

メトリック名を変更せずにしておくことで、メトリックが何か知ることが簡単になり、コードベースでの検索も簡単になる。

operationsを綺麗に保つために、sum()以外の演算がある場合は_sumを省略する。 結合性のある演算はまとめて良い(例えば、min_minminと同じである)。

利用すべき明らかな演算がない場合、sumを使う。 割り算をして比を取る場合、メトリクス名を_per_で区切り、演算はratioとする。

比を集約する場合、分母と分子を別々に集約した後、割り算をすること。 統計的に妥当ではないので、比の平均を取ったり、平均の平均をとったりしないように。

観測値の平均を計算するために、サマリーの_count_sumを集約し、割り算をする際に、それを比として扱うのは不恰好であろう。 代わりに、_count_sumという接尾辞をなくして、ratemeanで置き換えること。 これで、その時間幅の平均的な観測値を表している。

集約後に消したいラベルをwithoutで常に指定すること。 これは、jobなどの他の全てのラベルを保持することになり、コンフリクトを回避し、より有益なメトリクスやアラートが得られる。

ラベルpathを持つ秒間リクエストを集約する

- record: instance_path:requests:rate5m
  expr: rate(requests_total{job="myjob"}[5m])

- record: path:requests:rate5m
  expr: sum without (instance)(instance_path:requests:rate5m{job="myjob"})

リクエストと失敗の比を計算し、jobレベルの失敗の比に集約する。

- record: instance_path:request_failures:rate5m
  expr: rate(request_failures_total{job="myjob"}[5m])

- record: instance_path:request_failures_per_requests:ratio_rate5m
  expr: |
    (
        instance_path:request_failures:rate5m{job="myjob"}
      /
        instance_path:requests:rate5m{job="myjob"}
    )

# 分母と分子を集約し、pathレベルの比を得るために割り算する
- record: path:request_failures_per_requests:ratio_rate5m
  expr: |
    (
        sum without (instance)(instance_path:request_failures:rate5m{job="myjob"})
      /
        sum without (instance)(instance_path:requests:rate5m{job="myjob"})
    )

# メトリクス実装によって付与されたりインスタンス識別のためのラベルは残っていないので、
# レベルとして`job`を使う
- record: job:request_failures_per_requests:ratio_rate5m
  expr: |
    (
        sum without (instance, path)(instance_path:request_failures:rate5m{job="myjob"})
      /
        sum without (instance, path)(instance_path:requests:rate5m{job="myjob"})
    )

ある時間間隔の平均レイテンシーをサマリーから計算する

- record: instance_path:request_latency_seconds_count:rate5m
  expr: rate(request_latency_seconds_count{job="myjob"}[5m])

- record: instance_path:request_latency_seconds_sum:rate5m
  expr: rate(request_latency_seconds_sum{job="myjob"}[5m])

- record: instance_path:request_latency_seconds:mean5m
  expr: |
    (
        instance_path:request_latency_seconds_sum:rate5m{job="myjob"}
      /
        instance_path:request_latency_seconds_count:rate5m{job="myjob"}
    )

# 分母と分子を集約した後、割り算する
- record: path:request_latency_seconds:mean5m
  expr: |
    (
        sum without (instance)(instance_path:request_latency_seconds_sum:rate5m{job="myjob"})
      /
        sum without (instance)(instance_path:request_latency_seconds_count:rate5m{job="myjob"})
    )

関数avg()を使って、instanceとpathにまたがって平均クエリーレートが計算される

- record: job:request_latency_seconds_count:avg_rate5m
  expr: avg without (instance, path)(instance:request_latency_seconds_count:rate5m{job="myjob"})

入力されるメトリック名と比べると、withoutで指定されたラベルが出力されるメトリック名のレベルから消えていることに注意。 集約がない場合は、レベルが必ず同じになる。 そうでない場合は、ルールの中に間違いがある可能性が高い。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

セキュリティモデル - Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

SECURITY MODEL

Prometheusは、たくさんのコンポーネントとたくさんの他システムとの連携のある複雑なシステムである。 多種多様な、信頼性のある環境と信頼性のない環境にデプロイされる可能性がある。

このページは、Prometheusの一般的なセキュリティ上の仮定と、いくつかの設定で有効になる攻撃経路について記述する。

複雑なシステムであれば何であれ、バグがないという保証することは不可能である。 もし、セキュリティのバグを見つけたら、関係するリポジトリのMAINTAINERS.mdに挙げられているメンテナー個人にレポートし、prometheus-team@googlegroups.comにCCして下さい。 私たちは、その問題を修正し、リリース日をあなたと調整し、あなたが望むなら個人名を挙げてあなたの努力に謝意を示します。

Prometheus

PrometheusのHTTPエンドポイントとログに信頼性のないユーザーからのアクセスがあることが仮定されている。 データベースに含まれる全ての時系列に加えて各種の操作/デバッグ情報にアクセスできる。

また、信頼性のあるユーザーのみが、コマンドライン、設定ファイル、Prometheusとその他のコンポーネントの実行環境に関わるその他の側面を変更する力があることが仮定されている。

どの対象を、どれぐらいの頻度で、どのような設定でスクレイプするかは、完全に設定ファイルを通して決まる。 管理者は、サービスディスカバリーからの情報を使うこともできる。 サービスディスカバリーのデータを変更することができる人なら誰にでも、その情報をリラベリングと組み合わせることで、この制御を許してしまう可能性がある。

スクレイプの対象は、信頼性のないユーザーによって実行されている可能性がある。 あるターゲットが他のターゲットのふりをするデータを出力するのがデフォルトで可能であってはいけない。 honor_labelsは、この保護を取り除いてしまう。特定のリラベルの設定も同じことが起きてしまう可能性がある。

Prometheus 2.0以降では、フラグ--web.enable-admin-apiが、時系列削除などの機能を含む管理用のHTTP APIへのアクセスを制御する。 これはデフォルトで無効にされている。 有効化されると、管理用で変更を伴う機能が、パス/api/*/admin/以下でアクセス可能になる。 フラグ--web.enable-lifecycleは、Prometheusの再読み込みと停止を制御する。 これもデフォルトでは無効になっている。 有効になっている場合、パス/-/reload/-/quitでアクセス可能になる。

Prometheus 1.xでは、/-/reloadおよび/api/v1/seriesのDELETEの利用は、HTTP APIにアクセスできる人なら誰でもアクセス可能である。 エンドポイント/-/quitはデフォルトで無効になっているが、フラグ-web.enable-remote-shutdownで有効にできる。

remote read機能によって、HTTPアクセスできる人なら誰でもremote readエンドポイントにクエリを送信できるようになる。 例えば、もし、PromQLクエリが最終的にリレーショナルDBに対して直接実行されるならば、Prometheusに(Grafanaなどを通して)クエリを送信できる人なら誰でも任意のSQLをそのDBに対して出来ることになる。

Alertmanager

AlertmanagerのHTTPエンドポイントにアクセスできる人なら誰でもそのデータにアクセスできる。 アラートを作成したり解消できる。 サイレンスを作成したり、修正したり、削除できる。

どこに通知を送るかは、設定ファイルによって決まる。 テンプレートの設定次第で、通知の宛先がアラートによって決まってしまう。 例えば、宛先emailアドレスとしてアラートのラベルを使っていると、Alertmanagerにアラートを送信できる人なら誰でも、どんなemailアドレスにも通知を送ることが可能である。 もし、アラートで決まる宛先がテンプレート可能な秘密のフィールドならば、PrometheusまたはAlertmanagerにアクセスできる人なら誰でもその秘密を見ることが可能になるだろう。

テンプレート可能な秘密のフィールドは、通知をルーティングするための利用を意図している。 テンプレートファイルの機能を利用して秘密を設定ファイルから分離するための方法としては意図されていない。 例えば、大規模な設定の中では、各チームが自分で完全に制御できるAlertmanager設定ファイルの断片を持っていて、それらが結合されて、最終的な完全な設定ファイルになる。

Pushgateway

PushgatewayのHTTPエンドポイントにアクセスできる人なら誰でも、その中に含まれるメトリクスを作成、変更、削除することができる。 Pushgatewayは通常はhonor_labelsを有効にしてスクレイプされるので、これは、Pushgatewayにアクセスできる人は誰でもPrometheusの中に時系列を作成できるということを意味する。

Exporters

exporterは、一般的には、設定されたインスタンスとだけ、事前に決められたコマンド/リクエストの集合を用いて、通信する。

SNMPBlackbox exporterのような、URLパラメーターから監視対象を得るexporterも存在する。 したがって、それらのexporterにHTTPでアクセスできる人なら誰でも、任意のエンドポイントにリクエストを送信することが可能である。 それらはクライアント側での認証をサポートしているので、HTTP Basic認証のパスワードやSNMP community stringsのような秘密の漏洩に繋がる可能性がある。 TLSのようなチャレンジ/レスポンス認証はこの影響を受けない。

クライアントライブラリ

クライアントライブラリは、ユーザーアプリケーションに含まれることが意図されている。

クライアントライブラリが提供するHTTPハンドラを利用しているなら、そのハンドラに到達する悪意のあるリクエストが、付加的な負荷やスクレイプの失敗の問題以外の問題を起こすことが可能であってはならない。

認証、認可、暗号化

Prometheusおよびそのコンポートは、サーバーサイドで認証も認可も暗号化も提供しない。 もしそれらが必要なら、リバースプロキシの利用が推奨されている。

管理用エンドポイントと変更が起きるエンドポイントは、cURLなどの簡単なツールを通してアクセスされることを意図している。 そのような使い方ができなくなってしまうので、CSRFの防御は組み込まれていない。 したがって、リバースプロキシを使う際には、CSRFを防ぐためにそういうパスをブロックした方が良いだろう

変更が起きないエンドポイントに対しては、XSSを防ぐために、Access-Control-Allow-OriginのようなCORSヘッダをリバースプロキシに設定した方が良いだろう。

任意のPromQLを実行できるようにするつもりがない信頼性のないユーザーからの入力(コンソールテンプレートや自分で作った何かへのURLパラメーター)を含むPromQLクエリを構築している場合、信頼性のない入力の全てが適切にエスケープされるようにし、インジェクション攻撃を防ぐこと。 例えば、up{job="<user_input>"}は、仮に<user_input>"} or some_metric{zzz="だったとすると、up{job=""} or some_metric{zzz=""}るだろう。

Grafanaを使っている人は、ダッシュボードのパーミッションはデータソースのパーミッションではない、したがって、プロキシモードでユーザーが任意のクエリを実行する力を制限しないこと。

Prometheusの各種コンポーネントはクライアント側の認証と暗号化をサポートしている。 TLSクライアントが提供されている場合、SSLの検証をスキップするinsecure_skip_verifyという設定項目もしばしばある。

Secrets

秘密でない情報やフィールドは、HTTP APIやログを通じて利用可能な可能性がある。

Prometheusでは、サービスディスカバリーで取得したメタデータは秘密とは考えられていない。 Prometheusシステム全体で、メトリクスは秘密とは考えられていない。

設定ファイルの秘密を含むフィールド(ドキュメントにそう明示されている)は、ログやHTTP APIを通じて出力されることはない。 コンポーネントが自分の設定をHTTPエンドポイントから出力するのは普通なので、秘密を他の設定フィールドに入れてはいけない。

依存しているものに利用される他の情報源から来る秘密(例えば、EC2サービスディスカバリーで利用されるAWS_SECRET_KEY環境変数)は、Prometheusで制御できる範囲外のコードのせい、または、それが保存されている場所に出力する機能のせいで結局出力されてしまうかもしれない。

Denial of Service

過剰な負荷や高価なクエリに対する緩和策がいくつか講じられている。しかし、多すぎる、または効果すぎるクエリー/メトリクスが与えられると、コンポーネントは落ちてしまうだろう。 悪意のある行動よりも信頼しているユーザーによってたまたま破壊される可能性の方が高い。

CPU、RAM、ディスク容量、IOPS、ファイル記述子、通信帯域を含む十分なリソースをコンポーネントに与えるのはユーザーの責任である。

全てのコンポーネントのエラーを監視し、エラー時に自動的に再起動させることが推奨される。

ライブラリ

このドキュメントは、ソースコードからビルドされた普通のバイナリについて考える。 自分でソースコードを修正した場合や、Prometheusの内部を自分のコードから(公式クライアントライブラリAPIを超えて)利用した場合には、ここで記されている情報は適用できない。

ビルドプロセス

Prometheusのビルドパイプラインは、サードパーティーのプロバイダー上で動き、Prometheus開発チームのメンバーとそのプロバイダーのスタッフがパイプラインにアクセスできる。 もし、自分のバイナリの正確な出どころについて心配なら、プロジェクトによって事前にビルドされたバイナリに頼るのではなく、自分自身でビルドすることを推奨する。

外部監査

CNCFは、2018年4月から2018年6月に渡るcure53による外部のセキュリティ監査を支援した。

さらなる詳細は、監査の最終報告書を呼んで下さい。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

exporterの書き方 - Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

exporterの書き方

自分自身のコードにメトリクスを組み込む場合は、Prometheusクライアントライブラリを用いたコードへのメトリクスの組み込み方の一般的なルールに従うべきである。 他の監視システムやメトリクス組み込みシステムからメトリクスを取得する場合は、物事はそこまで白黒はっきりしていない。

このドキュメントには、exporterや独自コレクターを書く時に考慮すべきことが含まれている。 ここで述べられている理論は、直接メトリクス組み込みをする人にも興味深いだろう。

もしexporterを書いていて、このページのどこかが分からなければ、IRC(#prometheus on Freenode)またはメーリングリストで私たちに連絡して下さい。

Maintainability and purity

exporterを書く際に決めなければいけない主なことは、完璧なメトリクスを得るためにどれぐらいの労力をかけるつもりがあるのかということである。

もし目的のシステムにほぼ変化しないいくつかのメトリクスしかないなら、全てを完璧にするのは簡単な選択である。 この良い例が、HAProxy exporterである。

他方、新バージョンで頻繁に変わる数百ものメトリクスがあるシステムで完璧にしようとすれば、長期間にわたる多くの仕事の契約をした事になる。MySQL exporterがこちら側の極端な例である。

node exporterは、これらの中間にあり、モジュールごとに様々な複雑さがある。 例えば、mdadmコレクターは、ファイルを独自解析して、特にこのコレクターのために生成されたメトリクスを出力する。 meminfoコレクターに対してはカーネルバージョンによって結果が異なるので、正しいメトリクスを生成するために十分な変換をすることになる。

Configuration

もし粒度が細か過ぎるなら、特定のメトリクスを外せるようにしてあげなければいけないかもしれない。 例えば、HAProxy exporterは、サーバー毎の統計をフィルタリングできるようになっている。 同様に、デフォルトでは無効になっている高価なメトリクスがあるかもしれない。

他の監視システム、フレームワークプロトコルに取り組んでいるなら、 Prometheusに適したメトリクスを生成するように、追加設定やカスタマイズを提供しなければならないだろう。 一番良いケースでは、メトリクスの変換方法が自動的に決まるぐらいPrometheusに十分に似ているデータモデルを持っている。 CloudwatchSNMPcollectdがこのケースに当てはまる。 この場合、せいぜい、ユーザーがどのメトリクスを取り出したいかを選択できる機能が必要である。

他のケースでは、そのシステムから来るメトリクスは完全に非標準的であり、システムの使い方やその下にあるアプリケーションに依存している。 そのようなケースでは、メトリクスをどのように変換するかをユーザーが指定する必要がある。 この一番厄介な例がJMX exporterである。 Graphite exporterStatsD exporterもラベルを抽出するために設定が必要である。

設定なしにexporterが動くことの保証すること、および変換に設定が必要ならそのサンプル集を提供することが推奨される。

YAMLが標準のPrometheus設定フォーマットであり、全ての設定はデフォルトでYAMLを利用するべきである。

メトリクス

命名

メトリック名とラベル名ベストプラクティスに従うこと。

一般的に、メトリック名は、Prometheusには慣れていても特定のシステムには慣れていない人がメトリックの意味を推測しやすくなっているべきである。 http_requests_totalという名前は、物凄く有益という訳ではない(リクエストが入ってきた時に測られるのだろうか?何らかのフィルターの中で測られるのだろうか?それともユーザーのコードに到達した時に測れるのだろうか?)。 requests_totalはさらに悪い。どんな種類のリクエストなのか分からない。

メトリクスを直接組み込む場合、あるメトリックは一つのファイルだけに存在しているべきである。 したがって、exporterやcollectorの中でメトリックは一つのサブシステムにだけ適用されるべきであり、それに従って命名されるべきである。

メトリック名は、カスタムcollectorやexporterを書いている時以外は、決して機械的に生成してはいけない。

アプリケーションのためのメトリック名は、一般的に、exporter名で始めるべきである(例 haproxy_up)。

メトリクスには基本単位(秒やバイト)を使うべきである。単位を読みやすく変換するのはグラフ化ツールに任せるべきである。 どんな単位を使うことになったとしても、メトリック名に付ける単位は、使われている単位に合わせなければならない。 同様に、パーセントではなく比率を使うべきである。 その比率を成す二項目のカウンターを指定するとさらに良い。

メトリック名は、一緒に出力されるラベルを含んではいけない。例えば、by_typeは、集約されてラベルが亡くなった時に意味をなさなくなってしまう。

一つの例外は、同じデータを異なるラベルで複数のメトリクスを通して出力する時である。 こういう場合は、普通、メトリック名にラベルを含めるのがそれらのデータを区別するための最も合理的な方法となる。 これは、直接のメトリクス組み込みでは、一つのメトリックを全てのラベル値と共に出力すると個数が多くなり過ぎる場合にだけ起こるはずである。

Prometheusのメトリクスとラベル名は、snake_caseで書かれる。 camelCaseからsnake_caseに変換することが望ましいが、myTCPExampleisNaNのようなものに対しては自動で変換すると必ずしも良い結果にはならないので、そのままにしておくのが良い場合もある。

exposeされるメトリクスはコロンを含んではならない。コロンは、集約する時にユーザー定義のレコーディングルールが使うために予約されている。

メトリック名では[a-zA-Z0-9:_]のみが正しく、他のあらゆる文字はアンダースコアに書き換えるなければならない。

_sum_count_bucket_totalといったサフィックスは、サマリーやヒストグラム、カウンターで利用される。 これらを生成するのでなければ、これらのサフィックスは避けること。

カウンターには_totalを付けるのが慣例である。COUNTERタイプを使う場合は、それに従うべきである。

プリフィックスprocess_scrape_は、予約されている。 独自のプリフィックスをこれらに追加することは、matching semanticsに従っているのなら、問題ない。 例えば、Prometheusには、スクレイプにどれぐらいかかったかを表すscrape_duration_secondsがある。 また、特定のexporterがその処理にどれぐらいかかったかを表すexporterに関連するメトリック(例えば、jmx_scrape_duration_seconds)もあるのが良いプラクティスである。 PIDへのアクセスが出来るプロセスの統計情報に関して、GoとPythonはこの処理をするコレクターを提供している。 HAProxy exporterがこの良い例である。

成功したリクエスト数と失敗したリクエスト数がある場合、これをexposeする最も良い方法は、一つのメトリックをリクエスト総数に、もう一つのメトリックを失敗したリクエスト数にすることである。 これによってエラー率を計算するのが簡単になる。 failedやsuccessというラベルを持つ一つのメトリックを使わないこと。 同様に、キャッシュのヒットとミスに関しては、一つを総数、もう一つをヒット数にするのが良い。

監視を利用している人がメトリック名でコード検索やWeb検索をする可能性を考慮すること。 名前がうまく決められていてその名前に慣れている人達の範囲を超えて使われる可能性が低ければ(例えばSNMPとネットワークエンジニア)、 名前をそのままにしておくのが良いだろう。 この理論は全てのexporterに当てはまるわけではない。例えば、MySQL exporterのメトリクスは、DB管理者だけでなく色々な人に使われるだろう。 HELP文字列に元々の名前を含めておくと、元々の名前を使うのと同程度の恩恵が得られるだろう。

ラベル

ラベルに関する一般的なアドバイスを読むこと。

ラベル名としてtypeは避けること。 一般的過ぎるし、多くの場合意味がない。 regionzoneclusteravailability_zoneazdatacenterdcownercustomerstageserviceenvironmentenvのような、監視対象を表すラベルと衝突する可能性の高いラベルも、可能であれば避けること。 ただし、アプリケーションがリソースをそのような名前で呼んでいるなら、名前変更による混乱を起こさないようにするのが良い。

プリフィックスが共通しているというだけの理由で1つのメトリックにものを詰め込みたくなる誘惑に耐えること。 1つのメトリックとして意味を成すという確信がなければ、複数のメトリクスにしておく方が安全である。

leというラベルはヒストグラムにとって特別な意味があり、quantileというラベルはサマリーにとって特別な意味がある。 一般的にこれらのラベルは避けること。

Read/writeやsend/receiveは、ラベルではなく、別々のメトリクスにするのが良い。 これらは、一度に考慮するのは普通どちらか1つについてであって、そのように使った方が簡単である。

大まかなルールとして、1つのメトリックは、合計や平均した場合に意味を成すべきである。 そうすべきでない場合が1つあり、それはexporterに関連がある。 それはデータが本質的に表であるような場合であり、そうしなければユーザーがメトリクス名を正規表現で扱わなければならなくなるだろう。 マザーボード上の複数の電圧センサーについて考えてみると、それらの合計や平均は意味がない一方で、センサーごとのメトリックを持つのではなく、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の集約を利用すること。

自分の監視ツールがこのような出力をする場合、totalを落とすこと。 もし、何らかの理由(例えば、個別のカウントに入ってないものがtotalに入っている)で、それを持ち続けなければならないなら、違うメトリック名を使うこと。

メトリクスを組み込む際のラベルは、最小限にするべきである。 ユーザーがクエリを書く際に、余分なラベルそれぞれが、考慮しなければいけないことを1つずつ増やすことになる。 したがって、メトリクスを組み込む際に、消しても時系列の単一性を損なうことがないラベルは避けること。 メトリックの補足情報はinfoメトリックを介して追加することができる。 例えば、バージョン番号をどのように扱うかについて下記を参照すること。

ただし、あるメトリックの実質的に全てのユーザーが補足情報を求めていると予想される場合がある。 そのような場合、infoメトリックではなく、非ユニークなラベルを追加するのが正解である。 例えば、mysqld_exportermysqld_perf_schema_events_statements_totalのラベルdigestは、完全なクエリパターンのハッシュであり、ユニークさとして十分である。 しかし、人間が可読なラベルdigest_textがなければほとんど役に立たない。 これは、長いクエリに対してはクエリパターンの最初だけしか含んでおらず、ユニークではない。 したがって、結局は、人間のためのdigest_textと単一性のためのdigestの両方を持つことになる。

Target labels, not static scraped labels

もし、万が一、全てのメトリクスに同じラベルを適用したくなっても、やめること。

一般的に、これが起きる場合が2つある。

1つ目は、ソフトウェアのバージョン番号のような、メトリクスに付けておくと便利なラベルである。 この場合、代わりに、マシンロールのためのラベルはどう持つべきか?で説明されている方法を使うこと。

2つ目は、ラベルが実はターゲットラベルである時である。 リージョン、クラスタ名などのようなものは、アプリケーションではなく、インフラの構成から来るものである。 ラベルの分類としてどの場所に当てはまるかを決めるのは、アプリケーションではない。 それは、Prometheusサーバーを実行する人が設定することであり、同じアプリケーションを監視していても、人が異なれば、異なる名前を付けて良い。

したがって、これらのラベルは、利用している任意のサービスディスカバリーを経由して、Prometheusのスクレイプの設定に含まれる。 少なくとも一部のスクレイプする人にとっては、マシンロールは有益な情報である可能性が高いので、この考え方をマシンロールにも適用して良い。

自分のメトリクスをPrometheusの型に合うようにする必要がある。 これは、通常、カウンターとゲージである。 サマリーの_count_sumも比較的よくあり、たまに分位数も見かけるだろう。 ヒストグラムは稀であり、もしヒストグラムに出会ったら、出力形式が累積的な値を出力していることを思い出そう。

メトリクスの集合を自動的に処理するような場合は特に、メトリクスの型が明らかでないことがよくある。 一般的に、UNTYPEDが安全なデフォルトである。

カウンターは減少できないので、他のメトリクスを組み込むシステムから来るカウンター型がデクリメントされ得るのであれば、それはカウンターにしてはいけない。それはゲージである。 GAUGEがカウンターとして利用されるのは誤解を招くであろうから、ここで使うにはUNTYPEDがおそらく最善の型である。

ヘルプ文字列

メトリクスを変換している場合、元が何だったか、どんなルールがその変換で動いたのかをユーザーが辿れるようになっていると便利である。 collectorやexporterの名前と適用されたルールのID、元のメトリックの名前をヘルプ文字列に入れておくと、かなりユーザーが助かるだろう。

Prometheusでは、1つのメトリックに異なる複数のヘルプ文字列があるのは好ましくない。 多くのメトリクスから1つのメトリクスを作る場合、そのうちの1つを選んでヘルプ文字列の入れること。

この例として、SNMP exporterはOIDを利用し、JMX exporterはサンプルのmBean名を入れている。 HAProxy exporterには、手書きの文字列がある。 node exporterにも幅広い種類の例がある。

有益でない情報の削除

メトリクス組み込みの仕組みには、最小値、最大値、標準偏差に加えて、1m、5m、15mのレート、アプリケーションが起動してからの平均レート(Dropwizardでは、これらはmeanと呼ばれる)を出力するものがある。 。

これらは、有益ではないし、情報が散らかるので、全て削除されるべきである。 Prometheusは自分自身でレートを計算することができるし、普通は(出力された平均は指数関数的に陳腐化するので)より正確に計算できる。 minやmaxがどの時間にわたって計算されたのか分からないし、標準偏差は統計的に利用できないし、もし計算する必要があるならいつでも二乗和、_sum_countを出力することができる。

分位数にも同じような問題があり、それらを削除するのかサマリーに入れるのかを選ぶことができる。

ドット区切り文字列

多くの監視システムには、ラベルがない。 その代わり、my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3のようなことをする。

Graphite exporterStatsD exporterは、小さな設定言語を用いてそれらを変換する方法を共有している。 他のexporterも同じものを実装すべきである。 変換は、現在、Goでのみ実装されており、別のライブラリに切り出すことで恩恵が得られるだろう。

Collectors

自分のexporterのためにcollectorを実装している場合、通常の直接のメトリクス組み込みの手法を使って スクレイプのたびにメトリクスを更新するべきではない。

そうではなく、新しいメトリクスを毎回作成すること。 これは、Goでは、Update()メソッドの中でMustNewConstMetricを使うことでできる。 Pythonについてはhttps://github.com/prometheus/client_python#custom-collectorsを参照すること。 Javaについては、collectメソッドの中でList<MetricFamilySamples>を生成すること。 サンプルについては、StandardExports.java を参照すること。

こうすることには二重の理由がある。まず第一に、2つのスクレイプが同時に起こり得て、直接のメトリクス組み込みは実質的にファイルレベルでグローバルな変数を使っているので、レースコンディションが起きる。 第二に、ラベルの値が消滅しても出力はされ続ける。

exporter自体に直接メトリクスを組み込むのは問題がない。 転送されたバイト合計や全てのスクレイプにまたがってexporterによって実行された呼び出し回数は、その例である。 blackbox exporterSMNP exporterのような単一の監視対象に結びついていないexporterに対して、これらはいつも通りの/metricsの呼び出しでのみ出力するべきであり、特定の監視対象のスクレイプで出力すべきではない。

スクレイプ自体に関するメトリクス

スクレイプに関して、どれぐらい時間がかかったか、何個のレコードを処理したかのようなメトリクスを出力したい時がある。

これらは、1つのイベント(スクレイプ)に関するものなのでゲージとして出力すべきである。 その名前は、例えばjmx_scrape_duration_secondsのように、exporter名をプリフィックスにするべきである。 _exporterは、通常は除外され、exporterが単にcollectorとして使うのも理にかなっているなら絶対に除外される。

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

例えばElasticsearchのような多くのシステムは、CPUやメモリ、ファイルシステム情報のようなマシンのメトリクスも出力する。 これらは、Prometheusエコシステムの中で、node exporterが提供しているので、削除されるべきである。

Javaの世界では、多くのメトリクス組み込みフレームワークが、CPUやGCのようなプロセスレベルとJVMレベルの情報を出力する。 JavaクライアントとJMX exporterは既に、DefaultExports.javaを通して好ましい形でこれらを含んでいるので、これらも削除するべきである。

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

デプロイ

各exporterは、出来れば同じマシン上でちょうど1つのインスタンスのアプリケーションを監視すべきである。 つまり、運用しているHAProxyそれぞれに対してhaproxy_exporterプロセスを実行することになる。 Mesosワーカーがいる全てのマシンでMesos exporterを実行し、もしあるマシンにマスターもいるならマスターのためにさらに1つ実行する。

これはメトリクスを直接埋め込む場合にしていることであり、他の構成でも出来るだけそれに近くしようというのが、こうする理屈である。 つまり、全てのサービスディスカバリーは、exporterではなく、Prometheusの中で行われるということである。 これには、ユーザーがblackbox exporterで自分のサービスを検査できるような監視対象の情報をPrometheusが持つという恩恵もある。

これには2つの例外がある。

1つ目は、アプリケーションの横で監視を動かすのが根本的に意味がない場合である。 SNMPblackbox、IPMIのexporterがこの主な例である。 デバイスはその上でコードを実行できないブラックボックであることが多いので、IPMIやSNMPはこの例に当たる(ただし、node exporterをそのデバイスで動かせるなら、そうした方が良い)。 コードを実行する場所がないDNS名のようなものの監視をするのでblackbox exporterもこの例に当たる。 こういった場合でも、Prometheusがサービスディスカバリーをしてスクレイプする対象を渡すべきである。 サンプルは、blackbox exporterとSNMPを参照すること。

この種のexporterを書くことができるのは、現在、GoやPythonJavaのクライアントライブだけであることに注意。

2つ目の例外は、システムのランダムなインスタンスから情報を取得しており、どのインスタンスと通信していても構わない場合である。 MySQLレプリカの集合があり、そのデータに対してビジネスクエリを実行して出力したいと考えてみよう。 1つのレプリカと通信するためにいつも使っているロードバランスの方法を使うexporterにするのが最も合理的な方法である。

これは、マスター選出のあるシステムでは適用できない。 その場合、それぞれのインスタンスを個別に監視し、Prometheusで「マスターであること」を判断すべきである。 これは、必ずしもちょうど1つのマスターがある訳ではないからであり、Prometheusの足元でターゲットが何であるかを変更するとおかしなことが起きる。

スケジュール

メトリクスは、Prometheusがスクレイプした時にだけアプリケーションからpullされるべきである。 exporterは、自分のタイマーに基づいてスクレイプをするべきではない。 つまり、全てのスクレイプは同期的であるべきである。

したがって、出力するメトリクスにタイムスタンプをセットすべきではない。Prometheusがその面倒を見るべきである。 もし、タイムスタンプが必要と考えているなら、おそらく、代わりにPushgatewayが必要である。

もし、あるメトリクスの取得のコストが特に高い(例えば1分以上かかる)のであれば、それをキャッシュすることが容認される。 このことは、HELPで記載されるべきである。

Prometheusがスクレイプをタイムアウトさせるのは、デフォルトで10秒である。 自分のexporterがこれを超えそうなら、ユーザードキュメントでこのことを呼びかけるべきである。

プッシュ

アプリケーションや監視システムの中には、例えばStatsD、Graphite、collectdのように、メトリクスをプッシュするだけのものもある。

ここで考慮すべきことが2つある。

第一に、メトリクスをいつまで有効とするか? collectdおよびGraphiteと通信するものはどちらも定期的に出力をする。 それらが止まった時は、そのメトリクスの出力を止めたい。 collectdには有効期限が含まれているので、それを使う。 Graphiteはそうではないので、exporterのフラグになっている。

StatsDは、メトリクスではなくイベントを扱うものなので、少し違っている。 それぞれのアプリケーションの横で1つのexporterを起動し、アプリケーションが再起動するときに状態が綺麗になるようにexporterを再起動するのが最善の方法である。

第二に、この種のシステムでは、ユーザーが差分でも生のカウンターでも送信できる傾向にある。 生のカウンターがPrometheusの一般的なモデルなので、できる限り生のカウンターを使うべきである。

サービスレベルのメトリクス(例えばサービスレベルのバッチジョブ)では、状態を自分で管理するのではなく、exporterにPushgatewayへプッシュをさせ、イベントの後は終了させるべきである。 インスタンスレベルのバッチメトリクスでは、明らかなパターンはまだない。 選択肢としては、node exporterのtextfile collectorを乱用するか、メモリ内の状態に頼る(リブート後に状態を保持する必要がなければおそらく最善)か、textfile collectorに似た機能を実装するかのいずれかである。

失敗したスクレイプ

通信しているアプリケーションが反応しなかったり他の問題がある場合のスクレイプの失敗するときのために、現状で2つのパターンがある。

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

2つ目は、スクレイプが機能しているかどうかをもとに0または1の値になる変数myexporter_up(例えばhaproxy_up)を持つことである。

スクレイプが失敗してもまだ有益なメトリクスが取得できる場合には、後者の方が良い。 例えば、HAProxy exporterはプロセスの情報を提供する。 後者は、upが通常の動作をするので多少使いやすい。 ただし、exporterがダウンしているのかアプリケーションがダウンしているか区別することはできない。

ランディングページ

http://yourexporter/を見ると簡単なHTMLでexporterの名前と/metricsページへのリンクがあるとユーザーにとって親切である。

ポート番号

ユーザーは、同じマシンにたくさんのexporterとPrometheusコンポーネントを持っているかもしれない。 したがって、それぞれにユニークなポート番号を持たせるのが簡単であるようにすること。

https://github.com/prometheus/prometheus/wiki/Default-port-allocationsがポート番号が記録されるところになっていて、誰でも編集できる。

exporterを開発しているときは、次のポート番号を、できれば公衆にアナウンスする前に、とって構わない。 まだリリースの準備ができていないなら、ユーザー名とWIPを書くので構わない。

これはPrometheusユーザーの暮らしを少し良くするための登録であり、特定のexporterを開発するという公約ではない。 内部的なアプリケーションのためのexporterには、デフォルトのポート割り当て範囲外のポートを利用することを推奨する。

アナウンス

自分のexporterを世界にアナウンスする準備が出来たなら、メーリングリストにメールして、利用可能なexporter一覧に追加するためにPRを送ること。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

Prometheus: Up & Running: Infrastructure and Application Performance Monitoring

Prometheus: Up & Running: Infrastructure and Application Performance Monitoring

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

マシンロールのためのラベルはどう持つべきか? - Prometheusドキュメント

  • Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

マシンロールのためのラベルはどう持つべきか?

ターゲットラベルがターゲットの生涯に渡って不変にすることが、Prometheusを使うときのベストプラクティスである。 他方で、Apacheサーバーであるマシンにまたがってメトリクスを集約すると便利である。 これをどうやってやるか?

Prometheusで鍵となる概念は、時系列の連続性が求められるということ、つまり、時系列のラベルが変わって別の時系列になってしまわないということである。 Chefのロールのようにマシンのロールが変わると変わってしまうものは、良いターゲットラベルではない。 ロールが変わるたびに、グラフが途切れてしまい、アラートはリセットされてしまう。

だが、これは、あるロールを持つマシンにまたがってCPU利用率のようなメトリクスを集約できないということを意味する。 幸いなことに、これは、textfile collectorとグループ修飾子で出来る。

一から始めることにして、node exporterをダウンロードし、textfile collectorと共に実行する。

wget https://github.com/prometheus/node_exporter/releases/download/v0.15.1/node_exporter-0.15.1.linux-amd64.tar.gz
tar -xzf node_exporter-0.15.1.linux-amd64.tar.gz
cd node_exporter-*
mkdir textfile_collector
./node_exporter -collector.textfile.directory textfile_collector &

このマシンでpostfixapacheを実行しているとする。 以下のように、それらのロールのメトリクスを追加する。

cat <<EOF > textfile_collector/roles.prom
machine_role{role="postfix"} 1
machine_role{role="apache"} 1
EOF

これは、普通、設定管理システムで行われるだろう。 http://localhost:9100/metrics を見てみると、新しいメトリクスが見つかるだろう。

次に、これをスクレイプするためのPrometheusサーバーを手早く準備する。

wget https://github.com/prometheus/prometheus/releases/download/v2.0.0/prometheus-2.0.0.linux-amd64.tar.gz
tar -xzf prometheus-2.0.0.linux-amd64.tar.gz
cd prometheus-*
cat <<'EOF' > prometheus.yml
global:
  scrape_interval: 10s
  evaluation_interval: 10s
scrape_configs:
  - job_name: 'node'
    static_configs:
      - targets:
        - localhost:9100
EOF
./prometheus

もし、roleラベルが付与されたapacheロールのメトリクスが必要な場合、expressionブラウザでgroup_leftを使うことができる。

up * on (instance, job) group_left(role) machine_role{role="apache"}

こうすると普段通り集約できる。

sum by (job, role)(
    up * on (instance, job) group_left(role) machine_role{role="apache"}
)

この手法で、ラベルをターゲットの生涯に渡って変更したときのような欠点を排して、ターゲットにラベルを付与する恩恵が得られる。 アプリケーションに対しても似たような方法で上手くいく。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

メトリクス組み込みベストプラクティス - Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

このページは、あなたのコードをinstrumentするためのガイドライン集を提供する。

メトリクスの組み込み方

端的に言うと、全てにメトリクスを組み込めということになる。 全てのライブラリ、サブシステム、サービスは少なくとも、それがどのように振舞っているのか大まかに分かるようないくつかのメトリクスを持つべきである。

メトリクス組み込みは、あなたのコードと一体の部品となるべきである。 メトリックのクラスは、それを利用する同じファイル中でインスタンス化すること。 こうすることで、エラーを追跡する時に、アラートからコンソール、コードへと辿っていくのが簡単になる。

サービスの3タイプ

監視の目的に対して、サービスは一般的に3種類(online-serving、offline-processing、バッチジョブ)に分類できる。これらには重なりもあるが、サービスはこれらの分類の一つによく当てはまる傾向にある。

online-servingシステム

online-servingのシステムとは、人間または他のシステムが即座のレスポンスを期待しているシステムのことである。 たとえば、ほとんどのデータベースとHTTPリクエストはこの分類に当てはまる。

こういったシステムで鍵となるメトリクスは、実行されたクエリの数、エラーの数、レイテンシーである。 処理中のリクエストの数も有益かもしれない。

失敗したクエリの数え方については、下記の「Failures」の部分を参照のこと。

online-servingのシステムは、クライアント側サーバー側の両方で監視されているべきである。 もし、両側の振る舞いに違いがあれば、デバッグのための有益な情報となる。 一つのサービスに対してたくさんのクライアントがあるなら、サービス側でそれらのクライアントを個別に追跡するのは現実的ではないので、 クライアント側の統計に頼らざるを得ない。

クエリの開始時にカウントするのか、終了時にカウントするのかを統一すること。 エラーやレイテンシーの統計も揃うので、クエリ終了時にカウントすることが提案されている。また、そうすると実装も簡単になる傾向がある。

オフライン処理

オフライン処理に対しては、レスポンスを能動的に待っている人はおらず、一括化するのが普通である。 また、処理に複数の段階があることもある。

各段階に対して、入力された項目、処理中の数、処理の最終時刻、出力した項目を追跡すること。 バッチにしている場合は、処理中のバッチおよび終了したバッチも追跡するべきである。

処理の最終時刻を知ることは、処理の進行が遅れているかどうかを検出するために役立つが、非常に局所的な情報である。 より良い方法は、システムを通じたheartbeat(システム全体を通過し、入力された時のタイムスタンプを含むダミーデータ)を送信することである。 各段階では、システム中を伝播するのにどれぐらいかかっているかが分かるように、直近のheartbeatのタイムスタンプを出力することが可能になる。 何も処理されていない時間があるシステムでは、明示的なheartbeatは不要かもしれない。

バッチジョブ

オフライン処理はおそらくバッチジョブで行われるので、オフライン処理とバッチジョブの境界線は曖昧である。 バッチジョブには非連続的に動いているという特徴があり、それがscrapeすることを難しくしている。

バッチジョブの鍵となるメトリックは、最後に成功した時刻である。 その他に有益なのは、主な段階それぞれにかかった時間、全体の実行時間、成功・失敗いずれにしても完了した最終時刻である。 これらは全てゲージであり、PushGatewayにプッシュされるべきである。 一般的に、ジョブ特有の全体的な統計情報(例えば、処理されたレコード数合計など)も追跡すると便利である。

実行に数分以上かかるバッチジョブに対しては、pullベースの監視によるscrapeをすることも有益である。 これによって、他の種類のジョブと同じメトリクス(リソース使用量や他のシステムとの通信のレイテンシー)を追跡できるようになる。 これは、ジョブが遅くなり始めたらデバッグの助けとなる。

特に頻繁に(例えば、15分毎よりも頻繁に)実行されるバッチジョブに関しては、それをデーモンに変更し、offline-processingジョブとして扱うことを検討すべきである。

サブシステム

サービスの主な3タイプに加えて、システムは監視されるべき構成要素がある。

ライブラリ

ライブラリは、ユーザーによる追加の設定を必要とすることのないメトリクスの組み込み方を提供すべきである。

もし、あるライブラリがプロセス外のリソース(例えば、ネットワークやディスク、IPC)にアクセスするために使われるなら、全クエリ数、(起きるなら)エラー、レイテンシーは少なくとも追跡すること。

ライブラリがどれぐらい重いかによって、内部エラー、ライブラリ自体の中のレイテンシー、その他思い付く一般的な指標を追跡するのが有益だろう。

ライブラリは、一つのアプリケーションでも複数の独立した部分によって異なるリソースに対して利用される可能性がある。 従って、適宜利用方法をラベルで区別するように注意すること。 例えば、DBコネクションプールは接続先のDBを区別する必要がある一方で、DNSクライアントライブラリのユーザーを区別する必要はない。

Logging

一般的なルールとして、ロギングのコード各行に対してインクリメントされるカウンターが存在するべきである。 もしあなたの興味を引くログメッセージがあれば、どれぐらいの頻度、どれぐらいの長さでそれが起き続けているのか見れるようにしたいだろう。

同じ関数の中に複数の緊密なログメッセージがある場合(例えば、ifやswitch文の異なる分岐)、一つのカウンターをそれら全てでインクリメントするのが理に適っていることがある。

アプリケーション全体でログされたinfo/error/warningの合計数を出力し、リリースプロセスの一部として大きな差があったか確認することも一般的に有益である。

Failures

失敗は、ロギングと同様に処理されるべきである。失敗が起きるたびに、カウンターをインクリメントするべきである。 ロギングと違って、エラーは、コードの構造によっては、より一般的なエラーのカウンターになることもある。

失敗のレポートをする時には、一般的に、総試行回数を表す何か他のメトリックを持つべきである。 これによって失敗率の計算が簡単に成る。

スレッドプール

どんなスレッドプールに対しても、鍵となるメトリクスは、キューされたリクエスト数、利用されているスレッド数、スレッドの総数、処理済みのタスク数および処理にかかった時間である。 キューの中の待ち時間を追跡するのも有益である。

キャッシュ

キャッシュの鍵となるメトリクスは、クエリ総数、ヒット数、全体のレイテンシー、そしてキャッシュの元となっているonline-servingシステムのクエリ数、エラー数、レイテンシーである。

コレクター

瑣末でないメトリクスのコレクターを実装する際は、処理にどれぐらい時間がかかったかを秒で表すゲージおよび起きたエラーの数を表すゲージを出力することを推奨する。

これは、時間を、サマリーやヒストグラムではなく、ゲージとして出力しても良い2つのケースの1つである。 もう一つのケースは、バッチジョブの時間である。 どちらも、複数の時間ではなく、特定のpush/scrapeについての情報を表しているからである。

注意点

監視をする際には、一般的な注意すべき点があり、Prometheusに固有な特に注意すべき点もある。

ラベルを使う

ラベルという概念およびそれを活用するための言語を持つ監視システムは少ないので、慣れるのに少し時間がかかる。

add/average/sumをしたい複数のメトリクスがある場合、それらを、複数のメトリクスではなく、複数のラベル値を持つ1つのメトリックにするべきである。

例えば、http_responses_500_totalhttp_responses_403_totalではなく、HTTPレスポンスコードのためのラベルcodeを持つhttp_responses_totalという1つのメトリックを作成する。 これで、ルールやグラフ内で、1つのメトリックとして全体を処理することができる。

大まかなルールとしては、メトリック名のどの部分も手続き的に生成されるべきではない(代わりにラベルを使う)。 例外は、他の監視システム/メトリクス取得システムからメトリクスをプロキシする場合である。

メトリック名とラベル名ベストプラクティスも参照すること。

ラベルを使い過ぎない

ラベル集合はそれぞれ、RAM、CPU、ディスク、ネットワークのコストがかかる追加の時系列である。 普通はそのオーバーヘッドは無視できるが、たくさんのメトリクス、たくさんのラベル集合を何百ものサーバーから取得するような場合はすぐに積み上がってしまうだろう。

一般的なガイドラインとして、メトリクスのラベルの種類を10未満に収めるようにし、それを超えるメトリクスはシステム全体で一握りに収めること。メトリクスの大多数はラベルがないようにするべきである。

100種類以上のラベルを持つ(あるいはそれぐらい増えそうな)メトリックがあったら、代わりの解決策(例えば、分析を監視から切り離し汎用処理システムに移す)を調査すること。

背後にある数字を理解するために、node_exporterを見てみよう。 node_exporterは、マウントされたファイルシステムそれぞれのメトリクスを出力する。 各ノードには、例えばnode_filesystem_availのために、数十の時系列がある。 もし、10,000ノードあるとすると、結局、node_filesystem_availが約100,000時系列あることになるが、Prometheusは問題なく処理できる。

ここで、仮に、ユーザーごとのquotaを追加しようとすると、1万ノードに1万ユーザーで1億にすぐに達してしまう。 これは、Prometheusの現在の実装に対して多過ぎる。 これよりは少ない数の場合でも、もっと有益な可能性がある他のメトリクスをこのマシンでそれ以上持てなくなるという機会損失がある。

確信がない場合は、ラベルなしから始めて、時間とともに、具体的なユースケースが出てきたら、ラベルを追加していくこと。

カウンターvsゲージ、サマリーvsヒストグラム

あるメトリックに対して4つの型のどれを使うべきか知っておくことは重要である。

カウンターかゲージを選ぶための大まかなルールとして、値が減少するならそれはゲージである。

カウンターは、増加(およびプロセス再起動時などのリセット)しかしない。 イベント数や各イベントの何かの量の集積に便利である。 例えば、HTTPリクエストの総数やHTTPリクエストの送信バイト総数である。 生のカウンターは滅多に役に立たない。 値が増加する秒間レートを得るために関数rate()を使う。

ゲージは、値のセット、増加、減少ができる。 処理中のリクエスト、空きメモリ/総メモリ量、温度など、状態のスナップショットに便利である。 決してゲージのrate()をとってはいけない。

サマリーとヒストグラムは、もっと複雑なメトリック型であり、別ページで議論されている。

経過時間ではなくタイムスタンプ

何かが起きてからの時間を追跡したい場合、(それが起きてからの経過時間ではなく)Unixタイムスタンプを出力すること。

タイムスタンプ出力されていれば、time() - my_timestamp_metricという式を使ってそのイベントからの経過時間を計算することができ、メトリックの更新ロジックが要らなくなる。

Inner loops

メトリクスを処理したり開発するリソースの追加コストは、一般的には、それがもたらす利益と比べれば微々たるものである。

パフォーマンスが重要なコード(言い換えると、あるプロセスで秒間100k回以上呼び出されるようなコード)に対して、どれぐらい多くのメトリクスを更新するかについて注意を払いたくなるだろう。

Javaのカウンターは、インクリメントするのに12-17nsかかる。 他の言語でも同様なパフォーマンスになるだろう。 もし、その時間の長さがループにおいて重大であるなら、ループでインクリメントするメトリクス数を制限し、ラベルを避ける(言い換えると、GoのWith()Javalabels()などのラベル検索の結果をキャッシュする)こと。

時間の取得はシステムコールを含むので、時刻や時間幅を含むメトリックの更新にも注意すること。 パフォーマンスが重要なコードに関する全ての問題と同様に、変更の影響を確認するには、ベンチマークが最良の方法である。

メトリクスの欠落の回避

何かが起きるまで現れない時系列は、通常の簡単な操作がそれらを適切に処理するのに不十分なので、処理するのがむずかしい。 これを防ぐために、存在しうる時系列に対してあらかじめ00が誤解を生むならNaN)を出力すること。

GO、JavaPythonを含むPrometheusクライアントライブラリのほとんどは、ラベルのないメトリクスに対して自動的に0を出力する。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

Prometheus: Up & Running: Infrastructure and Application Performance Monitoring

Prometheus: Up & Running: Infrastructure and Application Performance Monitoring

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

ストレージ - Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

STORAGE

Prometheusは、ローカルのディスク上に時系列データベースを持っているが、オプションでリモートのストレージシステムとも連携する。

ローカルストレージ

Prometheusのローカル時系列データーベースは、時系列データを独自フォーマットでディスク上に保存する。

ディクス上の構成

取り込まれた値は、2時間のブロックにまとめられる。 2時間のブロックそれぞれは、1つ以上のチャンクファイル、メタデータファイル、インデックスファイルを含むディレクトリから成る。 チャンクファイルは、その時間の時系列の全ての値とを含む。 インデックスファイルは、メトリック名とラベルからチャンクファイル内の時系列へのインデックスを含む。 時系列がAPIを通して削除されると、削除レコードが別のtombstoneファイルに保存される(データはチャンクファイルから即時に削除されない)。

現在取り込みつつある値は、メモリに保持され、完全に永続化はまだされていない。 クラッシュに対して安全にするためにwrite-ahead-log(WAL)が利用される。 write-ahead logは、Prometheusサーバーがクラッシュ後に再起動した際にやり直しが出来る。 Write-ahead logファイルはディレクトwalに128MBセグメントで保存される。 これらのファイルは、まだコンパクト化されていない生データを含むので、通常のブロックファイルよりかなり大きい。 Prometheusは、少なくとも3つのwrite-ahead logを保持するが、トラフィックの多いサーバーでは、3つより多いWALファイルが見れらる。 なぜなら、少なくとも2時間相当の生データを保持しなければならないからである。

Prometheusサーバーのデータディレクトリの構成は、以下のようなものになるだろう。

./data/01BKGV7JBM69T2G1BGBGM6KB12
./data/01BKGV7JBM69T2G1BGBGM6KB12/meta.json
./data/01BKGTZQ1SYQJTR4PB43C8PD98
./data/01BKGTZQ1SYQJTR4PB43C8PD98/meta.json
./data/01BKGTZQ1SYQJTR4PB43C8PD98/index
./data/01BKGTZQ1SYQJTR4PB43C8PD98/chunks
./data/01BKGTZQ1SYQJTR4PB43C8PD98/chunks/000001
./data/01BKGTZQ1SYQJTR4PB43C8PD98/tombstones
./data/01BKGTZQ1HHWHV8FBJXW1Y3W0K
./data/01BKGTZQ1HHWHV8FBJXW1Y3W0K/meta.json
./data/01BKGV7JC0RY8A6MACW02A2PJD
./data/01BKGV7JC0RY8A6MACW02A2PJD/meta.json
./data/01BKGV7JC0RY8A6MACW02A2PJD/index
./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks
./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks/000001
./data/01BKGV7JC0RY8A6MACW02A2PJD/tombstones
./data/wal/00000000
./data/wal/00000001
./data/wal/00000002

最初の2時間のブロックは、最終的にバックグラウンドで、より長いブロックへとコンパクト化される。

ローカルストレージの制限は、クラスタ化されたり複製されたりしないことである。 したがって、自由にスケールしないし、ディスクやノードの停止に耐えられず、短期の時間枠の最近のデータとして扱われるべきである。 ただし、耐久性の要件が厳格でなければ、ローカルストレージに何年ものデータを保存し続けることが出来ることもある。

ファイルフォーマットのさらなる詳細は、TSDB formatを参照すること。

運用に関わる側面

Prometheusには、ローカルストレージを設定するいくつかのフラグがある。 特に重要なものは、以下の通り。

  • --storage.tsdb.path: Prometheusがどこにデータベースを書き込むかを決める。デフォルトは、data/
  • --storage.tsdb.retention.time: 古いデータをいつ削除するかを決める。デフォルトは、15d。storage.tsdb.retentionがデフォルト以外にセットされていたら、上書きする
  • --storage.tsdb.retention.size: [EXPERIMENTAL] ストレージのブロックが利用できる最大バイト数を決める。WALはかなりのサイズに成るが、これには含まれていないことに注意。最も古いデータが最初に削除される。デフォルトは0、つまり無効化されている。このフラグは、実験的であり、将来のリリースで変更される可能性がある。サポートされている単位は、KB、MB、GB、PB. 例: "512MB"
  • --storage.tsdb.retention: このフラグは非推奨である。storage.tsdb.retention.timeを利用するのが望ましい。

平均として、Prometheusは、1つの観測値につき大体1〜2バイトしか使わない。 したがって、Prometheusサーバーのキャパシティを計画するには、以下の式を利用することができる。

needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample

訳注: 必要なディスクスペース = 保持期間(秒) * 秒間サンプル採取数 * サンプルのバイト数

秒間サンプル採取数を調整するために、取得する時系列の数を減らす(ターゲットを少なくしたりターゲットあたりの時系列を少なくする)か取得間隔を長くすることができる。 ただし、1つの時系列の中で観測値の圧縮があるので、時系列の数を減らす方が効果的である可能性が高い。

もし、ローカルストレージが壊れたら、理由が何であれ、Prometheusを停止してストレージディレクトリ全体を消すのが最善策である。 POSIX互換でないファイルシステムは、Prometheusのローカルストレージでサポートされていない。 復元の可能性のない破壊が起きる可能性がある。 NFSは、POSIXの可能性はあるが、ほとんどの実装はPOSIXではない。 問題を解決するために、個々のブロックディレクトリを削除することも可能だが、ブロックディレクトリ毎に約2時間相当のデータを失うことになる。 Prometheusのローカルストレージは、長期保存に耐えられるようには意図されていないのである。

時間と容量の両方の保持ポリシーが指定されている場合、先に引っかかった条件がその時点で使われる。

リモートストレージ連携

Prometheusのローカルストレージは、スケーラビリティと耐久性の点で、1つのノードに制限されている。 Prometheus自体でクラスタ化されたストレージに対処する代わりに、Prometheusにはリモートストレージシステムと連携するインターフェースがある。

概要

Prometheusは、以下の2つの方法でリモートストレージシステムと連携する。

  • Prometheusは、取り込んだ値を標準化されたフォーマットでリモートURLに書き込むことができる
  • Prometheusは、標準化されたフォーマットでリモートURLから値を読み取ることができる

Remote read and write architecture

読み書きのプロトコルはどちらもsnappyで圧縮されたプロトコルバッファをHTTP越しに利用する。 これらのプロトコルはまだ安定したAPIと見なされておらず、将来的には、Prometheusとリモートストレージの間の経路が全てHTTP/2をサポートしたと仮定できるようになれば、gRPC over HTTP/2に変わるかもしれない。

Prometheusのリモートストレージ連携の設定に関する詳細は、Prometheus設定ドキュメントのremote writeremote readの部分を参照すること。

リクエストとレスポンスのメッセージに関する詳細は、リモートストレージプロトコルバッファの定義を参照すること。

読み込む行程では、Prometheusはラベルセレクターの集合と時間幅に対する生の時系列データを取得するだけであることに注意すること。 生データに対するPromQLの全ての評価はPrometheus自体の中で起きる。 つまり、必要なデータは全てクエリを発行しているPrometheusサーバーにまず読み込まれてその後Prometheusサーバで処理される必要があるので、remote readのクエリにはスケーラビリティの制限がある。 しかし、完全に分散されたPromQLの評価をサポートすることはしばらくの間は不可能だと考えられている。

既存の連携

リモートストレージとの既存の連携についてさらに学ぶには、インテグレーションのドキュメントを参照すること。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

histogram_quantile() - Prometheusドキュメント

このページはPrometheus公式ドキュメント和訳+αの一部です。

histogram_quantile()

histogram_quantile(φ float, b instant-vector)は、ヒストグラムバケットbからφ分位数(0 ≤ φ ≤ 1)を計算する (φ分位数とヒストグラムの一般的な使い方の詳細な説明はヒストグラムとサマリーを参照すること)。bの値は、各バケットに入る観測値の数である。 各バケットには、バケットの上限を表す値を持つラベルleがなければならない(そういうラベルがない値は無視される)。 メトリック型ヒストグラムは、_bucketサフィックスに持つ時系列と適切なラベルを提供する。

分位数の計算のためのタイムウインドウを指定するために関数rate()を利用すること。

例として、ヒストグラムのメトリックがhttp_request_duration_secondsだとする。 過去10分間のリクエスト持続時間の90パーセンタイルを計算するために以下の式を用いる。

histogram_quantile(0.9, rate(http_request_duration_seconds_bucket[10m]))

分位数は、http_request_duration_secondsのラベルの組み合わせそれぞれに対して計算される。 集約するためには、sum()を使ってrate()の結果を囲む。 leラベルは、histogram_quantile()に必要なので、byの中に含めなければならない。 以下の式は、90パーセンタイルをjobごとに集約する。

histogram_quantile(0.9, sum(rate(http_request_duration_seconds_bucket[10m])) by (job, le))

全て集約するためには、leラベルのみを指定する。

histogram_quantile(0.9, sum(rate(http_request_duration_seconds_bucket[10m])) by (le))

histogram_quantile()は、バケット内が線形に分布していると仮定して分位数の値を補間する。 一番大きいバケットは、上限が+Infでなければならない(そうでなければ、NaNが返される)。 分位数が一番大きいバケットに位置している場合、2番目に大きいバケットの上限が返される。 一番小さいバケットの下限は、そのバケットの上限が0より大きければ、0と仮定される。 その場合、そのバケット内では通常の線形補間が適用される。 そうでない場合、一番小さいバケットに位置している分位数に対して一番小さいバケットの上限が返される。

bバケットが2つより少ない場合、NaNが返される。 φ < 0に対しては、-Infが返される。 φ > 1に対しては、+Infが返される。

参考リンク

和訳活動の支援

Prometheusドキュメント和訳が役に立った方は、以下QRコードからPayPayで活動を支援して頂けるとありがたいです。

PayPayによる支援用QRコード
上のQRコードからPayPayによる支援

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 Prometheus ―インフラとアプリケーションのパフォーマンスモニタリング

入門 監視 ―モダンなモニタリングのためのデザインパターン

入門 監視 ―モダンなモニタリングのためのデザインパターン

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム