マイクロサービスの障害で胃を痛めないための「シン・オブザーバビリティ基盤」をOpenTelemetryで作るCloud Nativeチートシート(25)

Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、トレース、ログ、メトリクスを関連付け、Grafana、Promtail、Prometheusを利用した横断的なオブザーバビリティ基盤を紹介します。

» 2023年03月09日 05時00分 公開
[逆井啓佑, 岡本隆史, 正野勇嗣株式会社NTTデータ]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

勘や経験に頼るマイクロサービスのトラブル調査で胃を痛めないために

 Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。前回は、OpenTelemetryやObservabilityの概要について紹介し、簡単なサンプルアプリケーションを使った分散トレーシングの方法を紹介しました。トレースを収集、可視化することによって、マイクロサービスをまたがるリクエストの追跡が容易になりました。

 しかし、分散トレーシングだけではOpenTelemetryの真の力を発揮できません。トレースによって「どの処理に時間がかかっているのか」「どの処理にエラーが発生しているのか」を知ることはできても、その原因を特定するには不十分なケースは多々あります。そのような場合、問題があるトレースやスパンに対応するログやメトリクスを調べることで原因の特定に役立つことがありますが、異なるテレメトリーを関連付けながら調査するのは容易ではありません。

 OpenTelemetryを用いることで、これらのテレメトリーを関連付けることができます(下図の「OpenTelemetry Collection」)。それによって、問題のあるトレースに対応するログやメトリクスを調べることができ、トラブルシューティングを効率化できます。

 今回は、OpenTelemetryやオープンソースソフトウェア(OSS)のモニタリングツールを用いてテレメトリーを関連付けることで、Grafanaダッシュボード上でログ、トレース、メトリクス間を行き来できるような三位一体の、いわば「シン・オブザーバビリティ基盤」を紹介します。

テレメトリーを関連付けることのメリット

 前回紹介したOpenTelemetry Collection(下図)では、テレメトリーを横串で探索できる情報(例えば、テレメトリー取得時間やクラスタ、Pod、コンテナなどのリソース情報、トレースIDやスパンIDなどのトレース情報)を付与します。

(再掲)

 このようにログ、トレース、メトリクスといったテレメトリーを“関連付ける”ことによって、さまざまな恩恵、特にトラブルを生じた際の解析性向上に寄与できます。下記はトラブルシューティングの一例です。

  • エラーログから関連したトレースを見ることで、一連のリクエスト処理を追うことができ、エラーログ前後のイベントを把握する
  • レイテンシの大きい異常メトリクスから関連したトレースを見ることで、「マイクロサービスのどの箇所で障害が起きているか」を確認する

 テレメトリー同士が関連していない場合、ログコンソールやダッシュボードを用いながら、勘や経験を頼りにテレメトリー間を関連付けることもしばしばあることでしょう。しかし一刻を争う有事の際に、原因特定までのリードタイムは最小化できることが望ましいです。

 テレメトリー間を関連付け、テレメトリーから対応するテレメトリーを即座に特定できることは、上記のような場合大きなアドバンテージになります。特に動的にリソースが変化するクラウドネイティブ環境において、テレメトリー間の関連性を保ち続ける(見失わない)ことが非常に重要です。

テレメトリーの関連付け

 各テレメトリー間を関連付けるためにはどのようにすればいいのでしょうか? OpenTelemetryのコンセプトに触れながら説明します。

テレメトリーの“関連付け”――OpenTelemetryのコンセプト

 OpenTelemetryでは各テレメトリーに以下のような情報を付与することで、各種オブザーバビリティーデータを関連付けます。

  • 各テレメトリーに付与されるタイムスタンプによる関連付け
  • Resource(OpenTelemetryにより定義されるテレメトリーの標準的な属性値)による関連付け
  • トレースIDやスパンIDによる関連付け

 繰り返しますが、テレメトリー間の関連付けは、各テレメトリーのフィルタリングやクエリといった分析機能の基盤となります。タイムスタンプやリソース情報を用いて、特定時間の特定コンポーネント(例えば、KubernetesのnamespaceやPod、コンテナ単位)のテレメトリーを横串で調査することが可能になります。

 トレースIDやスパンIDによる関連付けは、一連のリクエストの過程で関係のあるテレメトリーを抽出できるので非常に重要です。特にマイクロサービスアーキテクチャではサービス間のつながりが複雑化し、とあるサービスでの影響が離れたサービスで検知されるような場合が多くあります。

 このような場合に、エラーログからログに付与されたトレースIDを介して、トレース全体を調査するといったトラブルシューティングの流れは有効的です。

 本稿では、トラブルシューティング時に大きな効果をもたらす「トレースIDやスパンIDによる関連付け」に着目し、テレメトリーにトレースIDやスパンIDを付与することで関連付け、「Grafana」などのOSSツールを使った実践的な分析基盤を構築します。

本稿で構築するアーキテクチャの紹介

 本稿で構築するOpenTelemetryを用いたテレメトリーを関連付けて収集するためのアーキテクチャは下図のようになります。

 今回扱うテレメトリーの収集方法は次の通りです。

  • ログ:コンテナから出力したログは、ログフォワーダーの「Promtail」で収集し、「Grafana Loki」に送信する
  • トレース:アプリケーションのSDKから送信したトレースは、「OpenTelemetry Collector」を介して「Grafana Tempo」に送信する
  • メトリクス:OpenTelemetry Collector内でトレース情報から作成(詳しくは後述)し、「Prometheus」に送信する

 各テレメトリーの関連付けの方法を整理します。

テレメトリー 関連付け バックエンド
ログ ログフィールドにトレースID、スパンIDを付与 Grafana Loki
トレース トレースID、スパンIDを保持 Grafana Tempo
メトリクス 「Exemplar」(後述)を用いてトレースIDを付与 Prometheus

 それぞれ関連付いたテレメトリーをモニタリングバックエンドツールに送信し、Grafanaダッシュボード上で可視化します。

ログとトレースの関連付け

 ここでは、ログにトレースID、スパンIDを付与し、トレースとログを関連付ける方法を説明します。

OpenTelemetryにおけるログの収集

 ログにトレースID、スパンIDを付与する方法は、OpenTelemetryにおけるログの収集方法によって異なります。まず、ログの収集方法からです。

 「OpenTelemetryの仕様」では下図の2つの方法を紹介しています。

  • 【1】OpenTelemetryのログライブラリを用いて、アプリケーションからOpenTelemetry Collectorにログを送信。OpenTelemetry Collectorを介してログバックエンドにログを送信する
  • 【2】ファイルやロギングツールを経由して、OpenTelemetry Collectorを介してログバックエンド(【2】-1)に、あるいは、OpenTelemetry Collectorを通さず直接ログバックエンド(【2】-2)にログを送信する

 アーキテクチャ的には、分散トレーシングの構成同様、【1】の方法がシンプルですが、現状OpenTelemetryのログライブラリの実装は過渡期であり、言語(JavaやPythonなど)によって「experimental」ステータスで提供されています。

 本稿では、現行のスタンダードなプラクティス、【2】-2の方式を用いて、ログとトレースの関連付けを紹介します。

ログへのトレースID、スパンIDの付与と、ログの収集/分析手法

 ここでは、上記【2】-2の方式を採用し、ログフォワーダーとしてPromtailを、ログバックエンドとしてGrafana Lokiを用います。

 後述する構築手順でも述べますが、Helmチャート「loki-stack」を用いることで、Grafanaダッシュボード、Promtail、Grafana Lokiを含めたツールセットを簡単にデプロイできます。

 本稿で紹介する方法は、次の手順です。

  1. アプリケーションログにトレースID、スパンIDに含めファイルに出力する
  2. Promtailでログを収集し、トレースID、スパンIDを抽出、ラベル化してGrafana Lokiに送信する
  3. GrafanaでGrafana Loki(ログバックエンド)、Grafana Tempo(トレースバックエンド)をデータソースとして、トレースID、スパンIDを指定してログとトレースを関連付ける
  4. Grafanaのログ検索結果から、トレースIDを介して、当該ログに関連するトレースに遷移する

 Grafanaのダッシュボード上での上記ステップの遷移は後述します。

コラム OpenTelemetryのログ仕様やステータスについて

 今回は、図中の【2】-2の方式を利用しましたが、全てのテレメトリーをOpenTelemetry Collectorに集約して処理する【1】の方式がOpenTelemetryでは推奨されています。しかし、2023年2月の原稿執筆時点ではOpenTelemetryのログ仕様は過渡期で、下表のようなステータスです。

概要 ステータス
API draft
SDK draft
Protocol stable

 ログデータモデルの仕様などを定めるProtocolのみstableで、APIとSDKはdraftです。従ってログインストルメンテーション用のAPI/SDKも、一部言語(Java、Pythonなど)のみexperimentalステータスで提供されている状況です。そのため、【1】の方式は時期尚早といえるでしょう。

 OpenTelemetryのログデータモデルは下表の通りです。トレースIDやスパンIDのフィールドも用意されています。

Field Name Description
Timestamp イベントが発生した時刻
ObservedTimestamp イベントが収集された時刻(Timestampより後の時刻)
TraceId トレースID
SpanId スパンID
TraceFlags W3Cで定義されるトレースフラグ
SeverityText ログレベル
SeverityNumber ログレベルを数字(優先度)で表したもの
Body ログメッセージ
Resource ログのソースを記述。Resourceで定義
InstrumentationScope ログを生成したスコープを定義
Attributes イベントに関するメタデータを定義

 また【2】-1の方式は、OpenTelemetry Collectorのコンポーネント「Filelog Receiver」「Fluent Forward Receiver」を用います。いずれもbetaステータスの段階です。

 上記のような理由で、本稿では安定して利用可能な【2】-2の方式を用いています。


アプリケーションのログ出力方式

 ここでは、「どのようにアプリケーションログに対してトレースID、スパンIDを付与するか」を紹介します。

 Go言語のサンプルアプリケーションで、トレースIDとスパンIDを構造化ログのフィールドに追加して標準出力します。トレース情報はコンテキストから取得します。下記のサンプルコードでスパン生成時に用いているtracerオブジェクトの設定方法などは前回記事を参照してください。

// スパンの生成とログ出力を行う関数
func LoggerAndCreateSpan(c *gin.Context, msg string) trace.Span {
    // スパンを生成する
    _, span := tracer.Start(c.Request.Context(), msg)
    // トレースIDとスパンIDを抽出する
    SpanId := span.SpanContext().SpanID().String()
    TraceId := span.SpanContext().TraceID().String()
 
    // ログライブラリの設定(本サンプルアプリでは、Go言語のログライブラリ「Zap」を利用)
    logger, err := zap.NewProduction()
    if err != nil {
        log.Fatal(err)
    }
 
    defer logger.Sync()
    // 出力する構造化ログの設定
    logger.Info(
        // ログメッセージ
        zap.String("Body", msg),
        // スパンIDの設定
        zap.ByteString("SpanId", []byte(span.SpanContext().SpanID().String())),
        // トレースIDの設定
        zap.ByteString("TraceId", []byte(span.SpanContext().TraceID().String())),
    )
    return span
}
 
func getSignup(c *gin.Context) {
    // スパン生成とログ出力
    defer LoggerAndCreateSpan(c, "ユーザー登録画面の取得").End()
    generateHTML(c, nil, "signup", "layout", "signup", "public_navbar", "footer")
}

 このようにログを設定することで、以下のようにログフィールドにトレースIDとスパンIDを設定してログを出力でき、ログとトレースを関連付けることができます。ログとしては次のようなJSON形式で出力します。

{
    # Zap で自動付与されるログフィールド
    "level": "info",
    "ts": 1676215284.5673957,
    "caller": "controllers/utils.go:102",
    # 手動で追加したログフィールド
    "Body": "ユーザー登録画面の取得",
    "SpanId": "96c9fe5a2b31f709",
    "TraceId": "3151c9f03ca6831bb8964dd78a53c2fb"
}

 「【1】ログライブラリを用いて、アプリケーションからOpenTelemetry Collectorにログ送信する」場合は、コラムで述べたOpenTelemetryで定められたデータモデルのフィールドにトレースIDとスパンIDがあるので、ログライブラリを用いてアプリケーション側でIDを設定します(言語によってはログライブラリ側で、自動的にトレースID、スパンIDをログフィールドにIDを付与してくれます)。

 本稿で検証する「【2】ファイルやログフォワードツールを経由して、OpenTelemetry Collectorに送信する」場合でも、上記のようにログ出力の際にIDを設定する必要があり、今回は手動でフィールドを追加しています。

メトリクスとトレースの関連付け

 ここまではログとトレースを関連付ける方法を見てきましたが、ここからは同様にメトリクスにトレースIDを付与してトレースとメトリクスを関連付ける方法を紹介します。

 ただ、全てのメトリクスをトレースなどのテレメトリーを関連付けることは難しく、またCPU使用率、メモリやディスク使用量といった特定のリクエストによらないメトリクスを特定のトレースのトレースIDで対応付けることは困難です。

 そこで本稿では、OpenTelemetry Collectorで簡単に生成でき、トレースIDと関連付けることができる「スパンメトリクス」を使って、メトリクスとトレースを関連付ける方法を紹介します。

スパンメトリクスについて

 スパンメトリクスは分散トレーシングのスパンにおける情報を提供するメトリクスです。OpenTelemetry Collectorで収集したトレース情報から生成され、トレースごとに下記のメトリクスが作られます。

メトリクス 種類 内容
calls_total Counter トレースに含まれるスパンの数
latency_sum Summary トレースに含まれるスパンのレイテンシ総和(=トレースのレイテンシ)
latency_bucket Histogram トレースのレイテンシ分布

 スパンメトリクスは、OpenTelemetry Collectorのコンポーネント「Span Metrics Processor」を用いることで、特にアプリケーション側にメトリクス収集のためのインストルメンテーションをすることなくアプリケーションメトリクスを収集できます。

 「latency_bucket」は収集した各トレースのレイテンシにおけるヒストグラムです。レイテンシ分布における平均値に最も近いトレースを「Exemplar」(エグザンプラー)といい、そのヒストグラムを代表するトレースとします。

 スパンメトリクスのlatency_bucketにExemplarのトレースIDをひも付けて付与することで、トレースと関連付けることができます。

 ExemplarのトレースIDのスパンメトリクスのひも付けは、OpenTelemetry CollectorのSpan Metrics Processorが行います。

 Exemplarのトレース情報が追加されたlatency_bucketは次のような形式です。

traces_spanmetrics_latency_bucket{“le”=0.05} 1
traces_spanmetrics_latency_bucket{“le”=0.5} 3
traces_spanmetrics_latency_bucket{“le”=1} 4  # trace_id="043cd631811e373e4180a678c06b128e",span_id="cd122e457d2ca5b0"
traces_spanmetrics_latency_bucket{“le”=INF} 5

 bucketは、各メトリクスがヒストグラムの各区間に「le=xxx」で指定した値以下のレイテンシのトレースが何個あるかを示しています(詳細は連載第15回参照)。平均値に一番近いトレースの値がExemplarとして、ヒストグラムにその値が含まれる区間("le"=1の区間)に追加されます。

スパンメトリクスへのトレースID、スパンIDの付与と、ログの収集/分析手法

 ここでは下記手順で、OpenTelemetry Collectorでスパンメトリクスを作成、メトリクスバックエンドとしてPrometheusを用います。

  1. OpenTelemetry Collectorの「OTLP Receiver」でトレース情報を収集する
  2. OpenTelemetry CollectorのSpan Metrics Processorでスパンメトリクス作成と、トレースIDの関連付け
  3. OpenTelemetry Collectorの「Prometheus Remote Write Exporter」でPrometheusにスパンメトリクスの送信
  4. Grafanaのメトリクス検索結果から、トレースIDを介して当該メトリクスに関連するトレースに遷移する

 Grafanaのダッシュボード上における上記ステップの遷移は後述します。

各種コンポーネントのデプロイ

 テレメトリーをひも付ける理論が分かったところで基盤を構築します。本稿では前回構築した環境は利用せずに、一から環境を構築します。

デプロイの全体像

 次の流れでコンポーネントをデプロイします。

  1. 作業用namespaceの作成
  2. OpenTelemetry Collectorのデプロイ
  3. Grafana Loki、Grafana、Promtailのデプロイ
  4. Grafana Tempoのデプロイ
  5. Prometheusのデプロイ
  6. サンプルアプリケーションのデプロイ

 テレメトリーを関連付けるように設定してデプロイするコンポーネントもあるので、適宜解説します。

作業用namespaceの作成

 Observabilityツールをインストールする「observability namespace」とサンプルアプリケーションをデプロイする「todo namespace」を作成します。前回記事に沿って、環境を構築している場合は、前回作成したnamespaceを削除してください。

$ kubectl create ns observability
$ kubectl create ns todo

OpenTelemetry Collectorのデプロイ

 最初に、OpenTelemetry Collectorデプロイに必要な、cert-managerとOpenTelemetry Operatorをデプロイします。

# cert-managerのデプロイ
$ helm repo add jetstack https://charts.jetstack.io
$ helm repo update
$ helm install \
  cert-manager jetstack/cert-manager \
  --namespace observability \
  --version v1.9.1 \
  --set installCRDs=true \
  --wait
 
# OpenTelemetry Operatorデプロイ
$ helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
$ helm repo update
$ helm install \
  otel-operator open-telemetry/opentelemetry-operator \
  --namespace observability \
  --version 0.20.4 \
  --set installCRDs=true \
  --wait
 
# cert-manager と OpenTelemetry Operator 確認
$ kubectl get po -n observability | grep manager
cert-manager-7b8d75c477-vdzsw                                1/1     Running   0          6h49m
cert-manager-cainjector-6cd8d7f84b-9vql7                     1/1     Running   0          6h49m
cert-manager-webhook-66fc85bd57-hrmz6                        1/1     Running   0          6h49m
opentelemetry-operator-controller-manager-7944958b76-6sf6j   2/2     Running   0          6h48m
# OpenTelemetry Collectorデプロイ
kubectl -n observability apply -f https://raw.githubusercontent.com/cloudnativecheetsheet/opentelemetry/main/02/otel-collector-config.yaml
# OpenTelemetry Collector確認
$ kubectl get po -n observability | grep collector
sample-collector-74dcc5ff88-7pl7b                            1/1     Running   0          16m

 OpenTelemetry Collectorのconfigファイルは「otel-collector-config.yaml」です。下表のコンポーネントを有効にしています。

テレメトリー Receiver Processor Exporter 設定
トレース otlp spanmetricsprocessor otlp Grafana Tempoにトレース送信
メトリクス --- --- prometheusremotewrite Prometheusにメトリクス送信(RemoteWrite)

 Receiverは、OpenTelemetry Collectorでテレメトリーを受け取るコンポーネントです。Processorは、ラベル付与やフィルタリング、サンプリングなどテレメトリーに対して処理するコンポーネントです。Exporterは、テレメトリーをバックエンドに送信するコンポーネントです。

 今回利用しているスパンメトリクスは、トレース情報からProcessorによって生成されるので、メトリクスを受け取るReceiverは何も設定されませんが、Processorによって生成されたメトリクスをバックエンド(Prometheus)に送る必要があるのでExporterはPrometheus Remote Write Exporterを利用しています。

 なお、Logging Exporterを利用して、各テレメトリーをOpenTelemetry Collectorのログとして出力できます。デバッグやOpenTelemetry Collectorの挙動確認時に便利です。ただし、大量のログが出力されるの運用時には留意が必要です。

コラム  Prometheusをプッシュ型で使う「Prometheus Remote Write」

 Prometheusのメトリクス収集方式としてプル型とプッシュ型があり、本稿では、プッシュ型でメトリクスをPrometheusに送信できる「Prometheus Remote Write」機能を利用します。

 プル型のexporterはPrometheus Exporterとして用意されています。

 アプリケーションからOTLP(OpenTelemetry Protocol)で送信されたメトリクスや、OpenTelemetry CollectorのProcessorで生成されたメトリクスは、OpenTelemetry CollectorのRemoteWrite ExporterからPrometheusサーバに送信されます。


Grafana Loki、Grafana、Promtailのデプロイ

 Grafana Loki、Grafana、Promtailをデプロイします。これらは前述のloki-stackに同梱されていて、一緒にデプロイできます。下記の手順でデプロイします。

# Grafana Loki/Grafana/Promtailデプロイ
$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
 
$ helm upgrade --install loki --namespace=observability grafana/loki-stack \
    --values=https://raw.githubusercontent.com/cloudnativecheetsheet/opentelemetry/main/02/loki-stack-values.yaml
 
# Grafana Loki/Grafana/Promtail確認
$ kubectl get po -n observability | grep loki
loki-0                                                       1/1     Running   0          3m35s
loki-grafana-9d68b6b77-jsrrg                                 2/2     Running   0          3m35s
loki-promtail-bvf5g                                          1/1     Running   0          3m35s
loki-promtail-lclrc                                          1/1     Running   0          3m35s
loki-promtail-vn2fr                                          1/1     Running   0          3m35s

 各コンポーネントの設定は「loki-stack-values.yaml」にあります。

 Promtailの設定は、デフォルトの設定から、下記のように変更しています。

promtail:
  enabled: true
  config:
    lokiAddress: http://loki.observability.svc.cluster.local:3100/loki/api/v1/push
    snippets:
      pipelineStages:
      - docker: {}
      - json:
          expressions:
            trace_id: trace_id
            span_id: span_id
      - labels:
          trace_id:
          span_id:

 「promtail.config.lokiAddresss」にGrafana Lokiの接続先を指定します。「promtail.config.snippets」に取り込んだログの「trace_id」「span_id」フィールドを、Lokiのラベルとして使用することを宣言しています。なお上記の設定は、今回のサンプルの場合です。ラベルの設定はログの出力形式によって異なるので注意してください。

コラム Filelog Receiverによるログ収集

 本稿では、Promtailを用いてログ収集を行っていますが、OpenTelemetry CollectorのFilelog Receiverを利用することも可能です。

 Filelog Receiverを用いることでPromtail同様にログファイルを取り込むことができます。収集したログはOpenTelemetry Collectorのパイプラインでログへの追加処理(属性情報の追加やフィルタリングなど)、エクスポーターを経由してログコレクターに送信されます。

 KubernetesでFilelog Receiverを利用する場合、OpenTelemetry Collector Helmチャートを利用すると便利です。Podのログを収集する設定を簡易化し、ログにKubernetesの情報(Pod名、namespace名、クラスタ名など)のラベルが自動的に付与されます。

 OpenTelemetry Colletor Helmチャートには、「DaemonSet」「Deployments」「StatefulSet」の3つのモードがあり、各ノードのログファイル取り込む場合、DaemonSetモードでデプロイする必要があります。また、ログコレクタの設定や属性の取得の設定などが必要ですが、詳細は、「Configuration for Kubernetes container logs」「Configuration for Kubernetes attributes processor」をご覧ください。


Grafana Tempoのデプロイ

 Grafana Tempoをデプロイします。

# Grafana Tempoのデプロイ
$ helm install \
  tempo-distributed grafana/tempo-distributed \
  -n observability \
  --version 0.26.8 \
  --set traces.otlp.grpc.enabled=true \
  --set search.enabled=true \
  --wait
 
# Grafana Tempo確認
$ kubectl get po -n observability | grep tempo
tempo-distributed-compactor-7d7b475d8f-gsj4g                 1/1     Running   0          92s
tempo-distributed-distributor-7c9bcc855c-s76nb               1/1     Running   0          92s
tempo-distributed-ingester-0                                 1/1     Running   0          92s
tempo-distributed-ingester-1                                 1/1     Running   0          92s
tempo-distributed-ingester-2                                 1/1     Running   0          91s
tempo-distributed-memcached-0                                1/1     Running   0          92s
tempo-distributed-querier-6599c84b5d-lggdp                   1/1     Running   0          92s
tempo-distributed-query-frontend-56b5f7fbc8-dtkwp            1/1     Running   0          92s

 Grafana Tempoは前回記事と同様の構成でデプロイしました。「traces.otlp.grpc.enabled」を有効化してOTLPポートを開いています(デフォルトでは無効)。「search.enabled」はトレース情報のサーチ機能です。取得したトレースを一覧表示できて便利なので、有効化してトレース確認時に使用します。

Prometheusのデプロイ

 Prometheusをデプロイします。

# Prometheusのデプロイ
$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
 
$ helm install kube-prometheus-stack \
prometheus-community/kube-prometheus-stack \
-n observability \
--values=https://raw.githubusercontent.com/cloudnativecheetsheet/opentelemetry/main/02/kube-prometheus-stack-values.yaml \
--wait
 
# Prometheus確認
$ kubectl get po -w -n observability | grep prometheus
kube-prometheus-stack-operator-68689b4554-hzvk7              1/1     Running   0          47s
prometheus-kube-prometheus-stack-prometheus-0                2/2     Running   0          45s

 Helmチャートは「kube-prometheus-stack」を用いました。幾つかコンポーネントが同梱されていますが、ここではPrometheusのみを有効化して他のツールは「enabled=false」としています。

 また「prometheus.prometheusSpec.enableFeatures」に「remote-write-receiver」と「exemplar-storage」を設定しています(デフォルトでは非設定)。この設定によって、PrometheusへのRemote Writeと、PrometheusでトレースIDが付与されたメトリクスを収集できるようになります。

サンプルアプリケーションのデプロイ

 最後に、OpenTelemetryでインストルメンテーションした、Go言語のサンプルアプリケーション(Todoアプリ)をデプロイします。ビルド済みのイメージを用意しているので、下記のマニフェストをデプロイするだけで使えます。

# サンプルアプリケーションデプロイ
$ kubectl -n todo apply -f https://raw.githubusercontent.com/cloudnativecheetsheet/opentelemetry/main/02/application.yaml
 
# サンプルアプリケーション確認
$ kubectl get po -n todo
NAME                         READY   STATUS    RESTARTS      AGE
postgresql-79969bf55-h5wtb   1/1     Running   0             44m
todoapi-7dc6cb8c8c-mvwzq     1/1     Running   0             44m
todobff-69f559468f-cxhg2     1/1     Running   0             44m
userapi-6db546df8d-vpw4j     1/1     Running   0             44m

 なお、サンプルアプリケーションのソースコードはGitHubのリポジトリに公開しているので、興味がある方はご覧ください。

Grafana上での各テレメトリーのトレースIDの関連付け

 最後にサンプルアプリケーションを動かしながら、Grafana上で“関連付けられた”テレメトリーを確認します。

 Grafanaダッシュボード上でテレメトリー間を遷移するために、各テレメトリーに付与しているトレースIDを関連付ける必要があります。ログのトレースIDやメトリクス(スパンメトリクス)のトレースIDと分散トレーシングのトレースIDと関連付けることで、ダッシュボード上でトレースIDを介して関連付いたテレメトリーを簡単に確認できます。

 トレースIDの関連付けのイメージは下図の通りです。

ログ→トレースの関連付け方法

 ログとトレースを関連付けます。Grafanaへのログイン方法は前回記事で紹介した方法と同じです。

 最初にGrafana Tempoのデータソースを追加し、設定します。「Data sources」で「Tempo」を選択します。

 次に、Tempoを設定します。

 【1】のエンドポイント指定では、「http://tempo-distributed-query-frontend:3100」を指定します。【2】の「Save & test」を押して、「Data source is working」のチェックマークが表示されたら設定終了です。

 続いて、Lokiを設定します。

 【1】のエンドポイント指定では、「http://loki:3100」を指定し、【2】のDerived fieldsでログフィールドのtrace_idと、TempoのトレースIDをひも付けます。これによって、ログフィールド「"trace_id"」に設定されているトレースIDでTempoのトレースを関連付け、Grafanaダッシュボード上でテレメトリー間を遷移できます。

 【3】「Save & test」を押して、「Data source connected and labels found」のチェックマークが表示されたら設定終了です。「Explore」を押してログを探索してみましょう。

 ExploreではLokiに対してアドホックなクエリを投げることができ、ログ解析を簡易化できます。

 ここでは、【1】「{app="todobff"}」でサンプルアプリケーションのログを抽出しています。

 ログを見ると、アプリ内で付与した属性情報が載っていることが分かります。trace_idやspan_idも付与されています。trace_idの横に【3】「tempo」のボタンが用意されています。先ほど、Derived fieldsにtrace_idを設定したからです。こちらを押下するとログに関連付いたトレースIDのトレースが、右側のペインに表示されます。

 ログに付与されたトレースIDを介して、Grafanaダッシュボード上で容易にトレースに遷移できました。テレメトリーをトレースIDによって関連付けることによる恩恵です。

メトリクス→トレースの関連付け方法

 メトリクス(スパンメトリクス)とトレースを関連付けます。先ほどと同様に、Prometheusのデータソースを追加し、設定します。

  • 【1】エンドポイント指定は、http://kube-prometheus-stack-prometheus:9090」
  • 【2】でExemplarを設定し、Tempoと関連付けるためのトレースIDのラベルを指定する。今回は「trace_id」
  • 【3】「Save & test」を押して、「Data source is working」のチェックマークが表示されたら設定終了。「Explore」を押して、Prometheusでメトリクスを探索する

 ExploreでPrometheusに対してアドホッククエリを投げてみましょう。検索に使うメトリクスは次の通りです。

histogram_quantile(0.90,
sum(rate(latency_bucket[30s])) by (le)
)

 これによって、「単位時間当たりのトレースにおけるレイテンシの、90%が収まるレイテンシの値」が求まります。言い換えると、例えばビジネス目標が「リクエストの90%が200ミリ秒以内にレスポンスを返却する」といった場合、このメトリクスが200ミリ秒を超えたときにアラートを発するような形で使えるかもしれません。

 メトリクスを指定して、【2】クエリを実行するとメトリクスが表示されます。ひし形(◇)のプロットが、Exemplarが付与された(=トレースIDと関連付いた)メトリクスです。

 ひし形のプロットをマウスオーバーすると、Exemplarの情報が表示されます。このメトリクスを生成するのに使用されたトレース情報です。トレースIDやスパンIDなどがラベルと共に付与されています。トレースIDにはTempoへの動線が用意されており、「Query with Tempo-ITMedia」を押すと、先ほどのLokiのときと同様に右側に分散トレーシングのペインが表示されます。

 先ほどと同様のカラクリで、Exemplarのtrace_idに格納されたトレースIDでTempoをクエリしている形です。このようにGrafanaダッシュボード上で、メトリクスに付与されたトレースIDを介してトレースに遷移できました。

まとめ

 今回は、テレメトリーにトレースIDを付与することで“関連付け”し、それぞれのテレメトリーの間に意味を与えました。さらに、テレメトリーを関連付ける意義の一例として、Grafanaダッシュボード上でテレメトリー間の遷移、例えば「ログからトレース」「メトリクスからトレース」といったテレメトリーの探索やトラブルシューティングを容易にできることを紹介しました。

 複雑で動的に変化するクラウドネイティブ環境では、従来のモニタリングとは異なる視点や切り口でテレメトリーを追跡できる可観測性(Observability)が非常に重要です。本稿で紹介した「テレメトリー間の関連性を保ち続ける(見失わない)こと」はObservabilityにとって重要な要素の一つです。

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。