2022年版 OpenTelemetryを知れば世界が平和に

はじめに

最初は、SRE に成る君に最低限の開発力を身に着けてほしい の解像度の上げる方法の為のGo言語みたいなこと書こうしました。しかし、内容をまとめる能力が乏しく、断念しました。書いている途中で、参考資料として改訂2版 みんなのGo言語実用 Go言語Cloud Native Go を読んでいたら、この内容を齟齬なく伝える自信が完全になくなったので読んでもらえばいいやと自暴自棄になりました。

今回は、OpenTelemetry の実装や仕組みに対する言及などが社内でないような気がしました。この共有会ではOpenTelemetry の話を何となく聞かれた時に答えられるようにしていただければと思います。OpenTelemetryプロジェクトを実際に使わなくてもプロジェクトの状況や概念、項目(ログの設計など)を知ることで現在の監視設計や運用、判断に活かせる場面が出てくるかもしれません。現状のOpenTelemetry はログ、メトリクス、トレースの全てカバーできるツールというわけではないですが1年後にはその全てをサポートしてそうな勢いと計画があります。

OpenTelemetryとは

https://opentelemetry.io/docs/concepts/what-is-opentelemetry/

OpenTelemetry は、オブザーバビリティの三本柱のログ、メトリクス、トレースの計装と収集を標準化しようとする野心的なプロジェクトです。ベンダーに依存しない実装を提供し、選択したバックエンドにテレメトリーデータを送信する方法を標準化することを目的としています。OpenTracingOpenCensusの後継的なプロジェクトで新たな標準化ツールとなります。また、仕様が全体的に固まってきたので今後、テレメトリデータを扱うツールとして広まっていくのではないかと思います。

こちらはOpenTelemetryのこれまでとこれから の資料を参考にさせていただきました。

Opentelemetry のコンポーネント

https://opentelemetry.io/docs/concepts/components/

OpenTelemetryは現在、いくつかの主要コンポーネントで構成されています。

  • 仕様
    • すべての実装に対する言語に限らない横断的な要件と実装に必要な事項を記述する。
  • Collector
    • OpenTelemetry Collectorは、テレメトリデータを受信、処理、およびエクスポートできるベンダーに依存しないプロキシです。複数の形式(OTLP、Jaeger、Prometheus、および多くの商用/独自のツールなど)でのテレメトリデータの受信と、1つ以上のバックエンドへのデータの送信をサポートします。
  • 自動計装
    • OpenTelemetry は、サポートされる言語用の一般的なライブラリやフレームワークから関連するテレメトリデータを生成する幅広い数のコンポーネントをサポートします。例えば、HTTP ライブラリからのインバウンドとアウトバウンドの HTTP リクエストは、それらのリクエストに関するデータを生成します。自動計測の使用方法は言語によって異なり、アプリケーションと一緒にロードするコンポーネントの使用を好むか要求するかもしれませんし、コードベースで明示的にパッケージを取り込むのが良いかもしれません。
  • 言語ごとのSDK
    • OpenTelemetryには言語SDKもあります。OpenTelemetry APIを使用して、選択した言語でテレメトリデータを生成し、そのデータを優先バックエンドにエクスポートすることもできます。これらのSDKを使用すると、アプリケーションの手動インストルメンテーションに接続するために使用できる一般的なライブラリおよびフレームワークの自動インストルメンテーションを組み込むこともできます。ベンダーは、バックエンドへのエクスポートを簡単にするために、言語SDKの配布を行うことがよくあります。

Opentelemetry のプロジェクトの仕様とStatus

https://opentelemetry.io/status/

現在、皆さんが関わっている各案件でOpenTelemetry の利用が可能かどうかについて聞かれると思います。何となく聞かれた時に答えられるようにこれ、各シグナルごとにこれぐらいは進んでいるんだと理解していただければと思います。

OpenTelemetryは、シグナルごとに開発されています。シグナルとはトレース、メトリクス、バッゲージ、ロギングなどの仕様でサポートされているテレメトリのカテゴリを指しています。シグナルは、分散システム間でデータを相関させるための共有メカニズムのcontext propagation(コンテキストの伝播)の上に構築されています。これらは主に4つで構成されています。

Tracing

https://opentelemetry.io/docs/concepts/signals/traces/

  • API: stable, feature-freeze
  • SDK: stable
  • Protocol: stable
  • Notes:
    • トレース仕様は現在完全に安定しており、長期的なサポートでカバーされています。
    • トレース仕様はまだ拡張可能ですが、後方互換性のある方法でのみ行われます。
    • OpenTelemetryクライアントは、そのトレース実装が完了した時点で、v1.0にバージョンアップされます。

Metrics

https://opentelemetry.io/docs/concepts/signals/metrics/

  • API: stable
  • SDK: mixed
  • Protocol: stable
  • Notes:
    • OpenTelemetry Metricsは現在活発に開発中です。
    • データモデルは安定しており、OTLPプロトコルの一部としてリリースされています。
    • メトリックパイプラインの実験的なサポートはCollectorで利用可能です。
    • PrometheusのCollectorサポートは、Prometheusコミュニティと協力して、現在開発中です。

Logging(Specification にドキュメントがない)

https://opentelemetry.io/docs/concepts/signals/logs/

  • API: draft
  • SDK: draft
  • Protocol: stable
  • Notes:
    • OpenTelemetry Logging は現在、活発に開発が進められています。
    • ログデータモデルはOpenTelemetryプロトコルの一部としてリリースされています。
    • OpenTelemetry プロジェクトへの Stanza の寄贈により、多くのデータフォーマットに対するログ処理が Collector に追加されています。
    • 現在、多くの言語でのログアペンダが開発中です。ログ・アペンダーは、トレースやスパンIDなどのOpenTelemetryトレース・データを既存のロギング・システムに付加することができます。
    • OpenTelemetry ロギングSDKも現在開発中です。これにより、OpenTelemetryクライアントが既存のロギングシステムからロギングデータを取り込み、トレースやメトリクスとともにOTLPの一部としてログを出力することができます。
    • OpenTelemetryのロギングAPIは、現在開発中ではありません。まず、既存のロギングシステムとの統合に重点を置いています。メトリクスが完成したら、OpenTelemetryのロギングAPIの開発に焦点を移します。

Baggage

https://opentelemetry.io/docs/concepts/signals/baggage/

  • API: stable, feature-freeze
  • SDK: stable
  • Protocol: N/A
  • Notes:
    • OpenTelemetry Baggage は現在完全に安定しています。
    • Baggage は観測可能なツールではなく、トランザクションに任意のキーと値を付加し、下流のサービスがそれらにアクセスできるようにするためのシステムです。そのため、BaggageにはOTLPやCollectorのコンポーネントはありません。

OpenTelemetry のSpanとTrace

https://opentelemetry.io/docs/concepts/observability-primer/

OpenTelemetryにおけるトレース情報はSpanTraceという概念で定義されています。

  • Span: リクエスト内の各処理の情報(e.g. 処理名、実行時間、ステータスコードなどなど)
  • Trace: あるリクエストに対するSpanのまとまり

https://lightstep.com/opentelemetry/spans より画像の引用

Span はトレースの構成要素で、いくつかの情報を持ちます。複数のスパンをつなぎ合わせて、Trace を作成します。Trace は、多くの場合、各Span が開始および完了した時間を反映するSpan の「Tree」と見なされます。また、Span 間の関係も示します。

Span の目的は、プログラムの実行に関する情報を観測可能なツールに提供することです。詳細が含まれている必要があり、Trace は全体像を把握するために必要な情報が含まれます。

Loggingでは今後、トレースとリンクできるようにトレースIDを持つようになることが検討されてるようです

  • 個々の情報を含むSpan
    • Name
      • 名前
    • Start and End Timestamps
      • 終了と開始の時間
    • Span Context
      • Span Context は、Trace ID と Span IDを提供します。各Span は、Span ID と呼ばれるTrace 内で一意の ID によって識別されます。 Span はTraceIDを使用して、Spanとそのトレース間の関係を識別します。Span は、サービスやプロセスの境界を越えて移動するために、Span Contextを必要とします。ログに含めることでログとSpan を紐付けることもできます
    • Attributes
      • メタデータを含むキーと値のペアのことで、Spanにアノテーションを付けて、追跡している操作に関する情報を運ぶために使用します。
    • Span Events
      • Span Event は通常、Spanの期間中の重要で特異な時点を示すために使用されます。
    • Span Links
      • Span Links はオプションですが相互に関連付ける為に利用されます。
    • Span Status
      • ステータスはSpanに添付されます。通常、アプリケーションコードに例外などの既知のエラーがある場合は、Span Statusを設定します。
    • Sample Span:
        {
          "trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
          "parent_id": "",
          "span_id": "086e83747d0e381e",
          "name": "/v1/sys/health",
          "start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
          "end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
          "status_code": "STATUS_CODE_OK",
          "status_message": "",
          "attributes": {
            "net.transport": "IP.TCP",
            "net.peer.ip": "172.17.0.1",
            "net.peer.port": "51820",
            "net.host.ip": "10.177.2.152",
            "net.host.port": "26040",
            "http.method": "GET",
            "http.target": "/v1/sys/health",
            "http.server_name": "mortar-gateway",
            "http.route": "/v1/sys/health",
            "http.user_agent": "Consul Health Check",
            "http.Scheme": "http",
            "http.host": "10.177.2.152:26040",
            "http.flavor": "1.1"
          },
          "events": {
            "name": "",
            "message": "OK",
            "タイムスタンプ": "2021-10-22 16:04:01.209512872 +0000 UTC"
          }
        }
  • 全体像を示すTrace

    OpenTelemetry のトレースがどのように機能するかを理解するために、コードの計装に関与するコンポーネントのリストを見てみましょう。

    • Tracer
      • Tracer は、サービス内のリクエストなど、与えられた操作で何が起こっているかについての詳細情報を含むSpanを作成します。Tracer はTracer Provider から作成されます。
    • Tracer Provider
      • Tracer Provider(TracerProviderと呼ばれることもあります)は、Tracerを生成します。ほとんどのアプリケーションでは、Tracer Provider は一度初期化され、そのライフサイクルはアプリケーションのライフサイクルと一致します。Tracer Providerの初期化には、ResourceとExporterの初期化も含まれます。
    • Trace Exporter
      • Trace Exportersは、Trace をコンシューマーに送信します。このconsumer は、デバッグおよび開発時の標準出力、OpenTelemetry Collector、または任意のオープンソースまたはベンダーのバックエンドにすることができます。
    • Trace Context
      • トレースコンテキストは、トレーススパンに関するメタデータで、サービスやプロセスの境界を越えてスパン間の相関関係を提供します。例えば、サービス A がサービス B を呼び出し、その呼び出しをトレースで追跡したいとします。この場合、OpenTelemetry はトレースコンテキストを使用して、サービス A からトレースの ID と現在のスパンを取得し、サービス B で作成されたスパンがトレースに接続し追加することができるようにします。
      • これは、Context Propagation(コンテキスト伝播)と呼ばれています。
    • Sample Trace
        {
            "name": "Hello-Greetings",
            "context": {
                "trace_id": "0×5b8aa5a2d2c872e8321cf37308d69df2",
                "span_id": "0×5fb397be34d26b51",
            },
            "parent_id": "0×051581bf3cb55c13",
            "start_time": "2022-04-29T18:52:58.114304Z",
            "end_time": "2022-04-29T18:52:58.114435Z",
            "attributes": {
                "http.route": "some_route1"
            },
            "events": [
                {
                    "name": "hey there!",
                    "タイムスタンプ": "2022-04-29T18:52:58.114561Z",
                    "attributes": {
                        "event_attributes": 1
                    }
                },
                {
                    "name": "bye now!",
                    "タイムスタンプ": "2022-04-29T22:52:58.114561Z",
                    "attributes": {
                        "event_attributes": 1
                    }
                }
            ],
        }
        {
            "name": "Hello-Salutations",
            "context": {
                "trace_id": "0×5b8aa5a2d2c872e8321cf37308d69df2",
                "span_id": "0×93564f51e1abe1c2",
            },
            "parent_id": "0×051581bf3cb55c13",
            "start_time": "2022-04-29T18:52:58.114492Z",
            "end_time": "2022-04-29T18:52:58.114631Z",
            "attributes": {
                "http.route": "some_route2"
            },
            "events": [
                {
                    "name": "hey there!",
                    "タイムスタンプ": "2022-04-29T18:52:58.114561Z",
                    "attributes": {
                        "event_attributes": 1
                    }
                }
            ],
        }
        {
            "name": "Hello",
            "context": {
                "trace_id": "0×5b8aa5a2d2c872e8321cf37308d69df2",
                "span_id": "0×051581bf3cb55c13",
            },
            "parent_id": null,
            "start_time": "2022-04-29T18:52:58.114201Z",
            "end_time": "2022-04-29T18:52:58.114687Z",
            "attributes": {
                "http.route": "some_route3"
            },
            "events": [
                {
                    "name": "Guten Tag!",
                    "タイムスタンプ": "2022-04-29T18:52:58.114561Z",
                    "attributes": {
                        "event_attributes": 1
                    }
                }
            ],
        }

OpenTelemetry Collectorとは

https://opentelemetry.io/docs/collector/

OpenTelemetry Collector は、テレメトリデータの受信、処理、エクスポートの方法について、ベンダーに依存しない実装を提供します。OpenTelemetry Collector は、計装と収集を標準化でいうところの収集を主に担当します。アプリケーションとテレメトリデータの中継役として動作するため各ベンダー固有のテレメトリデータをバックエンド(Jaeger、Prometheus、Fluent Bitなど)の対応したデータ形式に変換したりといった役割を持ちます。そのため、複数のエージェント/コレクタを実行、操作、保守する必要がなくなります。

Collector のメリット

  • 使いやすさ: デフォルトの設定を用意、よくあるプロトコルのサポート、そのまま使える
  • パフォーマンス: さまざまな負荷や構成に対応できるように設定することもできます
  • オブザーバビリティ: それ自体が観測可能である
  • 拡張性: コアコードに手を入れることなく拡張が可能
  • 統合: 単一のコード、エージェントでまたはコレクターとして配置可能でトレース、メトリック、ログ(将来)を扱う

OpenTelemetry Collectorは、AgentGateway2つのデプロイメント方法から選ぶことができます。

  • Agent: アプリケーションとともに、またはアプリケーションと同じホストで実行されるCollector インスタンス(バイナリ、サイドカー、デーモンセットなど)
  • Gateway: 通常、クラスタ、データセンター、地域ごとに単独のサービス(コンテナやデプロイメントなど)として稼働する1つまたは複数のCollectorインスタンス

OpenTelemetry Collector Architecture とは

https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/design.md

OpenTelemetry Collector Architectureは大まかにReceiver、Processor、Exporterの3つの要素で構成されいます。

  • Receivers
    • どういうフォーマットでどのようにテレメトリデータを受信するかという設定
    • サードパーティのテレメトリデータを受け取って、内部的にTraceとSpanに変換する役割を持つ
  • Processors
    • テレメトリデータの加工、フィルター、リトライ、バッチ処理等の設定
    • Receiverから送られてきたTraceとSpan情報を特定の条件で加工する
  • Exporters

    • テレメトリデータのエクポートに関する設定
    • Processorから送られてきたデータを、Export先のデータ形式に変換し、送信する
  • OpenTelemetry Collector の設定ファイル

    receivers:
      otlp:
        protocols:
          grpc:
          http:
      otlp/2:
        protocols:
          grpc:
            endpoint: 0.0.0.0:55690
    
    processors:
      batch:
      batch/test:
    
    exporters:
      otlp:
        endpoint: otelcol:4317
      otlp/2:
        endpoint: otelcol2:4317
    
    extensions:
      health_check:
      pprof:
      zpages:
    
    service:
      extensions: [health_check,pprof,zpages]
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlp]
        traces/2:
          receivers: [otlp/2]
          processors: [batch/test]
          exporters: [otlp/2]
        metrics:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlp]
        logs:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlp]

OpenTelemetry とSDKとパッケージ

https://opentelemetry.io/docs/concepts/instrumenting/

OpenTelemetryプロジェクトは、各言語ごとにテレメトリデータを送信するパッケージが用意されています。これらはアプリケーションの計測を容易にします。インストルメンテーションライブラリは、言語ごとにコアリポジトリを提供します。自動計測または非コアコンポーネント用の追加のリポジトリを提供する場合と提供しない場合があります。

  • opentelemetry-go
    • Tracing はStable
      • Goのコアリポジトリでありテレメトリデータ作成機能やJaeger、Zipkinといった主要なOSSやOTLPにテレメトリデータをexportするための機能を提供しています。
    • Metrics はAlpha

    • Logging はFrozen

      • Tracing のMetrics 2つの機能開発をやっているのでそれらが終わるまではIssue を認めないが
  • opentelemetry-go-contrib
    • コア機能でないものや、その他のオープンソースや商用のバックエンドのための実装を含みます。
    • Go言語の場合にここに計装系のコードも含まれている

OpenTelemetry と自動計装

https://opentelemetry.io/docs/concepts/instrumenting-library/

OpenTelemetryプロジェクトは、各言語ごとにテレメトリデータを送信するパッケージが用意されていますが、ライブラリーによっては全てを実装しなくてもよいことがあります。全てとはOpenTelemetry Trace は以下のような手順で作成されます。これらを全て自分でやるのは流石に骨が折れる作業です。

1. Exporter 作成
2. TracerProvider作成
3. Tracer取得
4. Span作成

上記の手順を生のAPIを叩いても実施してもよいのですが、アプリケーションの特定のミドルウェアフレームワークとのインタフェースがinstrumentationとして提供されており、2~4 を自動で取得することができます。トレース情報を取り出す便利ライブラリがいくつもあります(トレース、メトリクス、ロギングの全てが自動で取得できる世界線までもう少し)。

    package main
    
    import (
      "context"
      "log"
      "net/http"
      "net/http/httptrace"
    
      _ "go.opencensus.io/resource"
      _ "go.opencensus.io/trace"
      "go.opentelemetry.io/otel/attribute"
      "go.opentelemetry.io/otel/exporters/jaeger"
      "go.opentelemetry.io/otel/sdk/resource"
      "go.opentelemetry.io/otel/sdk/trace"
      semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
    
      "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
      "go.opentelemetry.io/otel"
    )
    
    func main() {
      tracerProvider, err := NewTracerProvider("otelhttp_client_trace")
      if err != nil {
          log.Fatal(err)
      }
      defer func() {
          if err := tracerProvider.Shutdown(context.Background()); err != nil {
              log.Fatal(err)
          }
      }()
      otel.SetTracerProvider(tracerProvider)
    
      ctx := context.Background()
      ctx, span := tracerProvider.Tracer("main").Start(ctx, "main")
      defer span.End()
    
      if err := httpGet(ctx, "https://3-shake.com/"); err != nil {
          log.Fatal(err)
      }
    }
    
    func httpGet(ctx context.Context, url string) error {
      ctx, span := otel.Tracer("main").Start(ctx, "httpGet")
      defer span.End()
      span.SetAttributes(attribute.Key("url").String(url))
    
      clientTrace := otelhttptrace.NewClientTrace(ctx)
      ctx = httptrace.WithClientTrace(ctx, clientTrace)
      req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
      if err != nil {
          return err
      }
      _, err = http.DefaultClient.Do(req)
      if err != nil {
          return err
      }
      return nil
    }
    
    func NewTracerProvider(serviceName string) (*trace.TracerProvider, error) {
      // Port details: https://www.jaegertracing.io/docs/getting-started/
      collectorEndpointURI := "http://localhost:14268/api/traces"
    
      exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(collectorEndpointURI)))
      if err != nil {
          return nil, err
      }
    
      r := NewResource(serviceName, "v1", "local")
      return trace.NewTracerProvider(
          trace.WithBatcher(exporter),
          trace.WithResource(r),
          trace.WithSampler(trace.TraceIDRatioBased(1)),
      ), nil
    }
    
    func NewResource(serviceName string, version string, environment string) *resource.Resource {
      r, _ := resource.Merge(
          resource.Default(),
          resource.NewWithAttributes(
              semconv.SchemaURL,
              semconv.ServiceNameKey.String(serviceName),
              semconv.ServiceVersionKey.String(version),
              attribute.String("environment", environment),
          ),
      )
      return r
    }

それらをjaeger (イエーガー)に食べさせた結果がこれ

今後のOpentelemetry について

https://www.cncf.io/blog/2022/07/07/opentelemetry-roadmap-and-latest-updates/

各ライブラリーでの対応は別としてこのような発言もあります。

Realistically at this point in time, I don’t expect logging to be stable until the end of the year at the earliest but I’d say early next year.

現実的に現時点では、ロギングが安定するのは早くても年末ですが、来年の早いタイミングだとは思います。 -> OpenTelemetry を取り巻く環境は来年2023年4月ぐらいにもう一度取り扱いと思います。

他にも以下のようなトピックに触れておりました。

  • OpenTelemetryがMetricsのRCに到達.
  • Logs の仕様が安定、Logs Beta の予定.
  • OpenTelemetryへのリアルユーザーモニタリングの追加.
  • OpenTelemetryへの継続的プロファイリングの追加.
  • リモートエージェント管理による操作性の向上.
  • OpenTelemetryに4317番ポートが登録.
  • eBPFとその他のアップデート.

とても気になるトピックが多くありますがこの記事では紹介しません。

次回予告:OpenTelemetry とOpenTelemetry Collectorを使ったTracingとMetricsをアプリケーションで利用する方法

ざっくりとではありましたがOpenTelemetryに関する技術要素とその概要をまとめました。野心的なプロジェクトでまだ道半ばですが個人的には将来がとても楽しみです。OpenTelemetry とOpenTelemetry Collectorを使ったTracingとMetricsをアプリケーションで利用する場合、基本的な流れは次のようになります。

# Tracing 
1. Exporter 作成
2. TracerProvider作成
3. Tracer取得
4. Span作成
# Metrics
1. Exporter 作成
2. MeterProvider 作成
3. Meter 作成
4. Instrument 作成
5. Measurement 作成

上記に関しては社内ハンズオンなどで実施していきたいと思います。 社内共有会で利用したものだからといって公開するにあたって参照を記載する際に気をつけることを学びました。良い学びです。

参照リンク