「OpenTelemetry」とは――「Observability」(可観測性:オブザーバビリティ)とテレメトリーの基礎知識:Cloud Nativeチートシート(24)

 テレメトリーの取得は、OpenTelemetryを利用しない場合(上記図左:Separate Collectionを参照)、ログはログファイルに出力し、「Grafana Loki」「ElasticSearch」などのログバックエンドに保存します。メトリクスはOSやプロセスのリソース利用量を取得して、「Prometheus」などのメトリクスバックエンドに保存します。トレースについても、「Jaeger」といったツールを使ってトレース情報を作成し、トレースバックエンドに保存するのが典型的な方法です。

 そうなると、テレメトリーごとにバラバラに実装しているだけで、各テレメトリーを関連付けることができません。例えば、トレース情報からレイテンシの大きいリクエストを見つけたとしても、そのトレースにひも付くログを確認することは困難になります。

 OpenTelemetryを用いると、OpenTelemetryで標準化された方法によって、ログとトレース、メトリクスを関連付けることができます(上記図右: OpenTelemetry Collectionを参照)(※注1)。

※注1:ただし、2023年1月の原稿執筆時点では、OpenTelemetryは完全には実装されておらず、現時点で実装する際のアーキテクチャは、上記の2つの図が混ざったアーキテクチャとなることにご留意ください。次回、OpenTelemetryやオープンソースソフトウェア(OSS)のモニタリングツールを使ったテレメトリーの関連付けについて、詳しく実践的に解説します。

 これらテレメトリーを取得可能にすることを「インストルメンテーション」といいます。OpenTelemetryによるSDKやライブラリを用いることで、取得と収集の方法を統一でき、OpenTelemetryでインストルメントされたアプリはさまざまなObservabilityツールで可視化できます。さらに、後述する自動インストルメンテーションによって、アプリのコードの変更なしにテレメトリーを取得できます。

 OpenTelemetryは、アプリに組み込むテレメトリーの取得と、「OpenTelemetry Collector」という、テレメトリーを収集、加工するコンポーネントに対する仕様策定、SDKやライブラリの提供を行っています。テレメトリーを保存、可視化するデータストアやダッシュボードなどのバックエンドはOpenTelemetryの対象外なので、OSSツールや商用製品を利用することになります。

 OpenTelemetryの特徴をまとめると下記のようになります。

  1. テレメトリー間の関連付けが(将来的に)可能になり、Observabilityの向上につながる
  2. テレメトリーの取得と収集の方法を統一でき、アプリ開発者はバックエンドのツールを意識することなく、さまざまなOSSツールや商用製品と組み合わせてテレメトリーを保存、可視化できる
  3. インストルメンテーションを簡単にするSDKやライブラリを提供。使用している言語、フレームワークがサポートされていれば、コードを変更することなくテレメトリーを取得できる(自動インストルメンテーション)

コラム 各言語と、テレメトリーごとの対応ステータス

 OpenTelemetryのインストルメンテーションは言語ごとにリポジトリが分かれており、各テレメトリーについては下表のようなステータスになっています(2023年1月段階)。最新のステータスは「Status」から確認できます。

 「Stable」ステータスは、厳密なテストが済んでおり、長期的な互換性が保証されます。「Experimental」「Alpha」「Beta」ステータスは実験段階を表し、互換性を損なう仕様変更やパフォーマンスの問題が発生する場合があるので、利用には注意が必要です。

言語 トレース メトリクス ログ
Java Stable Stable Experimental
Python Stable Stable Experimental
JavaScript Stable Stable Development
C#/.NET Stable Stable Mixed
C++ Stable Stable Experimental
Go言語 Stable Alpha Frozen
PHP Beta Beta Not yet implemented
Ruby Stable Not yet implemented Not yet implemented
Rust Stable Alpha Not yet implemented
Swift Stable Experimental In development
Erlang Stable Experimental Experimental
※ステータスは各プロジェクトの2023年1月段階の状態を転載したもの

 本稿の後半で、インストルメントしたアプリのソースコードを紹介しますが、手動でのインストルメンテーションは少々骨の折れる作業です。そういった場合、自動インストルメンテーションライブラリが役立ちます。

 OpenTelemetryでは、自動インストルメンテーションのツールも開発されています。ここではKubernetesを使っている場合に有効な自動インストルメンテーションツールについて触れます。詳しくは「OpenTelemetry auto-instrumentation injection」をご覧ください。ここで紹介されている自動インストルメンテーションのためのOperatorを用いることで、アプリPodにサイドカーコンテナがインジェクトされ、テレメトリーを自動で取得できます。現状、.NETとJava、Node.js、Pythonがサポートされています。

 他にもOpenTelemetry RegistryではOpenTelemetryのエコシステム(ライブラリやプラグイン、その他の便利なツールセット)がたくさんあるので、のぞいてみると新たな発見があるかもしれません。

 自動インストルメンテーションに関しては、言語やフレームワークなどが対応していれば便利ですが、お使いの言語もしくはフレームワークによっては仕組み上対応できないケースもあるので、そのような場合は、本稿で紹介する方法を参考に手動でインストルメントしてください。

OpenTelemetry Collector

 OpenTelemetry Collectorはバックエンドにテレメトリーを送信する際のプロキシのような役割を担い、テレメトリーデータの受信、処理、送信についてベンダー非依存な実装を提供します。OpenTelemetry Collectorをモニタリングサービスの前段に置く(下図)ことで、アプリ開発者はさまざまな恩恵を享受できます。

  1. モニタリングツールを変更する際は、OpenTelemetry Collectorからのテレメトリー送信先の設定変更のみで済む
  2. アプリに依存しない共通の処理をOpenTelemetry Collectorにオフロードできる(テレメトリー情報にプロジェクトやKubernetesクラスタの情報を付与するなど)
  3. OpenTelemetryのエコシステム(OpenTelemetry Collectorに関するツール群やノウハウ)を活用できる。例えば、後述するOpenTelemetry Collectorのコンポーネント「Processor」には多くのラインアップがあり、OpenTelemetry Collectorで集約したテレメトリー情報にフィルタリングやサンプリングなどを施せる

 OpenTelemetry Collectorは、「Receiver」「Processor」「Exporter」といったコンポーネントから構成されています。

  • Receiver:テレメトリーを受け取るコンポーネント。gRPCとHTTPのインタフェースが用意されている。Receiverのコンポーネント一覧はこちらのリポジトリにある
  • Processor:受け取ったテレメトリーを処理するコンポーネント。ラベル付与やフィルタリング、サンプリングなど多くのProcessorがある。Processorのコンポーネント一覧はこちらのリポジトリにある
  • Exporter(※注2):バックエンドのモニタリングサービスにテレメトリーを送信するコンポーネント。Exporterのコンポーネント一覧はこちらのリポジトリにある

※注2:ここで説明するExporterは、後述のサンプルコード内に出てくる「TraceExporter」(アプリからOpenTelemetry Collectorにトレース情報を送信するコンポーネント)とは別のコンポーネントなので、ご注意ください。

 OpenTelemetry Collectorは標準ディストリビューションとコントリビューションディストリビューションの2つがあります。上述のコンポーネント一覧リポジトリはコントリビューションのコンポーネントも含まれていて、Amazon Web Services(AWS)やGoogle Cloud Platform(GCP)といったベンダー製のコンポーネントや、OpenTelemetryが開発中のコンポーネント(非商用向け)も含まれています。

 標準ディストリビューションのコンポーネント一覧コントリビューションディストリビューションのコンポーネント一覧を見ると、含まれているコンポーネント数の違いが分かります。プロダクション環境では、イメージサイズの最適化などの理由で、必要なコンポーネントに構成を制限したカスタマイズ(リビルド)が推奨されています。

OpenTelemetryでテレメトリー(トレース)を収集、可視化してみよう

 ここからは、OpenTelemetryを使ってサンプルアプリをインストルメントします。今回は分散トレースに絞りますが、次回でログとメトリクスも織り交ぜて紹介します。

サンプルアプリのアーキテクチャ、概要

 本稿では、次のようなサンプルアプリを利用しながら、OpenTelemetryについて解説します。

 Go言語製のサンプルアプリ(マイクロサービス)にOpenTelemetryを使ってインストルメントし、OpenTelemetry Collectorにトレース情報を送信します。OpenTelemetry Collectorはトレーシングバックエンドの「Grafana Tempo」にトレース情報を送信し、ダッシュボード機能を持つ「Grafana」で可視化する構成です。

 サンプルアプリの「Service1」にリクエストを送ると、「Service2」と通信します。その処理中、各サービスからOpenTelemetry Collectorにテレメトリー(今回はトレース情報)が送信されます。最終的にGrafanaダッシュボード上でトレーススパン情報(詳細は後述)を確認します。

 それぞれのコンポーネントは「Helm」を使ってデプロイしています。検証に用いたそれぞれのChartのバージョンを記しておきます。

Package Chartのバージョン Helm Repository
opentelemetry-operator opentelemetry-operator-0.20.4 https://artifacthub.io/packages/helm/opentelemetry-helm/opentelemetry-operator
tempo-distributed tempo-distributed-0.26.8 https://artifacthub.io/packages/helm/grafana/tempo-distributed
grafana grafana-6.48.2 https://artifacthub.io/packages/helm/grafana/grafana
cert-manager cert-manager-v1.9.1 https://artifacthub.io/packages/search?org=cert-manager

OpenTelemetryの分散トレーシングについて

 分散トレーシングでは、サービス間のリクエストの流れや処理速度などを監視します。リクエスト時の一連の処理を表すトレースは、個々の処理を表すスパンから構成されており、その幅は処理時間を表しています。

 トレース情報(他のテレメトリーについても同様)は分散システム間でデータを関連付ける仕組み「Context Propagation」(コンテキスト伝搬)によってサービス間を流れていきます。

コラム OpenTelemetry第4のテレメトリー「Baggage」

 上記で説明したログ、トレース、メトリクスの他に、OpenTelemetryでは、第4のシグナルとして、「Baggage」が定義されています(OpenTelemetryサイトの「Signals」を参照)。

 Baggageは、OpenTelemetryのスパン間で渡されるコンテキスト情報で、スパン間で情報を共有したい場合に便利です。例えば、顧客からクレームが来たときに、その顧客のアカウントIDを元に、顧客が操作したリクエストのログやトレースなどのテレメトリー情報を確認したいことがあるでしょう。また、サービスの特定のリクエストに問題があったときに、影響がある顧客を特定するためにアカウントIDを確認したい場合もあると思います。

 このような、アカウントID、ユーザーID、商品IDなどのIDをBaggageに設定することによって、これらと、トレースや他のテレメトリーとの間に意味のあるIDの対応付けができます。OpenTelemetryでは「シグナル」として定義されていますが、テレメトリーに付与されるメタデータのようなものと考えた方が分かりやすいでしょう。

 Baggageは便利なテレメトリーですが、HTTPヘッダに格納されてサービス間で受け渡されるので、機密情報の受け渡しに利用しないようにしてください。

 本稿では簡単なサンプルを利用しているので、Baggageは利用していませんが、興味のある方は調べてみてください。

サンプルアプリのソースコード

 サンプルアプリのソースコードは次の通りです。

package main
 
import (
    "context"
    "fmt"
    "log"
    "os"
    "os/signal"
    "time"
 
    "github.com/gin-gonic/gin"
    "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/propagation"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
 
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
 
// トレースをOpenTelemetry Collectorに送信するトレースエクスポーターの初期化と、
// トレーサーオブジェクト(スパンを生成)を作成するトレーサープロバイダーを設定する
func initProvider() (func(context.Context) error, error) {
    ctx := context.Background()
 
    // トレーサープロバイダーでトレースに付与する属性情報(リソース)を定義する
    res, err := resource.New(ctx,
        resource.WithAttributes(
            // service1のトレースプロバイダーに設定するために"service1"とサービス名(ServiceName)属性情報を付与する
            semconv.ServiceNameKey.String("service1"), 
        ),
    )
    if err != nil {
        return nil, fmt.Errorf("failed to create resource: %w", err)
    }
 
    // トレースの送信先(エクスポート先)の設定
    // 今回はKubernetesのクラスタ内通信で、OpenTelemetry CollectorにgRPCでトレースを送信する設定を記述する
    conn, err := grpc.DialContext(ctx,
        "sample-collector.observability.svc.cluster.local:4317",
        grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
 
    if err != nil {
        return nil, fmt.Errorf("failed to create gRPC connection to collector: %w", err)
    }
 
    // トレースエクスポーターの設定
    traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
    if err != nil {
        return nil, fmt.Errorf("failed to create trace exporter: %w", err)
    }
 
    // スパンProcessor(スパンの開始/終了時に呼び出される処理)としてバッチProcessorを作成する
    // バッチProcessorによって、テレメトリーがある程度たまってからまとめてOpenTelemetry Collectorに送信する
    bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
 
    // トレーサープロバイダーの設定
    // 設定にはサンプル方式や、上記で設定したリソーススパンProcessorを付与する
    var tracerProvider *sdktrace.TracerProvider
    tracerProvider = sdktrace.NewTracerProvider(
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
        sdktrace.WithResource(res),
        sdktrace.WithSpanProcessor(bsp),
    )
    otel.SetTracerProvider(tracerProvider)
    otel.SetTextMapPropagator(propagation.TraceContext{})
 
    return tracerProvider.Shutdown, nil
}
 
var tracer = otel.Tracer("service1")
 
// main 関数
func main() {
    //--- ここから、opentelemetry-goでトレーシングするための定型コード
 
    // シグナルハンドラーを設定する
    // プロセスの強制終了時にハンドラーを適切に設定しないと、強制終了によってテレメトリーが失われるので、注意
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()
 
    shutdown, err := initProvider() // 上部で定義したOpenTelemetryのトレーシングコンポーネント初期化関数
    if err != nil {
        log.Fatal(err)
    }
    // main関数終了時にトレースプロバイダーを終了するdeferを定義する
    defer func() {
        if err := shutdown(ctx); err != nil {
            log.Fatal("failed to shutdown TracerProvider: %w", err)
        }
    }()
    //--- ここまで、opentelemetry-goでトレーシングするための定型コード
 
    r := gin.New()
    r.Use(otelgin.Middleware("service1"))
    r.GET("/service", service1_sample1)
 
    r.Run(":8080")
}
 
// ここからがサービスを実行するロジックコード
func service1_sample1(c *gin.Context) {
    // tracer.Startでスパンを生成する
    _, span := tracer.Start(c.Request.Context(), "service1 の sample1 関数を実行")
    // service1_sample1関数終了時にスパンを終了する
    defer span.End()
 
    // 他サービスの呼び出し例
    // OpenTelemetryのライブラリを用いて他サービスへのHTTPリクエストを実装し、コンテキストの持つトレース情報を呼び出し先のサービスに引き継ぐ
    rsp, err := otelhttp.Get(
        c.Request.Context(),
        "http://service2.observability.svc.cluster.local:8080/service",
    )
    if err != nil {
        log.Println(err)
        return
    }
    defer rsp.Body.Close()
    // 他関数の呼び出し例
    // 現在のコンテキストを呼び出し先の関数に渡してトレース情報を引き継ぐ
    service1_sample2(c)
}
 
func service1_sample2(c *gin.Context) {
    // スパンの生成
    _, span := tracer.Start(c.Request.Context(), "service1 の sample2 関数を実行")
    defer span.End()
    time.Sleep(time.Second * 2)
}

 プログラム全体のソースコードはこちらのリポジトリを参照してください。また、サービス1とサービス2のコンテナイメージはGitHub Container Registryに置いてあります。ソースコードは「opentelemetry-go」リポジトリの「example」コードを参考にしているので、興味のある方はそちらも併せてご確認ください。

 Serviec1から呼び出されるService2については、Service1とほぼ同じです。Service1から呼び出される「service2_sample1」関数は、下記のようになります。上記のservice1_sample2と比べると、同じようにコンテキストを引数にして、スパンを作成、終了していることが分かります。

func service2_sample1(c *gin.Context) {
    _, span := tracer.Start(c.Request.Context(), "service2 の sample1 関数を実行")
    defer span.End()
    time.Sleep(time.Second * 4)
}

 これでサンプルアプリの実装は終わりです。このように手動でトレースを設定する場合、必要な箇所にスパンを定義する設定が適宜必要です。いささか苦労するので、先述したような自動インストルメンテーションが重要になります。

 サンプルアプリを実行し、生成したトレーススパンをGrafanaで確認します。

Kubernetesへのデプロイ

 本サンプルは、Kubernetesクラスタならば特にベンダーに依存せずに動作しますが、本稿では、Kubernetesは1.22を「Amazon Elastic Kubernetes Service」(EKS)で使用しています。

 Kubernetes環境を用意したら、「Helm Chart」を用いて各種コンポーネントをデプロイします。

・作業用namespaceの作成

$ kubectl create ns observability

・cert-managerのデプロイ

 「cert-manager」はKubernetesでSSL/TLS証明書を扱うパッケージです。詳細は省きますが、次にデプロイするOpenTelemetry Operatorを使うにはv1以上のcert-managerをデプロイする必要があります。

# 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 Collectorデプロイ

 OpenTelemetry Collectorをデプロイします。cert-managerが起動していることを確認したら、まずはOpenTelemetry Operatorをデプロイします。OpenTelemetry Collectorは「OpenTelemetryCollector」というカスタムリソースとして定義されます。

# cert-managerの確認
$ kubectl get po | grep cert-manager
……
 
# 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
 
# OpenTelemetry Collectorデプロイ
$ cat <<EOF | kubectl apply -f -
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: sample
  namespace: observability
spec:
  mode: deployment
  config: |
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: "0.0.0.0:4317"
    processors:
    exporters:
      otlp:
        endpoint: tempo-distributed-distributor.observability.svc.cluster.local:4317
        tls:
          insecure: true
    service:
       pipelines:
         traces:
           receivers: [otlp]
           processors: []
           exporters: [otlp]
EOF

 OpenTelemetry Collectorの設定(25行目の「config: |」以下)を見ると、OpenTelemetry Collectorの内部では下表のようなコンポーネントでトレース情報の処理パイプラインが形成されていることが分かります。

<td style="margin:0px;border:0px;font:inherit;padding:0.25em 0.25em 0.25em 0.4em;vertical-align:top;"–<-

コンポーネント 名前 設定
Receiver otlp 0.0.0.0:4317
Processor 今回は設定なし
Exporter otlp tempo-distributed-distributor.observability.svc.cluster.local:4317

・Grafana Tempo/Grafanaデプロイ

 トレーシングバックエンドのGrafana Tempoと、ダッシュボードツールのGrafanaをデプロイします。

 Grafana Tempoのデプロイ時に「traces.otlp.grpc.enabled」を有効化してOTLP(OpenTelemetry Protocol)ポートを開いています(デフォルトでは無効)。

 「search.enabled」はトレース情報のサーチ機能です。取得したトレースを一覧表示できて便利なので、有効化してトレース確認時に使用します。

# tempo-distributedのデプロイ
$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update
 
$ 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のデプロイ
$ helm install 
  grafana grafana/grafana 
  -n observability 
  --version 6.48.2 
  --wait

サンプルアプリのデプロイと実行

 最後に、今回作成したサンプルアプリをデプロイすれば、全てのコンポーネントの準備が完了です。下記のように、Pod一覧が「Running」となっていればOKです。

# デプロイ
$ kubectl -n observability apply -f https://raw.githubusercontent.com/cloudnativecheetsheet/opentelemetry/main/01/service1/deployments/manifest.yaml,https://raw.githubusercontent.com/cloudnativecheetsheet/opentelemetry/main/01/service2/deployments/manifest.yaml
 
$ kubectl get po -nobservability
NAME                                                         READY   STATUS    RESTARTS   AGE
cert-manager-7b8d75c477-45rsc                                1/1     Running   0          11m
cert-manager-cainjector-6cd8d7f84b-pkjpj                     1/1     Running   0          11m
cert-manager-webhook-66fc85bd57-dc65n                        1/1     Running   0          11m
grafana-6f66688c89-fhnnc                                     1/1     Running   0          119s
opentelemetry-operator-controller-manager-7944958b76-ppr26   2/2     Running   0          10m
sample-collector-749dcc597b-x6m46                            1/1     Running   0          8m34s
service1                                                     1/1     Running   0          15m
service2                                                     1/1     Running   0          14m
tempo-distributed-compactor-7d7b475d8f-pz546                 1/1     Running   0          3m7s
tempo-distributed-distributor-7c9bcc855c-gv7jr               1/1     Running   0          3m7s
tempo-distributed-ingester-0                                 1/1     Running   0          3m6s
tempo-distributed-ingester-1                                 1/1     Running   0          3m6s
tempo-distributed-ingester-2                                 1/1     Running   0          3m6s
tempo-distributed-memcached-0                                1/1     Running   0          3m6s
tempo-distributed-querier-6599c84b5d-bpvbm                   1/1     Running   0          3m7s
tempo-distributed-query-frontend-56b5f7fbc8-hp677            1/1     Running   0          3m7s

 service1のサービスにポートフォワード(※注3)して、サンプルアプリにリクエストを送ってみます。

# service1 サービスにポートフォワード
$ kubectl port-forward svc/service1 8080:8080 -nobservability
 
# service1 サービスにリクエストを送信
$ curl localhost:8080/service

※注3:ここで、「AWS CloudShell」などを使っている場合は、ポートがCloudShell上にフォワードされるので、使用端末のブラウザからフォワードされたポートにアクセスできません。使用端末に「kubectl」をインストールし、端末からKubernetesクラスタに接続できるようにし、端末上から実行してください(もしくは、Serviceの「LoadBalancer」を利用して、パブリックIPから接続できるようにしてください)。

Grafanaでトレース情報を確認する

 Grafanaのダッシュボードを確認します。Grafanaダッシュボードにはサンプルアプリ同様にポートフォワードしてアクセスします。

# Grafana サービスにポートフォワード
$ kubectl port-forward svc/grafana 8090:80 -nobservability

 ブラウザで「http://localhost:8090」にアクセスすると、Grafanaのログイン画面が表示されます。

 デフォルトのユーザー名は「admin」です。パスワードはKubernetesのSecretリソースとしてデプロイされており、パスワードの内容は次のコマンドで確認できます。

# Grafanaダッシュボードのログインパスワードを表示
$ kubectl get secret --namespace observability grafana -o jsonpath="{.data.admin-password}" | base64 --decode
sHEqjnUjNUlt1TmVVuHMTU9P8KUklZNCzxjLbJcW

 ログインすると下記の画面が表示されます。

 Grafanaにログインできたら、まずはGrafana Tempoを「data source」として追加します。サブメニューの歯車アイコン「Configuration」→「Add data source」を押すと、追加可能なdata sourceの一覧が表示されます。

 「Tempo」をクリックします。

 HTTPフィールドのURLにGrafana Tempoのエンドポイント「http://tempo-distributed-query-frontend:3100」を指定します。ページ下部の「Save & Test」を押して「Data source is working」が表示されれば、data sourceの追加はOKです。

 「Explore」を開いてサンプルアプリのトレースを検索してみましょう。

  • 「Query type」で「Search」を選択(下図【1】参照)
  • 「Service Name」で「service1」を選択して、クエリを実行(下図【2】を参照)
  • 先ほど送ったリクエストのトレースIDが検索結果として表示される(下図【3】を参照)
  • トレースIDをクリックすると、トレース情報を確認できる(下図【4】を参照)

 サンプルアプリで設定したリソース情報(下図の「1.Resource情報」を参照)や、サービス2の処理では4秒間要している(下図の「2.Span情報」を参照)ことも確認できます。

 分散トレーシングの見方をもう少し詳しく解説します。下図をご覧ください。

 左側にサービス/関数(メソッド)の呼び出し関係が階層構造として表示されています。図の例では、一番上にservice1へのアクセス(エンドポイントは「/service」で、サンプルコード中の「r.GET("/service", service1_sample1)」が該当)がルートのサービスとして表示されており、次にサービスの中で呼び出されるservice1の関数や、さらにはservice1から呼び出されたservice2へのアクセス(/service)や関数の呼び出しも同様に表示されているのが分かります。

 また、右側に、各サービス/関数の実行時間がグラフと数値で表示されています。最下部のservice1のスパンは、service2の後に来ているので、一番下のservice1の前にservice2が実行されたことが分かります。

 このようにOpenTelemetryを利用して分散トレーシングを取得することで、「サービスの呼び出し階層の中の、どの部分で時間がかかっているか」を簡単に確認することができ、システムのボトルネックや障害の発見に役立ちます。

次回は、トレース情報をログやメトリクスにひも付けてアプリ/システムを分析

 今回はOpenTelemetryの簡単な概要を紹介し、OpenTelemetryとGrafana Tempoを用いてマイクロサービスの分散トレーシングを試してみました。

 アプリのコンテナ化が進み、複数のサービスを組み合わせてマイクロサービス的にアプリを開発することが増えた場合、OpenTelemetryを利用すると、アプリ内の処理だけでなく、サービス間のトレースも簡単に実現できます。

 しかし、トレースのみ取得できているだけでは不十分な状況もあります。現場の運用では、「エラーログや異常なメトリクス値を検出して、関連するトレースを解析しに行く」といった流れもよくあるパターンだと思います。このようにObservabilityを見るとき、一つ一つのテレメトリーがバラバラと個別に獲得できているだけでなく、上述したようにテレメトリー間が関連付けられていることも重要だと思います。

 次回は、トレース情報をログやメトリクスにひも付けることで、問題があるログや異常なメトリクスを起点に、トレースを使いながら詳しくアプリ/システムを分析する方法を、より実践的に紹介するので、お楽しみに。

この連載を「連載記事アラート」に登録する

Click to rate this post!
[Total: 0 Average: 0]