かゆいところに手が届く実践「Prometheus」「Grafana」――PromQL、スクレイプ、Exporter、運用のためのポイント:Cloud Nativeチートシート(15)
Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、Observabilityのシグナル「メトリクス」について紹介し、「Prometheus」「Grafana」を使う上でのポイントを解説します。
※岡本、正野、宇都宮はNTTデータ所属
Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。第13回から「Observability(オブザーバビリティ)」「可観測性」にフォーカスしています。第13回ではObservabilityの概要と、その構成要素や考慮点を、第14回ではそれを活用したトラブルシューティングフローを紹介しました。
第15回となる今回は、最もベーシックなObservabilityのシグナルであるメトリクスにフォーカスして、デファクトスタンダードなオープンソースソフトウェア(OSS)「Prometheus」「Grafana」に関連したポイントを紹介します。
1章:Prometheusの概要
連載第13回で説明したように、「メトリクス」は、サーバのリソース状況(CPU使用率など)やサービス状況(レイテンシ、トランザクション量、エラーレートなど)といった、指標となる数値データで、これまでも古くから監視されてきた項目です。このメトリクスの監視ツールとして、急速に利用されてきているのが、Prometheusです。
Prometheusでは次のような特徴から、コンポーネント数が多くなりがちなKubernetesを中心としたクラウドネイティブなアーキテクチャにおいて、監視OSSとしてはデファクトスタンダードといえます。
- 多次元データモデル
時系列のメトリクスデータにラベルを付与することでKey-Value形式で取得できる - メトリクス公開のしやすさ
決められたフォーマットでメトリクスデータをHTTP公開するだけで、Prometheusにメトリクスを収集できる - サービスディスカバリ
Kubernetesリソースなどのコンポーネントのモニタリングターゲットを検出する仕組みで、Kubernetesをデフォルトでサポートしている - スクレイプ
サービスディスカバリで提供される情報を使って「どういったターゲットのメトリクスを収集するのか」を設定できる - 多くのインテグレーション
各言語で利用できるライブラリ、さまざまなミドルウェアやソフトウェアで利用できるメトリクスを収集、公開するエージェント「Exporter」の開発が盛ん - クエリ言語「PromQL」
シンプルな記法で時系列データを探索できる
PrometheusのダッシュボードとしてはGrafanaが一般的といえます。GrafanaからPromQLを呼び出せるので、Prometheus上に収集した各種コンポーネントのメトリクスをラベルでうまくフィルタリング、集計しながらさまざまな表示形式で可視化できます。
本稿では、PrometheusとGrafanaについて以下の流れでポイントを紹介します。
- 2章:PromQL
PromQLを活用してシンプルな記法で時系列データを扱う方法 - 3章:スクレイプ
Prometheusによるメトリクス収集時のポイント(サービスディスカバリを活用したKubernetesリソース検出、ラベリング) - 4章:Exporter、Dashboard
ターゲットのメトリクスを公開するExporterを使った多様なミドルウェアやメトリクスとの連携 - 5章:運用のためのポイント
Prometheusを運用する際のよくある課題とその解決策
2章:PromQL
Prometheusでは PromQLという独特の記法を使って、シンプルに時系列データを扱うことができます。前述したように、Grafana上でPromQLを記述し、Prometheusからメトリクスを取得して可視化しますが、その際にPromQLの記法を理解しておくことで自身が確認したい観点でフィルター、集計したメトリクスを可視化できるようになります。
ダッシュボードの作成時には他の誰かが作ったGrafanaのダッシュボードを利用することが多いと思います。そういった場合に置いても、使用されているPromQLを理解することで、「可視化された情報がどういった集計に基づいて表示されているモノなのか」を理解でき、トラブルシューティングやカスタマイズが容易になります。
本章では、代表的なメトリクス集計クエリを解説し、チートシート的にPromQLの記法を紹介します。
逆引きPromQL
PromQLの利用例を理解できるように、4つのメトリクス集計の例を使って比較的よく使う関数や結合方法(ベクトルマッチング)などの記法を紹介します。PrometheusのOperator「kube-prometheus-stack」(4章)で提供されているGrafanaのダッシュボード内で利用されているPromQLをベースに、代表的なメトリクスの集計方法とそこで使われているPromQLの要素を見ていきます。
- 【関数:irate(...[5m])】
- 【メトリクス:container_cpu_usage_seconds_total】
- 【セレクタ:\{job="kubelet", metrics_path="/metrics/cadvisor", image!=""\}】
- 【関数:changes(...[1m])】
- 【メトリクス:kube_pod_container_status_restarts_total】
- 【関数:histogram_quantile(0.95,...[1m])】
- 【メトリクス:istio_request_duration_milliseconds】
- 【演算子:sum by、max by】
- 【メトリクス:kube_pod_container_resource_limits、kube_pod_status_phase】
- 【ベクトルマッチング:on、group_left】
kube-prometheus-stackのhelmテンプレートはこちらにあります。
・1.コンテナのCPU使用率
メトリクスでもよく参照するコンテナCPU使用率の取得方法です。例えば、PodやコンテナごとのCPU使用率を時系列で表示するグラフを作成したい場合に活躍します。
後述しますが、CPU使用量に関するメトリクスとしては「cadvisor」が公開する「container_cpu_usage_seconds_total」というコンテナ起動時からのCPU時間(秒)の累計値を利用できます。この秒間増加数を計算すれば、1秒当たりのCPU使用時間=CPU使用率を計算できます。
例えば、kube-prometheus-stackではレコーディングルール(後述)として以下の箇所でCPU使用量を取得するPromQLが定義されています(参考)。
irate(container_cpu_usage_seconds_total{job="kubelet", metrics_path="/metrics/cadvisor", image!=""}[5m])
このPromQLをGrafanaで実行すると、時系列のコンテナCPU使用率を可視化できます。
このPromQLを例に、それぞれ分解します。
「irate(...[5m])」が関数、「container_cpu_usage_seconds_total」がメトリクス名、「{}」がラベルによる絞り込みをしている構成です。
【関数:irate(...[5m])】
「irate」はPrometheusの関数です。「メトリクスが1秒当たりどれくらいのペースで増えているのか」を計算できます。
Prometheusには幾つかのメトリクスタイプがあります。「カウンタ」メトリクスは、開始からの合計値(累積値)として公開されています。なお、他のメトリクスタイプには「ゲージ」「サマリ」「ヒストグラム」があります(参考)。
カウンタのメトリクスについて可視化する際には秒間の増分を確認する必要があり、その際にこのirateを利用します。
[5m]は「範囲ベクトル」というものです。指定した時間範囲内の複数のデータポイントを引数として関数に渡します。
「irate(...[5m])」という書き方をすることで、5分のデータポイントにおいて最後の2個のデータポイントを使って秒間の増分を計算します。ただし、5mで計算に使うデータの範囲を指定しますが、irateは最後の2ポイントの増分を利用するので、実質的にはこの範囲ベクトルは無視されます。
コラム irateとrate
irateに似た関数として「rate」もあります。
irateとrateの違いは増分を計算するデータポイントが、irateだと最後の2個のみを参照した現在の傾きを計算するのに対し、rateでは与えた範囲ベクトルのデータポイントを全て参照した範囲内の平均的な傾きを計算します。
irateとrateで同じデータを使ってグラフを作りました。irateでは個々の増減に反応して乱高下しているのが分かります。
この結果として、irateでは計算時間が短くなる一方で、グラフが変動しやすくなります。頻繁に確認してクエリ負荷を抑えたい場合はirate、短期的な変化に左右されたくない(アラート、長期の傾向を把握する用途など)場合はrateを使う方がよくなります。用途とクエリのレイテンシにおけるトレードオフで検討しましょう。
【メトリクス:container_cpu_usage_seconds_total】
container_cpu_usage_seconds_totalはメトリクス名です。コンテナのCPUの使用時間累計をカウンタとして返します。カウンタによる累計値なので、前述のirateを利用することで秒間のCPU使用時間=CPU使用率を計算できます。
なおcontainer_cpu_usage_seconds_totalは、Kubernetesのcadvisor機能によって提供されています。cadvisorが公開するメトリクスを活用することで、コンテナのリソースメトリクスを収集利用することができます。
cadvisorで公開されるメトリクスはこちらのドキュメントに一覧があります。
※上記ドキュメントで確認すると、container_cpu_usage_seconds_totalはカウンタと記載されていることが分かります。
なお、cadvisorは「kubelet」にも含まれており、Kubernetesではデフォルトでこのコンテナリソースメトリクスが公開されています。
【セレクタ:{job="kubelet", metrics_path="/metrics/cadvisor", image!=""}】
Prometheusはラベルをキーとして、メトリクスの値を絞り込むことができます。PromQLでは{}によってラベルによる絞り込み(セレクタ)が可能です。{job="kubelet", metrics_path="/metrics/cadvisor", image!=""}だと、「job」「metrics_path」「image」ラベルで絞り込んでいます。
前述の例では、「job」によってメトリクスのスクレイプ(Prometheusによる収集)をする際に定義したジョブ名が「kubelet」でかつ、「metrics_path」によってメトリクスを公開しているパスが「/metrics/cadvisor」でかつ、「image」によって利用しているコンテナイメージが「!=""」つまり空でないメトリクスに絞り込んでいます。
このラベルにはメトリクスを公開する側で付与しているモノと、スクレイプ時にPrometheus側で付与するモノ(ターゲットラベル)とがあります。例えば「job」「metrics_path」などはPrometheus側で付与しているラベルです(「scrape_config」で設定)。
・2.Podのリスタート回数
Podのリスタート回数の取得方法を紹介します。例えば、各Podのステータスやリソース情報を確認するダッシュボード上でリスタート回数などを確認する際に活躍します。
後述しますが、Podのリスタートに関するメトリクスとしては、「kube-state-metrics」が公開するデータポイント「kube_pod_container_status_restarts_total」を取得する時のコンテナごとのリスタート回数累計値を利用できます。「リスタート累計回数が一定期間内にどれくらい増加したか」を計算できれば、時系列でリスタート回数を確認できるようになります。
例えば、次のようなPromQLを利用することで、Podの分間リスタート回数を集計することができます。
sum( changes(kube_pod_container_status_restarts_total{ exported_namespace=~\"$namespace.*\", pod=~\"$pod.*\"}[1m] ) )by(pod)
「$namespace」「$pod」のように、先頭に付いている「$」はGrafanaの変数です。Grafanaのダッシュボードの画面から、選択したラベル値を参照できるようになります。例えば、下記のダッシュボードで「namespace」に「monitoring」を選択すると、$namespaceにはmonitoringが入り、メトリクスを絞り込めるようになっています。
コラム Grafanaにおける変数定義
Grafanaでは下図のようにダッシュボードの上部に変数(Variables)を定義し、ドロップダウンリストによる変数の指定によって動的にダッシュボードの表示内容を変える実装が一般的です。
変数は次のようにGrafanaの設定画面で定義できます。
変数にはGrafanaから実行できる「label_values()」というラベル一覧を取得するクエリなどを利用できます。例えば上記の例だと、namespaceラベルの一覧を取り出して、Grafanaの変数として利用できるようにしています。
こうして定義された変数は、上述のように$を先頭に付けて$namespaceという変数として利用できます。
【関数:changes(...[1m])】
「changes」はPrometheusの関数です。「メトリクスが指定した期間でどれくらいの変化があったのか」を計算することができます。
リスタート回数の一定期間内の増加数を確認したい場合、リスタート回数は基本的に1ずつしか増えないので、changesによる変化数を取得することで問題なく計算できます。
似た関数として「increase」があります。
changesとincreaseの違いは指定した期間における変化回数を返すか増分を返すかです。期間内の変化回数に興味があり、増分が2以上を意識しない場合(頻繁に増加しないメトリクスなど)はchangesがよいですが、増分を捉えたい場合(頻繁に増加するメトリクスなど)はincreaseがよいでしょう。
increaseは、増分を捉えているのではなく、増分から計算された秒間増加数による増分を返します(increaseの実体は「rate() × 期間」)。
グラフを見てみましょう。changeとincreaseで同じデータを使ってグラフを作りました。
このグラフでは分かりやすくするために、10秒間隔のデータについて15秒の範囲を指定しています。40秒のタイミングを見てみると、「changes[15s]」では15秒の実際の変化回数(2回)が表示される一方で、「increase[15s]」は過去10秒間のデータポイントの増加量(傾き)から15秒間の増加量の推定値(1.5回)が表示されることが分かります。
【メトリクス:kube_pod_container_status_restarts_total】
「kube_pod_container_status_restarts_total」はkube-state-metricsによって公開される、コンテナの累計リスタート回数を返すメトリクスです。
kube-state-metricsで公開されるメトリクスはこちらのドキュメントに一覧があります。
※上記ドキュメントで確認すると、「kube_pod_container_resource_limits」「kube_pod_status_phase」は「ゲージ」と記載されています。これは前述したカウンタとは異なるメトリクスタイプで、各データポイント取得時点での値が返されます。
- ゲージについてのドキュメント:https://prometheus.io/docs/concepts/metric_types/#gauge
なお、kube-state-metricsは素のPrometheusには含まれていないので、DaemonSet配置によるExporterの別途導入が必要です。後述するkube-prometheus-stackにはコンポーネントの一つとして含まれており、「helm install」でPrometheus Operatorと併せてクラスタにインストールされます。
・3.マイクロサービスごとのレイテンシ
パーセンタイルを利用したマイクロサービスごとのレイテンシを取得する例を紹介します。マイクロサービスを構築している場合、各マイクロサービスの処理レイテンシをSLI( Service Level Indicator)、SLO(Service Level Objective)として確認することがあり、そういったサービス稼働状況を確認するダッシュボードの利用において活躍します。
マイクロサービスのレイテンシを取得する方法は、アプリケーションで実装したり、ミドルウェアのアクセスログを利用したりといったものがありますが、一案としてサービスメッシュの「Istio」で利用される軽量プロキシ「Istio Proxy」が取得するメトリクスを利用する方法が考えられます。
例えば、Istioのドキュメントでは、下記のようなサービスにおける送信元/送信先ごとの分間95パーセンタイルのレイテンシを集計するPromQLが紹介されています。
histogram_quantile(0.95, sum(irate(istio_request_duration_milliseconds_bucket{reporter="source"}[1m])) by ( destination_canonical_service, destination_workload_namespace, source_canonical_service, source_workload_namespace, le ) )
コラム パーセンタイル(Percentile)とは
レイテンシのメトリクスの集計の方法として平均や最大といった集計を利用することもできますが、Webサービスのレイテンシの評価においてより有効な集計方法が「パーセンタイル」です。パーセンタイルは「分位数」ともいいます。
データを小さい順に並べたときに、全体のα%の位置にあるデータを「αパーセンタイル」といいます。例えば、「10パーセンタイル」は下から10%目の辺りとなり、「50パーセンタイル」は中央値になります。
一般的に、Webサービスのレイテンシは下図のように正規分布とは異なった遅延リクエストがロングテールを形成するような分布になります。こういった分布で平均値を利用すると、外れ値に引きずられやすくなり、一定の評価が難しくなります。そのため、例えば全リクエストのうち10%がどれほどのレイテンシ(以上)にかかっているのかを示す「90パーセンタイル」の値を見ることによって、上位10%の外れ値の影響を受けずに評価できます。それに加え、全体の10%以上が遅延した場合には遅延を確認できるようになります。
例えば上記のグラフを見てみましょう。これはあるWebサービスのレイテンシの出現回数をグラフにしたものと考えてください。上のグラフと下のグラフの差分は、下のグラフのみ100秒越えレイテンシが数回発生している点のみです。これは、いわゆる「NW瞬断」やGC(Garbage Collection)発生時の遅延のような外れ値を想定していますが、単純な平均だと数回発生した外れ値に影響を受けやすく、90パーセンタイルだと影響を受けにくいのが分かります。
詳細はこちらの記事が参考になります。
【関数:histogram_quantile(0.95,...[1m])】
通常パーセンタイルを利用する際は、ヒストグラムタイプのメトリクスを利用します。ヒストグラムタイプのメトリクスに対して、「histogram_quantile」関数を用いることで、パーセンタイルを計算できます。
※ヒストグラムメトリクスタイプのドキュメント:https://prometheus.io/docs/concepts/metric_types/#histogram
ヒストグラムメトリクスタイプには、「レイテンシの範囲内に幾つリクエストが存在したか」をヒストグラムのように複数のメトリクス(「_bucket」というサフィックスが付いており、「バケット」といいます)として表すことで、レイテンシの分布を表現できるような仕組みです(各値は累積値となっています)。
下図の例だと、0.25秒以下(le="0.25")のリクエストが1個、0.5秒以下(le="0.5")のリクエストが1個発生し……というようにバケットによってリクエストの分布が表現されます。
また合計16個メトリクスが発生し(histogram_metrics_count)、合計レイテンシは17.99秒(histogram_metrics_sum)という情報がヒストグラムメトリクスタイプによって表されています。
※レイテンシの値自体はメトリクスとして保存されません。あくまで分布のみが保存されます。
こういったヒストグラムメトリクスタイプのバケットをhistogram_quantile関数にパーセンタイルの分位数(90パーセンタイルなど)と併せて渡すことで、パーセンタイルの計算が可能になります。上図にあるように、パーセンタイルの近似値がhistogram_quantileによって計算されることになります。
注意点としては、ややこしいのですがバケット自体のメトリクスタイプは基本的にカウンタ(計測開始時からの累計値)なので、irateやrateによって秒間増分を計算してから、histogram_quantileに渡す必要があります。
【メトリクス:istio_request_duration_milliseconds】
「istio_request_duration_milliseconds」はメトリクス名です。Istio Proxyによって公開されるリクエストのレイテンシを返します。
Istioで公開されるメトリクスはこちらのドキュメントに記載があります。
・4.Namespace内での稼働しているコンテナのCPU Limits合計
Namespace内で稼働しているコンテナのCPU Limits合計の取得方法を紹介します。
例えば、「Namespace内で稼働するPodのLimit合計がリソースクオータに対してどの程度を占めている状況か」などを把握するダッシュボードで活躍します。後述しますが、コンテナのCPU Limitsに関するメトリクスとしては、kube-state-metricsが公開する「kube_pod_container_resource_limits」というデータポイント取得時点でのCPU Limitsの値を利用できます。このCPU Limitsの値をクラスタやNamespaceの単位で合計できればよいでしょう。
例えば、kube-prometheus-stackでは「recording rule」(後述)として下記リンクの箇所でCPU使用量を取得するPromQLが定義されています。
sum by (namespace, cluster) ( kube_pod_container_resource_limits{resource="cpu",job="kube-state-metrics"} * on(namespace, pod, cluster) group_left() max by (namespace, pod, cluster) ( kube_pod_status_phase{phase=~"Pending|Running"} == 1 ) )
「sum by」「max by」が演算子、「kube_pod_container_resource_limits」「kube_pod_status_phase」がメトリクス名、「group_left」「on」が二項演算子、「{}」がラベルによる絞り込みという作りです。
【演算子:sum by、max by】
「sum」「max」はPrometheusの演算子です。見た目の通り、グループ内の合計、最大値を算出することができます。他にも平均(「avg」)、標準偏差(「stddev」)、標準分散(「stdvar」)など多くの演算子が用意されています。
sum、maxを利用する際に、メトリクスがカウンタの場合、前述したirateなどを使って秒間増分を計算してから適用する必要がある点に注意しましょう(カウンタは累積値であり、例えばmaxを計算しても最後の値しか出てこないので)。今回のクエリで対象としているkube_pod_container_resource_limitsはゲージです。従って特に前処理せずに、合計や最大の計算が可能です。
「by」によって合計や最大の集計時に軸となるラベルを指定できます。加えて不要なラベルは結果から取り除けます。今回は、「cluster」「namespace」「pod」などのラベルを指定しています。
※byとは別に同様にグルーピングを行う演算子として「without」もあります。withoutではbyと異なり、集計の軸にしたくないラベルを列挙します。
【メトリクス:kube_pod_container_resource_limits、kube_pod_status_phase】
「kube_pod_container_resource_limits」「kube_pod_status_phase」は、kube-state-metricsというPrometheus Exporter(メトリクスを収集、公開するエージェント)によって公開されるKubernetesリソースのステータスや設定に関するメトリクスの一つです。コンテナのCPU Limits、Podのステータスをそれぞれ返します。どちらのメトリクスもゲージなのでそのままsumやmaxで計算できます。
【ベクトルマッチング:on、group_left】
「on」「group_left」は「ベクトルマッチング」という、ラベルが完全に一致しない2つのメトリクスを結合(SQLでいう「join」)したい際に利用する演算子です。
今回稼働しているコンテナのCPU Limits合計を取りたいので、稼働していないコンテナの情報は除外したいので、PromQLではgroup_leftによる結合で、Podのステータスが「Pending」「Running」のコンテナは「1」、それ以外のコンテナには「0」をCPU Limitsに掛けることで、稼働していないコンテナを除外しています。
kube_pod_container_resource_limits{resource="cpu",job="kube-state-metrics"} * on(namespace, pod, cluster) group_left() max by (namespace, pod, cluster) ( kube_pod_status_phase{phase=~"Pending|Running"} == 1 )
この際にkube_pod_container_resource_limitsのメトリクスとkube_pod_status_phaseのメトリクスがそれぞれ複数の異なるラベルを持っている可能性があるので、そのままでは掛け算ができません。onによって「どのラベルを軸に掛け算をするのか」を指定する必要があります。
今回だと、「on(namespace, pod, cluster)」とNamespace、Pod、クラスタと3つのラベルを指定しており、onの左辺が返すデータに対してこれらのラベルを軸にマッチングした右辺のデータを掛け合わせます。
また、今回左辺はコンテナのCPU Limitsを計算し、右辺はPodのステータスを計算しています。そのため、左辺はコンテナごとのデータが返される一方で、右辺はPodごとのデータが返されるようになっており、左辺と右辺は多対一の関係です。
こういった場合にはgroup_leftを利用することで、左辺のラベルが違うデータごとに計算できます。つまり、Pod内に複数コンテナが含まれている場合においても、各コンテナに対して掛け算ができます。
PromQLチートシート
ここでは、「逆引きPromQL」で紹介し切れなかった演算子や関数を簡単に紹介します。PromQLで登場する概念としては大きくは、セレクタ、演算子、関数があります。代表的な要素を下図にまとめました。
セレクタは、SQLでいうと「WHERE」句のような条件を指定する概念です。ラベルによる絞り込みが可能なラベルセレクタや、指定した時間範囲でデータのリストを返す範囲ベクターは既に紹介しました。
- offset
指定した時刻分過去の情報を取得する。例えば「up offset 10m」とすることで、10分前のupメトリクスを取得できる。過去と比較して計算したい場合などに用いる。
【演算子】
演算子は与えたデータについて各種演算や、グルーピング(SQLでいう「group by」)、ベクトルマッチング(SQLでいう「join」)をする概念です。グルーピングやベクトルマッチングについては既に「逆引きPromQL」で触れました。
- 集計演算子
- sum(合計)、count(個数集計)、avg(平均)、stddev(標準偏差)、stdvar(標準分散)、min(最小)、max(最大)
- topk、bottomk:上位n個、下位n個
- 算術演算
- +、-、*、/、%、^
- 比較演算
- ==、!=、>、>=、<、<=
- 論理演算
- or
左辺のグループがサンプルを持っているグループについてはそのサンプルを返し、そうでないグループについては右辺のグループのサンプルを返す(SQLでいう「COALESCEE」)。2つの時系列データの条件に合う方(例:大きい方、小さい方)を使いたい場合などに用いる - unless
左辺の要素から右辺が一致するモノを削除する。式に基づいて時系列データを絞り込みたい(例:特定のメトリクス条件に合うPodのCPU使用率を取得したい)場合などに用いる - and
unlessの逆で、左辺の要素から右辺が一致するモノを残す。複数の式に基づいて絞り込みたい(例:特定条件時のアラート)場合などに用いる
- or
- ベクトルマッチング
- 一対一:on、ignoring
左と右辺でマッチするサンプルが一対一である場合、onかignoringで結合できる。結合に使うラベルを明示的に指定する場合はon、結合に使わないラベルを明示的に指定する(指定したラベル以外全てが結合条件となる)場合はignoringを使う - 多対一:group_left
左辺にマッチするサンプルが複数あり得る(多対一)である場合、group_leftで結合できる
- 一対一:on、ignoring
【関数】
関数は、変換(型変換、数学的変換、ラベル変換)、各メトリクスタイプ(カウンタ、ゲージ、ヒストグラム)に応じた集計、時系列データの経時的集計、欠損値の処理などが行えます。
- 型変換
- vector、scaler
Prometheusのデータ型として、0個以上の時系列データは「インスタントベクトル」といい、単独の数値は「スカラ型」という。vectorによってスカラ型をインスタントベクトル型に変換し、scalerによってインスタントベクトル型をスカラ型に変換できる - 数学変換
- abs(絶対値)、ln(自然対数)、log2、log10(常用対数)、exp(自然指数)、sqrt(平方根)、ceil(切り上げ)、floor(切り捨て)、round(四捨五入)、clamp_max(上限指定)、clamp_min(下限指定)
- ラベル変換
- label_replace
ラベルを正規表現で変換できる - label_join
複数のラベルを結合できる - カウンタで使える関数
- resets
「指定した範囲の中で時系列データが何回リセットされているか」を返す。カウンタは上述したように累積値だが、その累積値がリセットしたかどうか(例:再起動によってカウンタメトリクスがリセットされてしまった回数)を集計する場合などに使う - ゲージで使える関数
- deriv
指定した範囲の時系列データの傾きを最小二乗回帰を使って算出する。「ゲージメトリクスが、どういった傾向で変化しているか」を知りたい場合(例:過去1時間分のメモリの増加傾向)などに使える - predict_linear
指定した範囲の時系列データから将来の値を予測する(例:「過去1時間分のメモリ増加傾向に基づき、1時間後のメモリ使用量がどうなっているか」を推定する) - delta、idelta
指定した範囲の差分を返す。ideltaは時系列データの直近最後の2つのサンプルの差分を返す。「瞬間的に、どういった増分が見られるか」を確認したい場合などに使える - holt_winters
ホルト・ウィンタースの二重指数平滑法を利用して平滑化値を返す。スパイクの頻度が激しくて読みにくい場合に、平滑化する手段の一つとして有効 - 経時集計
- 時系列データの一定期間での集計を取れる
- 次のように多様な経時集計を利用できる
sum_over_time、count_over_time、avg_over_time、stddev_over_time、stdvar_over_time、min_over_time、max_over_time、quantile_over_time - 欠損値
- absent
空でない要素を渡すと空の要素を返し、何も要素がなければ値が1のサンプルを返す - タイムスタンプ
- time
クエリを実行した日時をエポック秒で返す(UTC) - day_of_month
1〜31で日付を返す(UTC)
レコーディングルールを利用してダッシュボードを高速化する
レコーディングルールとは、Prometheusに定期的にPromQLを計算させ、新しいメトリクスとして保持することができる機能です。段SQLを使う人はRDBMS中間テーブルをイメージすると理解しやすいでしょう。また、PromQLの結果を新しいメトリクスとして定義できます。普段SQLを使う人はRDBMS中間テーブルをイメージすると理解しやすいでしょう。
レコーディングルールを利用することで、Prometheusに定期的にPromQLを計算させることができ、次のようなメリットがあります。
- ダッシュボードの高速化
あらかじめPromQLの結果を計算させておくことで、その都度、PromQLでグラフの結果を計算するのに比べて、ダッシュボードの表示を高速化できる - 共通化
よく使う集計結果をPromQLであらかじめ計算させておくことで、その都度PromQLを記述する必要がなくなり、共有化できる
レコーディングルールは、「ルールファイル」という、「prometheus.yml」とは別の設定ファイル上(例:rules.yml)で定義します(※ルールファイルはprometheus.ymlのrules_files:セクションで指定しておく必要があります)。
例えば、kube-prometheus-stackから、以下のようなファイルで設定を定義しています。
groups: - name: k8s.rules interval: 15s rules: - expr: |- sum by (cluster, namespace, pod, container) ( irate(container_cpu_usage_seconds_total{job="kubelet", metrics_path="/metrics/cadvisor", image!=""}[5m]) ) * on (cluster, namespace, pod) group_left(node) topk by (cluster, namespace, pod) ( 1, max by(cluster, namespace, pod, node) (kube_pod_info{node!=""}) ) record: node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate
このようにルールファイルでは、グループを定義し、その中にルールを定義します。ルールには、exprに定期的に実行されるPromQL、「recode」に結果を格納するメトリクス名を定義します。
PromQLの実行頻度はprometheus.ymlか各ルールグループで「interval」によって指定ができます。intervalについては、「global」セクションの「evaluation_interval」でも設定できますが、個別にintervalを設定すると、各メトリクスで評価間隔にばらつきが発生するので、メトリクス利用時に利用者が混乱することがあります。できるだけ、同じintervalで評価するようにした方がいいでしょう。
定義したルールの直近実行タイミング(Last Evaluation)や実行時間(Evaluation Time)はPrometheusダッシュボードの「Status」→「Rules」で確認できます。
評価された結果は定義したメトリクス名として格納されるので、次のように別のPromQLからメトリクスとして参照することが可能です。
sum( node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=\"$namespace\", pod=\"$pod\", cluster=\"$cluster\"} ) by (container)",
3章:スクレイプ
1章で簡単に説明したようにPrometheusでは、サービスディスカバリという仕組みを利用して、Kubernetesリソースなどの監視対象を検出できます。スクレイプという機能によって「サービスディスカバリで提供される情報を使って、どういったターゲットのメトリクスを収集するのか」を設定することができます。
設定自体はprometheus.yml内の「scrape_configs:」に記述します。設定できる項目としては、次のような項目があります。
- サービスディスカバリの設定(例:kubernetes_sd_configs)
- ラベル衝突時の優先順位設定(honor_labels)
- スクレイプの収集間隔(scrape_interval)
- スクレイプのタイムアウト時間(scrape_timeout)
- メトリクスパス(metrics_path)
- スクレイプ時のラベルの貼り替え(relabel_configs)
※代表的な設定項目を抜粋しています。詳しくはドキュメントを参照してください。
Kubernetesにおけるサービスディスカバリ
PrometheusはサービスディスカバリによってKubernetesが提供する情報を利用し、NodeやPodなどターゲットとなるリソースのIPを検出できます。サービスディスカバリによって、動的に変化するリソースに対して、モニタリングを追従して行えます。
サービスディスカバリではメタデータとして、サービスの名前やラベル、アノテーションなどを提供できます。このメタデータを利用することで、スクレイプ時のターゲットの絞り込みやラベル書き換えを円滑にできます。
Prometheusで使えるKubernetesサービスディスカバリには、現在のところ、node、service、pod、endpoints、endpointslice、ingressの6種類があり、scrape_configs.kubernetes_sd_configs.roleで指定できます。
これらのサービスディスカバリをどのように使い分けるかを簡単に説明します(参考)。
名前 | 概要、使いどころ | メタデータ |
---|---|---|
node | Nodeごとに1つターゲットを検出する。Nodeごとに存在するメトリクスパス(例:Kubeletのメトリクスなど)を収集したいケースで有用 | Nodeのアノテーション、ラベルなど |
service | Serviceのポートごとにターゲットを検出する | Serviceのアノテーション、ラベル、ポートなど |
pod | Podのコンテナごとにターゲットを検出する | Podのアノテーション、ラベル、ステータス、コンテナ名、コンテナポート、Node名など |
endpoints | ServiceのEndpointごとにターゲットを検出する。EndpointにPodがひも付いている場合、Podの全てのコンテナポートもターゲットとして検出する | Endpoint名、ポートなど(※Podひも付き時はpodサービスディスカバリで利用できるメタデータも利用できる) |
endpointslice | EndpointSliceのEndpointアドレスごとにターゲットを検出する。EndpointにPodがひも付いている場合、Podの全てのコンテナポートもターゲットとして検出する。ブラックボックスモニタリング(外形監視)に有用 | EndpointSlice名、ポート、Endpointアドレス名など(※Podひも付き時はpodサービスディスカバリで利用できるメタデータも利用できる) |
ingress | 各Ingressのターゲットを検出する | Ingressのアノテーション、ラベルなど |
relabel
「relabel」はスクレイプ直前に、時系列データに付加されるラベルを動的に書き換える機能です。ラベルを書き換えるだけでなく、サービスディスカバリのメタデータの情報(Kubernetesリソースのアノテーションなど)を利用した動的な監視対象の設定ができるので、Prometheusのスクレイプを支える強力な機能の一つです。
スクレイプの設定(scrape_configs)でジョブごとに複数のrelabel処理を設定することができます(参考)。
初期のターゲットラベルは次のように設定されています(一例)。
- job:scrape_configs.job_name
- __address__:スクレイプするときにPrometheusが接続するホストとポート(<host>:<port>)
- instance:relabel後の__address__
- __metrics_path__:メトリクスパス
- __meta_プレフィックス:サービスディスカバリで提供されるメタデータ(例:Kubernetesリソースのラベル、アノテーションなど)
※__プレフィックスのラベルはrelabel後にラベルセットから削除されます。
relabelの設定では「action」として以下のような処理を定義できます。
- replace:regexで定義される正規表現。「source_labels」がマッチした場合、「target_label」が「replacement」に置換される。replacementでは正規表現のマッチした文字列を「${1}」のように参照できる
- keep:regexで定義される正規表現。source_labelsがマッチした場合、ターゲットに含める。マッチしない場合、ターゲットから外れる
- drop:regexで定義される正規表現でsource_labelsがマッチした場合に、ターゲットから外れる
- labelmap:regexで定義される正規表現で全てのソースラベルからマッチした場合に、マッチしたラベルがreplacementに置換される
- labeldrop:regexで定義される正規表現で全てのソースラベルからマッチした場合に、マッチしたラベルがターゲットから外れる
- labelkeep:regexで定義される正規表現で全てのソースラベルからマッチしたラベルのみターゲットに含める。マッチしなかったラベルがターゲットから外される
スクレイプの設定(scrape_configs)の例を見てみましょう。次のようなPrometheusによる監視情報をアノテーションとして定義したPod定義を想定します。
kind: Pod .... metadata: annotations: prometheus.io/scrape: true 【1】 prometheus.io/path: /metrics 【2】 prometheus.io/port: 9090 【3】 ...
prometheus.ymlのスクレイプ設定です。
scrape_configs: ... - job_name: 'kubernetes-pods' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] 【1】 action: keep regex: true - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] 【2】 action: replace target_label: __metrics_path__ regex: (.+) - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port] 【3】 action: replace target_label: __address__ regex: ([^:]+)(?::\d+)?;(\d+) replacement: $1:$2 - source_labels: [__meta_kubernetes_namespace] 【4】 action: replace target_label: namespace - source_labels: [__meta_kubernetes_pod_name] 【5】 action: replace target_label: pod_name
サービスディスカバリとしてKubernetesのPodのコンテナごとにターゲットが検出され、relabelで次の処理がサービスディスカバリ直前に行われるようになっています。
【0】KubernetesのサービスディスカバリのメタデータによってKubernetesリソースのアノテーションが(「__meta_kubernetes_pod_annotation_」プレフィックスで始まるラベルとして利用可能になります。
※アノテーション名の「., /」は「_」に置き換えられます。例えば、「prometheus.io/scrape」は「__meta_kubernetes_pod_annotation_prometheus_io_scrape」になります。
【1】prometheus.io/scrapeの値がtrue(「regex: true」)のアノテーションが付与されているPodのみを抽出(keep)します。
【2】prometheus.io/pathで指定されている値をスクレイプで利用するパス(ラベル__metrics_path__)に設定します。
【3】__address__に<PodのIP>:<ポート>が指定されていればそれを利用し、なければ「prometheus.io/port」で設定されているポート番号を利用します(「regex: ([^:]+)(?::\d+)?;(\d+)」)。
【4】【5】namespace(「__meta_kubernetes_namespace」)とPod名(「__meta_kubernetes_pod_name」)を分かりやすいラベル「namespace」「pod_name」に置換します。
このようにrelabelの仕組みを利用することで、スクレイプ時にアノテーションなどを利用して動的に監視対象を設定できます。上記のアノテーションを利用したポート指定は、Pod内の複数ポートに対してスクレイプしたい場合に利用できないので、ご注意ください。
「relabel時にどういったラベルをターゲットラベルとして保持するか」は集計、可視化する際に重要になるので気を付けましょう。
例えば別々のエクスポーターから集計したメトリクスに付与されているラベルが、スクレイプ時のjobが異なることによって、微妙にラベル名が異なってしまっていたり、片方のメトリクスにしか重要なラベルが付いていなかったりすることがあります。そうなってしまうと、可視化する際に統合的、横断的にメトリクスを見ようとしても「ラベルが不一致で見られない」ということになってしまうので気を付けましょう。
4章:Exporter、Dashboard
さまざまなミドルウェアやソフトウェアからメトリクスを収集、Prometheusからスクレイプできるようにエンドポイントを公開するのがExporterです(※下図のピンクで囲った部分です)。
メトリクスを収集する方法としてPrometheusのクライアントライブラリを利用してプログラミング言語側で自前で実装することも可能ですが、よく使われるミドルウェアなどではExporterを利用するのが便利です。
ExporterはPrometheus公式のGitHubリポジトリでメンテナンスされるものもあれば、サードパーティーとして提供、メンテナンスされるものもあります。
Prometheusのドキュメントに多くのExporterがリストされているので、一度確認してみてください。
Exporterのデプロイ方式
Exporterには4つのデプロイ方式が存在します。収集対象のコンポーネントの特性やExporterの実装(Exporterのドキュメントを確認)に合わせて選択します。
- DaemonSet型
KubernetesのDaemonSetとして各NodeにExporterのPodを1つ配置する方法。 各Nodeで公開しているモニタリングターゲットからメトリクスを収集する必要がある際にこの方式を採る - Deployment型
クラスタ内に1つエクスポーターを配置する方法。Kubernetes APIサーバや外部のDBなどに対してメトリクスを取得する際などにこの方式を採る - Sidecar型
プロセスとして起動し、他のミドルウェアやアプリケーションを監視する方式。厳密には、Podとしてデプロイして利用することもできるが、監視対象とExporterは同じPod内にあった方がネットワーク負荷の点から、こっちの方がよい。監視対象とExporterは1対1の方が管理しやすいというメリットがある(Exporter1に対して監視対象が複数あると、監視対象をExporterで管理する必要があるので)。このような場合は、Sidecarとして、Exporterをデプロイした方が管理しやすくなる。本記事では、Sidecarでの管理をお勧めするので、ExporterをSidecar型と定義する - アプリ埋め込み型
アプリケーション、ミドルウェアにコードを埋め込んでアプリケーション、ミドルウェア自身でメトリクスを公開する。メトリクス公開の方法としてはPrometheusや「Open Telemetry」のSDKを利用する方法がある
用途別、すぐ使えるExporter、Dashboard
よく使われるExporterと、Grafana Dashboardsで公開されているDashboardを併せて、表形式で紹介します。
Exporter | Exporterタイプ | デフォルトポート | 説明 | Dashboard |
---|---|---|---|---|
node_exporter | DaemonSet型 | :9100/metrics | ノードのメトリクスを取得 | リンク |
kube-state-metrics | Deplyoment型 | :8080/metrics | Kubernetesクラスタとリソースの情報のメトリクスを取得 | リンク(注) |
nginx-vts-exporter | Sidecar型 | :9113/metrics | Nginxのメトリクスを取得 | リンク |
apache_exporter | Sidecar型 | :9117/metrics | Apacheのメトリクスを取得 | リンク |
black box expoter | Deplyoment型 | :9115/metrics | 各種通信プロトコルの状況をメトリクスとして取得 | リンク |
mysqld_exporter | Sidecar型 | :9104/metrics | MySQL/MariaDBのメトリクスを取得 | リンク |
postgres_exporter | Sidecar型 | :9187/metrics | PostgreSQLのメトリクスを取得 | リンク |
oracledb_exporter | Sidecar型 | :9121/metrics | Oracleのメトリクスを取得 | リンク |
elasticsearch_exporter | Deployment型 | :9114/metrics | ElasticSearchのメトリクスを取得 | リンク |
redis_exporter | Sidecar型 | :9121/metrics | Redisのメトリクスを取得 | リンク |
SpringBoot | アプリ組み込み型 | :80/prometheus | SpringBoot組み込みの機能 | リンク |
Python | アプリ組み込み型 | :8000/metrics | Python用クライアントライブラリ | リンク |
Node.js | アプリ組み込み型 | 指定なし | Node.js用クライアントライブラリ | リンク |
Go言語 | アプリ組み込み型 | 指定なし | Go言語用クライアントライブラリ | リンク |
注:kube-state-metricsのサンプルは、Prometheus Operatorでの定義例を貼っておきます。このダッシュボードはConfigMapのデータとして定義されているので、ご注意ください。 |
※Podのメトリクスについては特にエクスポーターを置かずともKubernetesが提供する仕組みでメトリクスが公開されており、スクレイプを設定するだけで収集が可能です。
※ExporterとGrafana Dashboardの注意点
GitHubの更新履歴やGrafana DashboardのRevisionを見ると、最終更新が古いものも含まれています。Dashboardについては、メトリクス名が変わらなければ基本的に使えるはずですが、そもそも監視ダッシュボードはカスタマイズして見るべきものを見るのが、あくまで参考として必要なメトリクスを増やしたり、PromQLをカスタマイズしたりしてください。
5章:運用のためのポイント
Prometheus運用をより良く
ここまでPrometheusをそのまま利用する前提で説明しましたが、運用を考えると幾つか課題があります。それらの課題に対する解決策として、導入できるアイデアを簡単に紹介します。
・Prometheus Operator
例えば、次のような運用管理の課題があると思います。
- 冗長化やシャーディングの設定が面倒
- ExporterやGrafanaなど周辺コンポーネントをそれぞれ管理するのが面倒
- スクレイプの設定が難しくメンテナンスが面倒
- Prometheusサーバの継続的なメンテナンス
こういった課題に対しては、Prometheus Operatorを活用することで、ある程度解決すると考えています。
Prometheus OperatorはKubernetesオペレーターの一つで、Prometheusと関連コンポーネント管理するものです。Prometheusをリソース定義として扱えたり、ラベルをしていることでスクレイプ設定を自動的に生成できる「PodMonitor」「ServiceMonitor」を利用できたりします。Prometheusリソースでは、Prometheusのバージョンやデータ永続化、レプリカ数の定義などができます。
このPrometheus Operatorの設定例(PrometheusとAlertmanagerの冗長構成)とnode_exporterとkube-state-metricsの関連リソース設定とGrafanaの関連リソース設定を管理している「kube-prometheus」というPrometheus Operatorチームがメンテナンスしているプロジェクトや、コミュニティーによってメンテナンスされている「kube-prometheus-stack」というhelmチャートがあります。
Prometheus Operatorを利用する際には、これらを参考にするといいでしょう。
※前述の「逆引きPromQL」では、kube-prometheus-stackで管理されているGrafanaダッシュボードのPromQLを例にしている部分もあります。
・サードパーティーのリモートストレージ
次に、次のような可用性、性能、スケーラビリティの課題が出てくると思います。
- ストレージおよびAPIの可用性、スケーラビリティの確保
- Prometheusを冗長化させたり、分割運用したりすると、それを横断的にクエリできる必要がある
- クエリ応答時間やコスト削減のためのメモリ、ストレージの最適化
- メトリクスの長期間保存が性能上難しい
こういった課題に対しては、Prometheusの「Remote Write」という機能を使って、サードパーティーのPrometheus互換のリモートストレージにデータを書き出す方法によってある程度解決すると考えています。
サードパーティーのPrometheus互換のリモートストレージとしては、次のようなものがあります(参考)。
- OSS
- Cortex
- Thanos
- VictoriaMetrics
- Cloud
- Amazon Web Services(AWS)の「Amazon Managed Service for Prometheus」
- Google Cloud Platform(GCP)の「Managed Service for Prometheus」
これらのリモートストレージに対してPrometheusのRemote Write機能でデータを書き出し、メトリクスの分析、可視化はストレージに対して、GrafanaからPromQLを発行するようなイメージです。
こういったリモートストレージには可用性、長期保管、拡張性を考慮した冗長化、スケーラビリティの仕組みなどが組み込まれています。VictriaMetricsを例に取ると、下図のようなアーキテクチャを内部的には取っています(参考)。
Load Balancerで負荷分散されたPrometheusとGrafanaからのリクエストは、シェアードナッシングアーキテクチャで処理されます。「vminsert」によってコンシステントハッシュで各「vmstorage」に分散保存されたデータを、「vmselect」を用いて取り出すことができます。これによって、可用性とスケーラビリティに優れたPrometheus互換のリモートストレージを実現しています。
Grafanaのダッシュボードのコード管理
Grafanaのダッシュボードは内部でバージョン管理されているので、誤った操作でダッシュボードを壊してしまった場合でも以前のバージョンに戻せるようになっています。しかし、ダッシュボードの細かい変更を確認するために、最新版のJSONと古いバージョンのJSONの差分をダウンロードして比較する必要があるなど、手間がかかります。開発環境でダッシュボードをテストしてから運用環境へリリースする場合など、IaC(Infrastructure as Code)としてダッシュボードをコードとして管理しておくと便利です。
上記のPrometheus Operatorを利用したGrafanaを利用した場合、ダッシュボードは、ConfigMapで管理されているので、作成したダッシュボードのファイルからConfigMapを作成し、「Git」で管理してKubernetesにデプロイするようにすると便利です。
デプロイ時に「Argo CD」などの「GitOps」ツールを利用すれば、Gitにコミットした変更や、プルリクエストで承認された変更を自動的にGrafanaのダッシュボードに反映させることもできます。
また、自身でGrafanaを構築したりした場合など、ConfigMapで管理できないような場合は、ダッシュボードの格納ディレクトリ自身をGitで管理するようにしておき、開発環境でダッシュボードを変更した後で、Gitにコミットし、「CIOps」で商用環境にGitリポジトリの変更内容を反映させるようにしてもよいでしょう。
CIOpsによるダッシュボードの管理は、下記のサイトに分かりやすく記載されているので、紹介しておきます。
まとめ
本稿ではメトリクスにフォーカスして、クラウドネイティブなOSS監視ツールとしてデファクトスタンダードなPrometheus、Grafanaに関連したポイントを紹介しました。
Prometheusを利用する上では、PromQL、スクレイプ、Exporterという3つの観点が重要です。PromQLを使うことで、PromQLを活用してシンプルな記法で時系列データを扱えますし、スクレイプは設定次第でサービスディスカバリを活用したKubernetesリソースの動的な監視対象追加が可能です。また、Exporterを活用することで多様なミドルウェアやソフトウェアのメトリクスを監視可能になります。
今回紹介した内容をベースに、それぞれのアーキテクチャや負荷特性に応じた監視をカスタマイズしていただけると幸いです。
5章で紹介したように、Prometheusを素で運用するには苦労や課題が伴うケースが多いので、Operatorやサードパーティーのリモートストレージの利用なども併せて検討してみてはいかがでしょうか。
次回は、最近リリースされたKubernetes 1.24について解説します。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- New Relicがアプリの問題解決で開発者を助ける新機能、「開発者は本来の仕事に集中できる」
New Relicの日本法人が可観測性プラットフォームで新機能「New Relic CodeStream」を発表した。 開発ツールを離れることなくソースコードの問題箇所を特定し、チーム内で情報を共有して迅速に解決できるという。 - AWS、障害注入試験に向くフルマネージドサービス「AWS Fault Injection Simulator」を正式リリース
AWSはシステムに意図的に障害を発生させる障害注入試験に向いたフルマネージドサービス「AWS Fault Injection Simulator」の一般提供を開始した。CPUやメモリの使用量の急増といった破壊的なイベントを発生させてアプリケーションに負荷をかけ、システムの反応を監視して、改善できる。 - ユーザーの幸福度を定量化――SLI、SLO実践の4ステップ
SREは計測、自動化など取り組むことが多く、求められる知識量も少なくない。また周囲の理解が得られなければ、組織でSLI、SLOを定義してSREを実践するのも容易ではない。組織でSREに取り組む最初の一歩をどう踏み出せばいいのか。