DUICUO

Prometheusの詳細な理解:アラートにインジケータートレンドチャートを添付する

Prometheus はオープンソースのシステム監視およびアラート ツールキットであり、2016 年に Cloud Native Computing Foundation に加わり、Kubernetes に次ぐ 2 番目の管理プロジェクトとなりました。

この記事では、アラームの原因とタイミングをより正確に特定するために、インジケーターのトレンド チャートをアラーム情報に添付する方法について説明します。

アラートを発行する際に指標トレンドチャートを添付することの難しさ

Prometheus では、インジケーター チャートを表示する方法が 3 つあります。

  • エクスプレッションブラウザ
  • グラファナ
  • コンソールテンプレート

一般的には、EXPRESSION BROWSER のすべての機能を備え、印象的な画像出力とユーザーフレンドリーなエクスペリエンスもサポートする Grafana の使用をお勧めします。

ノードメモリ使用量の傾向チャート

Prometheus は、通常、次のように PromQL に基づくアラート ルールをサポートしています。

 グループ:
- 名前:
ルール
- アラート: NodeHighMemoryUsed
: node_memory_used_percent { ジョブ= "myjob" } > 0.8
10 m
ラベル:
重大度: 警告
注釈:
概要: ノードのメモリ使用量

最も重要なフィールドはPromQL式である`expr`です。式がtrueを返す場合、つまりデータがフィルタリングされた場合、各時系列メトリックに対してアラートが生成されます。デフォルトのアラートメッセージには通常、ラベルとアノテーションの情報が含まれます。この情報は通常、事前定義されたテキストとテキストテンプレートです(アノテーションはラベルを変数テンプレートとして使用することをサポートしており、アラート処理中に実際の値としてレンダリングされ、最終的にはテキストになります)。

アラームテキストに基づくメッセージでは、アラーム発生時のステータス情報しか取得できません。アラームは最初のステップに過ぎません。アラームの原因を特定するには、通常、アラーム発生前の過去の蓄積されたステータス情報が必要です。そして、それぞれのアラームに対応する独自の指標トレンドチャートを生成する必要があります。

上の画像に示すように、デフォルトのアラーム式では、条件を満たすすべてのデータが表示されますが、次の 2 つの問題があります。

  1. 各アラームには独自のトレンド チャートのみが必要です。他のオブジェクトのトレンド チャートを表示する必要はありません。
  2. 目標は、完全なトレンド チャートを表示して、トレンドの問題をより簡単に特定できるようにすることです。

下の図に示すように、このグラフは特定のアラーム オブジェクトに焦点を当てており、アラームの傾向とベースラインの両方が含まれているため、アラームの特定が容易になります。

私たちのルールは通常、次の形式になります。

 「node_cpu_usage > 0」
「rate(node_cpu_total{node=\"n1\"}[1m]) > rate(node_cpu_total{node=\"n2\"}[1m])」
「container_cpu_limit_usage / avg_over_time(container_cpu_limit_usage[1d] offset 1d) > 1.01」
「container_cpu_limit_usage > 0 かつ container_memory_limit_usage > 0」
「container_cpu_limit_usage > 0.5 かつ container_memory_limit_usage > 0.5 または container_cpu_limit_usage > 0.8」
`sum ( rate ( apiserver_request_duration_seconds_sum { サブリソース!= "log"verb ! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 m ])) インスタンスポッドなし
/
合計( レート( apiserver_request_duration_seconds_count { サブリソース!= "log"動詞! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 ])) なし( インスタンスポッド) `

これらのルールの基本原理は、メトリック名とラベルに基づいてデータをフィルタリングし、何らかの関数処理を実行し、最終的に特定の閾値行と比較することです。PromQLでは、比較操作は実際にはフィルタリング操作に変換されます。基準を満たすデータがフィルタリング可能な場合、アラームがトリガーされます。

アラートがトリガーされた理由を理解するには、まず各式を分解し、分解された各式に対してクエリを実行して、どのデータに問題があるのか​​を特定する必要があります。また、データ異常の前後における各式のコンテキストを調べる必要があり、これにはアラートの期間よりも長い期間が必要です。

もう1つのポイントは、アラームルールは通常、CPUアラームやノードのメモリアラームなど、特定のオブジェクトタイプに適用できることです。しかし、実際にアラームがトリガーされると、特定のノードごとの情報が提供されます。他のアラームも同様です。アラームデータを照会したい場合は、アラームをトリガーしたオブジェクトのメトリックデータのみを照会すればよく、他のオブジェクトを照会する必要はありません。

上記の機能を実現するには、通常、次の 2 つの問題を解決する必要があります。

  1. Prometheus アラート ルールを複数のクエリ式に分解します。
  2. アラート オブジェクトを区別するために、Prometheus アラート ルールにこのアラートに固有のラベルを追加します。

解決

上記の 2 つの機能は Prometheus では直接使用できませんが、コード分析からその有用性と妥当性を導き出すことができます。

 func InjectSelectors ( expr Expr , selectors [] * labels . Matcher ) error {
検査( expr , func ( node Node , _ [] Node ) エラー{
vsok : = ノード.( * VectorSelector )
大丈夫なら{
vs. LabelMatchers = append ( vs. LabelMatchersセレクター...)
}
nil を返す
})
nil を返す
}

質問1については、上記記事の分析中に対応する解決策が見つかりましたが、当時は認識されていませんでした。その後のデバッグとステップバイステップのテスト中に初めてこのパターンが発見され、PromQL設計への理解も深まりました。

PromQLはネストされた関数型言語であり、基本的な算術演算と論理演算もサポートしています。そのため、数学の問題を解くように、段階的に解くことができます。さらに、PromQL式を構成する要素は固定されており、11種類の型があります(詳細は上記の記事を参照してください。ここでは繰り返しません)。各式は、これらの型のインスタンスの組み合わせに分解できます。

アラーム式の大部分は、2つのデータポイント間の算術演算または論理演算を伴います。これらはBinaryExpr型で、これは下位レベルの型で構成された新しい式型です。BinaryExpr型は直接識別できます。BinaryExpr型の場合は、プロットのために2つの式に分割し、そうでない場合はスキップします。

実践的な検証

テストコードを分割します。

 func TestSplitQuery ( t * テスト. T ) {
testExprs : = [] 文字列{
「node_cpu_usage > 0」
「rate(node_cpu_total{node=\"n1\"}[1m]) > rate(node_cpu_total{node=\"n2\"}[1m])」
「container_cpu_limit_usage / avg_over_time(container_cpu_limit_usage[1d] offset 1d) > 1.01」
「container_cpu_limit_usage > 0 かつ container_memory_limit_usage > 0」
「container_cpu_limit_usage > 0.5 かつ container_memory_limit_usage > 0.5 または container_cpu_limit_usage > 0.8」
`sum ( rate ( apiserver_request_duration_seconds_sum { サブリソース!= "log"verb ! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 m ])) インスタンスポッドなし
/
合計( レート( apiserver_request_duration_seconds_count { サブリソース!= "log"動詞! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 ])) なし( インスタンスポッド) `
}
_ の場合qs : = 範囲testExprs {
expr , err : = parser.ParseExpr ( string ( qs ) )
err != nil の場合{
t . 致命的( err )
}
parser.Inspect ( expr , func ( node parser.Node , _ [] parser.Node ) error {
スイッチT : = ノード.( タイプ) {
case * パーサー. VectorSelector :
t . Log ( "VectorSelector " , T . String ())
ケース* パーサー.BinaryExpr :
t . Log ( "BinaryExpr" , T . 文字列(), T . Op . 文字列(), T . LHS . 文字列(), T . RHS . 文字列())
nil を返す
}
nil を返す
})
}
}

結果:

 実行中のツール: / usr / local / go / bin / go test - timeout 30 s - run ^TestSplitQuery$ xxx / pkg / xxx - v
=== TestSplitQuery を実行する
xxx / pkg / xxx_test . go : 77 : BinaryExpr node_cpu_usage > 0 > node_cpu_usage 0
xxx / pkg / xxx_test .go : 75 : VectorSelector node_cpu_usage
xxx / pkg / xxx_test . go : 77 : BinaryExpr rate ( node_cpu_total { node = "n1" }[ 1 m ]) > rate ( node_cpu_total { node = "n2" }[ 1 m ]) > rate ( node_cpu_total { node = "n1" }[ 1 m ]) rate ( node_cpu_total { node = "n2" }[ 1 m ])
xxx / pkg / xxx_test . go : 75 : VectorSelector node_cpu_total { node = "n1" }
xxx / pkg / xxx_test . go : 75 : VectorSelector node_cpu_total { node = "n2" }
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage / avg_over_time ( container_cpu_limit_usage [ 1 d ] offset 1 d ) > 1.01 > container_cpu_limit_usage / avg_over_time ( container_cpu_limit_usage [ 1 d ] offset 1 d ) 1.01
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage / avg_over_time ( container_cpu_limit_usage [ 1 d ] offset 1 d ) / container_cpu_limit_usage avg_over_time ( container_cpu_limit_usage [ 1 d ] offset 1 d )
xxx / pkg / xxx_test .go : 75 : VectorSelector コンテナのCPU制限使用率
xxx / pkg / xxx_test . go : 75 : VectorSelector container_cpu_limit_usage オフセット1 d
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage > 0 かつcontainer_memory_limit_usage > 0 かつcontainer_cpu_limit_usage > 0 container_memory_limit_usage > 0
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage > 0 > container_cpu_limit_usage 0
xxx / pkg / xxx_test .go : 75 : VectorSelector コンテナのCPU制限使用率
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_memory_limit_usage > 0 > container_memory_limit_usage 0
xxx / pkg / xxx_test .go : 75 : VectorSelector コンテナのメモリ制限使用量
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage > 0.5 かつcontainer_memory_limit_usage > 0.5 またはcontainer_cpu_limit_usage > 0.8 またはcontainer_cpu_limit_usage > 0.5 かつcontainer_memory_limit_usage > 0.5 container_cpu_limit_usage > 0.8
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage > 0.5 かつcontainer_memory_limit_usage > 0.5 かつcontainer_cpu_limit_usage > 0.5 container_memory_limit_usage > 0.5
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage > 0.5 > container_cpu_limit_usage 0.5
xxx / pkg / xxx_test .go : 75 : VectorSelector コンテナのCPU制限使用率
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_memory_limit_usage > 0.5 > container_memory_limit_usage 0.5
xxx / pkg / xxx_test .go : 75 : VectorSelector コンテナのメモリ制限使用量
xxx / pkg / xxx_test . go : 77 : BinaryExpr container_cpu_limit_usage > 0.8 > container_cpu_limit_usage 0.8
xxx / pkg / xxx_test .go : 75 : VectorSelector コンテナのCPU制限使用率
xxx / pkg / xxx_test . go : 77 : BinaryExpr sum without ( instance , pod ) ( rate ( apiserver_request_duration_seconds_sum { subresource != "log" , verb ! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 m ])) / sum without ( instance , pod ) ( rate ( apiserver_request_duration_seconds_count { subresource != "log" , verb ! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 m ])) / sum without ( instance , pod ) ( rate ( apiserver_request_duration_seconds_sum { subresource != "log" , verb ! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 ])) インスタンスポッドなしの合計( レート( apiserver_request_duration_seconds_count { サブリソース!= "log"動詞! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }[ 5 ]))
xxx / pkg / xxx_test . go : 75 : VectorSelector apiserver_request_duration_seconds_sum { サブリソース!= "log"動詞! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }
xxx / pkg / xxx_test . go : 75 : VectorSelector apiserver_request_duration_seconds_count { サブリソース!= "log"動詞! ~ "LIST|WATCH|WATCHLIST|DELETECOLLECTION|PROXY|CONNECT" }
--- 合格: TestSplitQuery ( 0.00 )
合格
OK xxx / pkg / xxx 0.025

要約

この記事では、アラーム情報にセグメント化されたアラーム固有のメトリクストレンドチャートを追加し、アラームの原因特定を容易にする方法について主に考察します。ここでは主に、アラーム表現をセグメント化する方法と、これらの表現に新しいラベルマッチャーを追加する方法という2つの問題を取り上げます。この記事では分析をさらに深掘りし、アラーム表現をセグメント化する方法に関する詳細な分析とコード例を示します。これら2つのアプローチを組み合わせることで、要件を完全に達成できます。