OTEP-0264: リソースとエンティティ - データモデル 第2部

これは、リソースとエンティティのデータモデルの相互作用に対応するための提案であり、現行のリソース仕様における当面の摩擦や問題に対処するための道筋を含みます。 これは以前のエンティティ提案を拡張するものです。

動機

この提案は、複数のワーキンググループのブロッキングを解消するために、OpenTelemetry内の以下の問題に焦点を当てようとするものです。

  • 変更可能な属性がリソースに参加できるようにすること(OTEP 208)。
  • リソースが、SDKのライフタイムと一致しないライフタイムを持つエンティティを扱えるようにすること(OTEP 208)。
  • 非同期のリソース検索をサポートすること(spec#952)。
  • ほとんどの実装が違反している、仕様書における現行のリソースのマージルールを修正すること(oteps#208spec#3382spec#3710)。
  • セマンティック規約のリソースモデリングを前進させられるようにすること(spec#605spec#559など)。

設計

アプローチ - リソースの改善

エンティティ検出器とリソースの構成の概要を示すことに焦点を当てましょう。 これはOpenTelemetry内で修正する優先度が高く、より早くブロッキングを解消する必要があります。 その後、データモデルとCollectorのユースケースへと話を戻します。

私たちは以下のSDKコンポーネントを定義します。

  • リソース検出器(レガシー): 既存のリソース検出器をそのまま維持します。 これらは今日と同じ振る舞いとインターフェースを持ちます。
  • エンティティ検出器(新規): 現在のSDKのインスタンスに関連するエンティティを検出します。 たとえば、現在のSDKのためのサービスエンティティや、そのプロセスを検出することになります。 すべてのエンティティは、現在のSDKと何らかの関係を持っていなければなりません。
  • リソースプロバイダー(新規): リソースおよびエンティティ検出器を受け取り、以下を行う責任を持つコンポーネントです。
    • 検出器からSDKのためのリソースを構築すること。
    • 検出器間の競合を処理すること。
    • 設定されたLogProviderでのLogシグナルによる報告のために、検出されたリソースへのSDK内部アクセスを提供すること。
    • (新規)SDKのライフタイム中のエンティティの変化、特にSDKよりも短いライフタイムを持つエンティティの扱いを管理すること

リソースプロバイダー

SDKのリソースプロバイダーは、設定されたすべてのリソース検出器およびエンティティ検出器を実行する責任を持ちます。 これらには(ユーザーが制御可能な、OTelのデフォルトの)優先順位が存在します。

  • リソースプロバイダーは、同じ型のエンティティが検出された際の競合を検出し、どちらを使用するかを選択します。
  • エンティティ検出器とリソース検出器を併用する場合、以下のマージルールが使用されます。
    • まずエンティティのマージが行われ、「エンティティマージ済み」のリソースが生成されます(こちらのアルゴリズムを参照)。
    • それ以外の点では、リソース検出器は既存のマージセマンティクスに従います。
      • 仕様書のマージルールは、リソース検出のすべての実装に蔓延している違反を考慮して更新されます。
      • 具体的には、schema-urlをまたいだリソースのマージに関するルールは廃止されます。 代わりに、競合する属性のみが破棄されます。
      • リソースのSchemaURLは非推奨となり、エンティティ固有のschema-urlがそれに代わります。 SDKは、検出されたすべてのエンティティ間でSchemaURLが一致する場合にのみ、リソースのSchemaURLを埋めます。 さらに、安定版のOpenTelemetryコンポーネントでは、リソースのSchemaURLに使用できるのは既存の安定版リソース属性のみです(具体的には、service.*sdk.* のみが安定化されたリソース規約です)。 リソースのマージ仕様に関する実装側の懸念が広く存在することを踏まえると、この非推奨化による影響は最小限であり、既存の利用はセマンティック規約の「実験的」フェーズの範囲内にとどまっていたと私たちは考えています。
    • リソース検出のためにSDK全体で環境変数を要求する代わりに、既製の「環境変数エンティティ検出器」が仕様として定められます。
  • さらに、リソースプロバイダーは、SDK自身のライフタイムと一致しない、あるいはそれを超えるライフタイムを持つエンティティ(たとえば、ブラウザセッションなど)について、エンティティのライフサイクルイベントを理解する責任を持つことになります。

エンティティ検出器

SDK内のエンティティ検出器は、SDKを識別しうる可能性のあるエンティティ(「関連エンティティ」と呼ばれます)を検出する責任を持ちます。 たとえば、SDKがKubernetesのPod内で稼働している場合、そのPodのためのエンティティを提供することがあります。 SDKのエンティティ検出器は識別属性を提供することのみが要求されますが、今日のSDKと同様の属性を含む合成後のリソースを確実にするために、記述属性を提供してもかまいません。

エンティティ検出器は、次のようなAPIを持ちます。

trait EntityDetector
  pub fn detect_entities(...) -> Result<Vec<Entity>, EntityDetectionError>>

ここで、Result は選択した言語におけるエラーチャネルの相当物です(たとえば、Goでは entities, err := e.detectEntities() となります)。

エンティティ検出器は、同じエンティティ型(たとえば、2つの host や2つの service エンティティ)を持つ2つのエンティティを提供してはなりません(MUST NOT)。

エンティティのマージとリソース

この設計のもっとも重要な側面は、リソースを構成するためにエンティティがどのようにマージされるかという点です。

私たちはこの振る舞いのためのシンプルなアルゴリズムを提供します。

  • 検出されたエンティティの集合 E を構築します。
    • すべてのエンティティ検出器は優先度順(最高優先度が先)にソートされます。
    • 各エンティティ検出器 D について、エンティティを検出します。
      • 検出された各エンティティ d' について、
        • d' と同じエンティティ型を持つエンティティ e'E に存在する場合、次のいずれかを行います。
          • エンティティのアイデンティティとschema_urlが同じであれば、d' の記述属性を e' にマージします。
            • d' 内の各記述属性 da' について、
              • da'.keye' に存在しなければ、da'ei に追加します。
              • そうでなければ無視します。
          • エンティティのアイデンティティは同じだが、schema_urlが異なる場合は、新しいエンティティ d' を破棄します。 注: この場合に設定を提供することも考えられます。
          • エンティティのアイデンティティが異なる場合は、新しいエンティティ d' を破棄します。
        • それ以外の場合は、エンティティ d' を集合 E に追加します。
  • 集合 E からリソースを構築します。
    • E 内のすべてのエンティティが同じ schema_url を持つ場合、リソースの schema_url をそれに一致させます。
    • そうでなければ、リソースの schema_url は空のままにします。

このアルゴリズムと同じ結果を達成する実装であれば、どのようなものでも受け入れられます。

環境変数検出器

プラットフォームが、そのプラットフォーム上で動作するワークロードにエンティティのアイデンティティ情報を注入できるようにするためのエンティティ検出器が定められます。 たとえば、OpenTelemetry Operatorは、Kubernetes DeploymentとContainerに関する情報を環境変数に注入することができ、SDKは(環境変数エンティティ検出器の設定を通じて)これと連携することを選択できます。 ここでいうプラットフォームとは、ワークロードのアイデンティティを提供できる、そのワークロードを実行できる環境、たとえばKubernetes、Spark、クラウド環境などを意味します。

この問題の背景については、#3966を参照してください。

環境変数の詳細は変更される可能性がありますが、以下のような形になると考えられます。

set OTEL_DETECTED_ENTITIES=k8s.deployment[k8s.deployment.name=my-program],k8s.pod[k8s.pod.name=my-program-2314,k8s.namespace=default]
<run my program>

このエンティティ検出器の最小限の要件は以下のとおりです。

  • 環境変数は複数のエンティティ(リソース属性のバンドル)を指定できます。
  • 環境変数は、必要に応じて複数の参加システムによって容易に追記されたり利用されたりできます。
  • 環境変数を通じて発見されたエンティティは、汎用的にリソースプロバイダーに参加できます。 すなわち、競合する定義の解決にも参加できます。
  • 環境変数は、プラットフォームのエンティティプロバイダー(たとえば前置と後置など)によって影響を受けうる優先度を持ちます。

この環境変数の相互作用に関する実際の設計は、本OTEPの承認を経て決定されます。

OpenTelemetry Collectorとの相互作用

OpenTelemetry Collectorは、リソース上のエンティティとオプションで相互作用するように更新できます。 個々の属性レベルではなく、エンティティレベルでの追加/上書きの振る舞いを可能にする、新しいエンティティ中心のリソース検出プロセスを作成できます。

たとえば、既存のリソース検出器は以下のようになっています。

processors:
  resourcedetection/docker:
    detectors: [env, docker]
    timeout: 2s
    override: false

将来のエンティティベースの検出器は、まったく同じ見た目になりますが、リソースのエンティティモデルと相互作用します。

processor:
  entityresourcedetection:
     # Order determines override behavior
     detectors: [env, docker]
     # False means only append if entity doesn't already exist.
     override: false 

検出器のリストは優先度順に与えられます(先勝ち、上書き設定を除き同点の場合)。 このプロセッサーは、上書きフラグが個々の検出器に適用されるように更新する必要があるかもしれません。

属性に関するルールは、SDKのリソースプロバイダーで定義されているエンティティマージルールに従います。

注: この提案は、resourcedetection プロセッサーを置き換える新しいプロセッサーを示していますが、既存の resourcedetection プロセッサーをその場で修正するか、新しいものを作成するかの詳細は、この設計のフォローアップとして決定されます。 理想的には、Collector内のリソースのために、ユーザーが新しい設定を必要としないようにしたいと考えています。

データモデルの変更

エンティティの検出、マージ、操作について望ましい設計とアルゴリズムを踏まえると、エンティティとリソースがどのように関連するかを示す能力が必要です。 これらの変更は、リソースの既存の利用を壊してはならないため、次のことが求められます。

  • エンティティモデルは、リソースモデルの 上に階層化されなければなりません 。 システムは、正しく動作するためにエンティティと相互作用する必要はありません。
  • エンティティを使用する場合でも、リソースの既存の主要な用途、特にナビゲーション(参照: OpenTelemetry Resources: Principles and Characteristics)は維持されなければなりません。
  • ダウンストリームのコンポーネントは、リソース内のエンティティモデルと関わることができるべきです。

以下の変更が加えられます。

リソース

フィールド説明変更内容
schema_urlstring既知の場合のスキーマURL。これは、リソースデータが記録されているスキーマの識別子です。このフィールドは非推奨であり、今後使用すべきではありません。非推奨化
dropped_attributes_countintegerdropped_attributes_countは、破棄された属性の数です。値が0の場合、破棄された属性はありません。変更なし
attributesrepeated key-valueリソースを記述する属性のセット。

属性キーは一意でなければなりません(同じキーを持つ属性を複数持つことはできません)。
変更なし
entitiesrepeated ResourceEntityRefこのリソースに参加するエンティティのセット。追加

データモデルは、リソース内の属性がエンティティの識別属性と記述属性の両方から生成されることを保証します。 これは、プロトコルが重複したデータを送信する必要があるという意味ではなく、その設計は今後決定されます。

ResourceEntityRef

entityrefのデータモデルは、リソース内での参照を示すために、元のエンティティOTEPから以下の変更が加えられます。

フィールド説明変更内容
schema_urlstring既知の場合のスキーマURL。これは、エンティティデータが記録されているスキーマの識別子です。スキーマURLについて詳しくは(こちらのドキュメントを参照)。追加
typestringエンティティの型を定義します。エンティティのライフタイム中に変更されてはなりません(MUST not)。たとえば「service」や「host」です。このフィールドは必須であり、有効なエンティティにおいて空であってはなりません(MUST not)。変更なし
identifying_attributes_keysrepeated stringエンティティを識別する属性キー。
エンティティのライフタイム中に変更されてはなりません(MUST not)。IDは少なくとも1つの属性を含まなければなりません。

これらのキーは、Resource.attributesに存在しなければなりません(MUST)。

OpenTelemetryの共通属性定義に従います。属性についてはOpenTelemetryのセマンティック規約に従うべきです(SHOULD)。
参照になった
descriptive_attributes_keysrepeated stringエンティティの記述的な(非識別的な)属性キー。
エンティティのライフタイムを通じて変更されることがあります(MAY)。空であってもかまいません(MAY)。これらの属性キーはエンティティのアイデンティティの一部ではありません。

これらのキーは、Resource.attributesに存在しなければなりません(MUST)。

OpenTelemetry仕様における任意の値の定義に従います。スカラー値、バイト配列、配列、あるいは値のマップとすることができます。配列やマップに対する任意の深さのネストが許可されます。

属性についてはOpenTelemetryのセマンティック規約に従うべきです(SHOULD)。
参照になった

リソースアイデンティティ

OpenTelemetryのリソースアイデンティティは、以下のように変更されます。

  • リソース上の entities が空の場合、そのアイデンティティは(キーと値の両方の)すべての attributes の集合です。
  • リソース上の entities が空でない場合、そのアイデンティティは、キーが entities.descriptive_attributes_keys に含まれないすべての attributes の集合です。

OTLPデータをグルーピングまたは混在させる際には、そのアイデンティティを使用して2つのリソースが同一であるかどうかを検出し、(該当する場合)データモデル内で正式化される、上述のエンティティマージアルゴリズムを使用して記述属性をマージすることができます。

この提案が動機となった問題をどのように解決するか

エンティティ提案からいくつかの動機となった問題を見てみましょう。

問題1: エンティティの混在

私たちは、リソース内でエンティティを混在させる必要性を受け入れ、これらの詳細を消し去るのではなく、ダウンストリームのユーザーが個々のエンティティと相互作用できるようにします。

問題2: 正確なアイデンティティの欠如

アイデンティティは、リソースのエンティティ部分を通じて記述から明確に区別されるようになりました。 エンティティがリソースに使用される場合、リソースのアイデンティティを構成するために相互作用が必要なのは識別属性のみです。

問題3: 変更可能な属性の欠如

この提案は、この問題に対する今後の2つの解決策を提供します。

  • 記述属性は、リソースのアイデンティティを侵害することなく変更できます。
  • SDKと一致しないライフタイムを持つエンティティは、リソースに対して付加/削除できます。

問題4: メトリクスのカーディナリティの問題

(2)の解決策を通じて、エンティティ上の識別属性から合成されたアイデンティティを活用できます。 エンティティのライフタイムを直接モデル化することで、リソース内のアイデンティティの変化がテレメトリーのソースが変化したときに のみ 発生することを保証します。 これは意図しないメトリクスのカーディナリティの問題を解決します(一方で、対処が必要なもの、たとえば本質的にカーディナリティが高い携帯電話やブラウザインスタンスからのメトリクス収集などは残ります)。

エンティティWGルーブリック

Entities WGは、この取り組み全体に対する共有された信念と目標に基づいて解決策を評価するためのルーブリックを考案しました。 各項目がどのように達成されるかを見てみましょう。

リソース検出器(間もなくエンティティ検出器になる)は、構成可能/互いに素である必要がある

エンティティ検出とリソースマネージャーは、今やこのニーズを満たします。

拡張によって追加された新しいエンティティは、既存のコードを壊すべきではない

ユーザーは、モデル化される新しいエンティティのために新しいエンティティ検出器を設定する必要があります。

リソースは引き続き、エンティティの識別属性と記述属性から構成され、ユーザーがすでにリソースに期待している基本的なナビゲーション属性を許容します。

Collectorの拡張/エンリッチメント(リソースなど) - 拡張可能であるべきで、ハードコードされているべきではない。特定のルールセットではなく、一般的なアルゴリズムが必要である

「エンティティ」という概念は、リソースにとっての新しい定義です。 以前は、リソースは属性の集合であり、ユーザーは各属性と個別に相互作用していましたが、今や「エンティティ」と呼ばれる属性の「バンドル」があります。 エンティティはアイデンティティと記述を持ち、Collectorはエンティティを構成する属性の集合に対する競合を識別できます。

ここで定義されたマージルールは、host.idhost.name にどのように影響するかというニュアンスを理解しなければならないハードコードされたルールではなく、Collectorが「型」「識別属性」「記述属性」と汎用的に相互作用できる先例を与えます。

ユーザーは「検出器」を提供/優先順位付けし、どのエンティティがあるシグナルを「生成している」か、あるいはもっとも重要かを決定することが期待される

リソースマネージャーは、ユーザーがエンティティ検出器の優先度を設定できるようにします。

SDKにおいては、すべてのテレメトリーが同じエンティティ(リソースラベル)の集合に関連付けられるべきである

リソースマネージャーは、エンティティを、今日のリソースと同じ要求を満たす一貫したリソースへと解決する責任を持ちます。

未解決の課題

以下は未解決のまま残っている課題です。

リソース内でエンティティの「バンドル」情報をどう添付するか

今日のプロトコルは、リソース上に生の属性の寄せ集めを要求します。 私たちは今後もこれを壊すことはできません。 しかし、エンティティはリソース上で属性を「バンドル」し、これらのバンドルと相互作用するための新しいメカニズムを表します。 私たちは、これがプロトコルを肥大化させることも、奇妙な挙動を引き起こすことも望んでいません。

今後、私たちにはいくつかの選択肢があります。

  • リソースの Entity セクション内で属性を複製する。
  • エンティティの中でリソースの属性を参照する。
  • エンティティIDのみを識別し、属性とエンティティの関連付けは帯域外に保つ。
  • OTLP内の任意の属性に対して、キー・バリューごとにエンティティ型を追跡できるように、リソース上の属性を拡張する。

第3の選択肢は、汎用的なコードが、それぞれのモデルを理解することなくリソースとエンティティと相互作用することを妨げます。 第1の選択肢は、情報の重複というコストと引き換えに、エンティティのすべての利用をシンプルに保ちます。 そして中間の選択肢は、OTLPの利用の観点からは扱いにくいものです。 第4の選択肢は、OTLPに対する私たちの安定性ポリシーに違反します。

SDKと一致しないライフサイクルを持つリソース/エンティティをどう扱うか

この提案は、エンティティのライフタイムの変化を管理する役割を含みうる、SDK内のリソースプロバイダーを動機づけますが、これらの変化がTracerProvider、LogProvider、MeterProviderなどにどのようにブロードキャストされるかについては考慮していません。 それは、後続のOTEPで対応されることになります。

SDK以外のテレメトリーにおけるPrometheus互換性をどう扱うか

今日、Prometheus互換性は、リソース内の2つの主要な属性、service.nameservice.instance.id に依存しています。 これらは、OpenTelemetry SDKによる生成の外部では、存在することが保証されていません。 この課題は完全には解決されていませんが、OpenTelemetry内のすべてのリソースにおいてアイデンティティを概説することにより、今日機能しているものとの互換性を保ちながら、将来的な解決策を定義できるようになると私たちは考えています。

以下は解決策に対する要件のリストです。

  • 既存のPrometheus/OpenTelemetryユーザーが、今いる場所から移行できること。
  • どのような解決策も、Prometheusに追加されつつあるinfoタイプのメトリクスと連携しなければなりません(MUST)。
    • リソースの記述属性は info() またはメタデータを活用すべきです。
    • リソースの識別属性は、OpenTelemetryセマンティック規約とエンティティWGによる、さらなる検討・設計が必要です。
    • 注: 現在の info() の設計は、デフォルトでは target_info メトリクスに対してのみ機能し(他のinfoメトリクスは info 呼び出しごとに指定可能)、結合のために job/instance ラベルを使用します。 これらのラベルは、Prometheus内のOTLPエンドポイントによって生成されなければなりません(MUST)。
  • (望ましい)ユーザーは、Prometheus内でラベルとして表示されるリソース属性を通じて、メトリクスの時系列を他のシグナルと相関付けられるべきです。
  • (望ましい)OTLP -> Prometheus の変換は、OTLP -> Prometheus -> OTLP が非可逆的でないように、逆変換が可能であるべきです。

これがどのような形になりうるか、(すべてを網羅しているわけではありませんが)いくつかの選択肢を挙げます。

  • 選択肢1 - 今日あるものを維持する
    • target_info は、すべてのリソース属性を持つ現状のまま存在し続けます。
    • PrometheusのOTLP取り込みは、リソース属性をメトリクスラベルに昇格させることをサポートし続けます。
  • 選択肢2 - すべての識別属性を昇格させる
    • デフォルトで、リソース上のすべての識別ラベルがリソース属性に昇格されます。
    • すべての記述ラベルは target_info に配置されます。
    • (おそらく)service エンティティを持たないリソースのために、job/instance を合成する必要があります。
  • 選択肢3 - エンティティをinfoメトリクスとしてPrometheusにエンコードする
    • {entity_type}_entity_info メトリクスを作成します。
    • すべての *_info メトリクス間の結合のために job/instance ラベルを合成します。
    • このエンコーディングと連携するために、Prometheusにおけるinfoタイプメトリクスの取り組みの範囲を拡張します。
  • 選択肢4 - メタデータ設計を活用した解決策を探る

これらの設計は、要件に照らして検討・評価されます。 現時点では、Prometheusの互換性は選択肢1のまま継続し、その間に私たちはPrometheusにおけるリソースのより良い未来を共に築いていく取り組みを進めます。

エンティティはドメインを持つべきか

エンティティに、型に加えて domain を持たせる価値はあるでしょうか。 各エンティティを1つのドメインに存在するように強制し、リソース管理において汎用的にドメインを活用することもできるでしょう。 エンティティ検出器はドメイン全体に責任を持ち、リソースに適用するものを1つだけ選択することになります。 ドメインは階層化することができます。 たとえば、クラウド固有のドメインがKubernetesドメインの上に階層化され、「GKEクラスターエンティティ」が、あるKubernetesインフラエンティティが どの Kubernetesクラスターに属しているかを識別するといった具合です。 この階層化は、参加しているエンティティの自動的な結合や、GKE固有のフックから導出される明示的な関係を通じて、素朴に行われることになります。

これが最初から必要かどうかは不明であり、これは後から階層化できるものだと私たちは考えています。

リソースは1つだけの関連エンティティを持つべきか

Entitiesワーキンググループにつながった問題群と、既存のリソースユーザーの今日のニーズを踏まえると、リソースを1つのエンティティのみに制限することは実行不可能であり、拡張性がないと私たちは考えています。 これは、OpenTelemetryがエンティティ定義の唯一の情報源であることを要求し、オープンで拡張可能なエコシステムの構築を損なうような制約を、エンティティのモデリングに課すことになります。 さらに、以下の問題/ルーブリックに対する解決策の慎重な定義が必要になります。

  • 拡張によって追加された新しいエンティティは、既存のコードを壊すべきではない
  • Collectorの拡張/エンリッチメント(リソースなど) - 拡張可能であるべきで、ハードコードされているべきではない。特定のルールセットではなく、一般的なアルゴリズムが必要である

エンティティはどのようなアイデンティティ(LID、UUID/GUID、その他)を使用すべきか

最初のエンティティOTEPにおけるもっとも大きな課題の1つは、エンティティをどのように識別するかということでした。 これは、ナビゲーション属性の必要性と、今後はエンティティの識別属性のみがリソースに表示されるべきだという考えを統一しようとする試みでした。 この提案では、この制約はもはや必要ではなく、エンティティのアイデンティティをどのようにモデル化するかを再検討すべきです。

これは、後続の設計/OTEPで行うことができます。

Collector内の既存のリソース変換が、エンティティが依存するリソース属性を除去した場合どうなるか

Collectorはアーキテクチャの中で最初にエンティティと関わり始めるコンポーネントになると予想される一方、これはデータモデル違反につながる可能性があります。 この問題に対処するためのいくつかの選択肢があります。

  • これをバグとみなし、ユーザーにそうしないよう警告する。
  • 記述属性については、キーの欠落が許容されることを規定する。
  • キーの欠落は、そのバッチのテレメトリーに対してエンティティが利用不能であることを意味すると規定し、その内容を不正な形式として扱う。

Collectorにおける高度なエンティティの相互作用について

この設計を動機づけた問題の1つは、OpenTelemetry Collectorにおける「ローカルなリソース検出」対「リモートなシグナル収集」の問題です。 すなわち、あるマシン上で動作しているプロセスが、別のマシン上で動作しているOpenTelemetry Collectorに書き込んでいるという状況です。 現在の resourcedetectionprocessor は、Collectorがどこで動作しているか を発見することに基づいてリソースに属性を追加します。 しかし、Collectorがテレメトリーが別のマシンから来たものであると判断できるのであれば、着信データに関連しないリソース属性の追加を回避することもできるはずです。

今日、resourcedetectionprocessor は素朴であり、本OTEPで提案されているアルゴリズムも同様です。 私たちは、データを取得するために使用された通信プロトコルに関するより高度な知識(たとえば、OTLPサーバー上での送信元のIPアドレスを使用するなど)に基づいて、Collectorがリソースにエンティティを結合すべきでないと判断できる、より洗練された解決策を作り出せると考えています。

トレードオフと緩和策

ここで提案されている設計は、仕様書における問題のある課題を改善する必要性と、(後方互換性と前方互換性の両面で)非破壊的な変更とのバランスを取ろうとするものです。 ほとんどのSDKが現行のリソースマージ仕様を実装できていないことを踏まえると、これを破壊することが実際のユーザーに与える影響はほとんどないと考えられます。 その代わりに、提案されているマージ仕様は、新しいモデルと関わるユーザーのために進化しつつも、実装が現在の振る舞いと期待に一致することを可能にするはずです。

なぜスキーマURLの内容をダウンロードしないのか

OpenTelemetryは、外部のインターネットへのアクセスがない、あるいは限られている環境でも動作する必要があります。 私たちは、schema_url の内容へのアクセスを 必要とする マージの解決策を検討しましたが、それを却下しました。 コアとなるアルゴリズムはこのアクセスを 必要としてはならない 一方で、このデータを活用しうる、より改善された処理やアルゴリズムを提供 できる べきです。

たとえば、

  • SDK内では、EntityDetector にエンティティのスキーマ情報を登録できます。
  • OpenTelemetry Collectorは、設定を通じて登録された schema_url を許可したり、(オプションで)オンデマンドでスキーマをダウンロードしたりできます。

この設計はこれらの解決策を妨げるものではありませんが、schema_url にアクセスできない場合でもエンティティが使用可能でなければならないという、基準/フォールバックを提供します。

先行技術と代替案

以前に、私たちにはいくつかの受理されなかったOTEP、たとえばOTEP 208があります。 さらに、Entities WGで検討され、却下されたいくつかの代替案があります。

以下は、いくつかの設計上の決定に関する簡単な議論です。

  • リソースに1つのエンティティのみを関連付ける。 これは、セマンティック規約を発展させ、独立したシステムがOpenTelemetryエコシステム内でアイデンティティとエンティティを協調させることを可能にする上で、あまりに大きな摩擦点となるため却下されました。 最終的には、これによりOpenTelemetryは世界のあらゆる可能なエンティティをモデル化し、それらの相互作用を理解することを強いられるか、そうでなければOpenTelemetry以外の計装がOpenTelemetryのエンティティと相互作用することを妨げることになったでしょう。
  • エンティティをリソースに完全に埋め込む。 これは、リソース属性とエンティティが乖離することを容易に、あるいは些細なことにしてしまうため却下されました。 これは後方互換性/前方互換性の目標を妨げ、参加するすべてのOTLPユーザーがエンティティを活用することを要求してしまいます。 エンティティは、ユーザーのニーズに応じて関わるかどうかを選択できる、オプトイン/追加機能であるべきです。
  • 既存のリソース検出をそのまま再利用する。 これは、実行可能な互換性の道筋を持たないため却下されました。 既存の振る舞いを維持しながら、ユーザーが新しい機能を採用できるようにする新しいコンポーネント群を作成することで、ユーザーはシステムの振る舞いをいつ確認・変更するかをよりよく制御でき、エコシステム全体での採用もより明確になります。

将来の可能性

この提案は、エンティティのライフタイムがSDKのライフタイムと一致しないという問題に対応する道を開くとともに、変更可能な(記述的な)属性が、そのアイデンティティに影響を与えることなく、リソースのライフタイムを通じて変更できるデータモデルを提供します。 私たちは、この問題を直接扱う後続のOTEPが提案されることを期待しています。

ユースケース

以下は、この設計の動機を説明するための一連のユースケースです。

SDK - 同じエンティティ型に対する複数の検出器

複数のエンティティ検出器が登録されている状況でのSDKの相互作用を考えてみましょう。

flowchart LR
    SDK["`**SDK**`"] -->|OTLP| BACKEND["`**Backend**`"]
    SDK -.- RC((Resource Provider))
    RC -.- OTEL_DETECTOR((OpenTelemetry Default Resource Detection))
    RC -.- GCP_DETECTOR((Google Cloud Specific Resource Detection))
    GCP_DETECTOR -. Detects .-> GCE{{gcp.gce}}
    GCP_DETECTOR -. Detects .-> GCPHOST{{"host (gcp)"}}
    OTEL_DETECTOR -. Detects .-> HOST{{"host (generic)"}}
    OTEL_DETECTOR -. Detects .-> PROCESS{{process}}
    OTEL_DETECTOR -. Detects .-> SERVICE{{service}}

ここでは、Google Compute Engine上でサービスが稼働しています。 ユーザーはGoogle Cloud固有のエンティティ検出器のセットを設定しています。 組み込みのOpenTelemetry検出と、設定されたGoogle Cloud検出の両方が、host エンティティを発見します。

以下の結果が生じます。

  • 結果として得られるリソースは、hostprocessservicegcp.gce のすべてのエンティティを持つことになります。
  • ユーザーが設定したリソース検出器は組み込みのものより優先されます。 すなわち、Google Cloud検出によって定義された host が「勝ち」、リソースに含まれます。
    • これは、たとえば host.id が、GCEのVMのために発見されたIDになりうることを意味します。 たとえば、Amazon EC2のように、クラウドプロバイダーによって汎用的なマシンIDではなく一意のIDがVMに付与される他のクラウドプロバイダーの検出についても同様です。
    • これは、host.id の意味に関して、AWSやGCPなどについて今日存在している振る舞い/期待に一致します。
  • ユーザーは、「デフォルト」検出とクラウド固有検出の優先順位を入れ替えることによって、どちらのホストが勝つかを設定できます。

SDKとCollector - シンプルな協調

SDKとCollectorが存在する状況における、リソースとエンティティの相互作用を考えてみましょう。

flowchart LR
    SDK["`**SDK**`"] -->|OTLP| COLLECTOR["`**Collector**`"]
    COLLECTOR -->|OTLP| BACKEND["`**Backend**`"]
    SDK -.- RC((Resource Provider))
    COLLECTOR -.- RP((Resource Processor))
    RP -. Detects .-> EC2{{aws.ec2}}
    RP -. Detects .-> HOST{{host}}
    RC -. Detects .-> PROCESS{{process}}
    RC -. Detects .-> SERVICE{{service}}

ここでは、SDKがAmazon EC2上で稼働しています。 これは、processservice エンティティを発見するリソース検出が設定されています。 このSDKは、ec2host エンティティを検出するように設定されたリソースプロセッサーを持つOpenTelemetry Collectorにデータを送信しています。

Collectorから出力されるOTLPは、すべてのエンティティ(processserviceec2host)を含むリソースを持つことになります。 これは、これらのエンティティがすべて互いに素であるためです。

注: これは、既存のリソース検出とOpenTelemetry Collectorにおいて、すべての属性がリソース上に行き着くという今日の振る舞いと一致します。

SDKとCollector - 記述属性を伴うエンティティの協調

SDKとCollectorの両方が1つのエンティティを検出する状況における、リソースとエンティティの相互作用を考えてみましょう。

flowchart LR
    SDK["`**SDK**`"] -->|OTLP| COLLECTOR["`**Collector**`"]
    COLLECTOR -->|OTLP| BACKEND["`**Backend**`"]
    SDK -.- RC((Resource Provider))
    COLLECTOR -.- RP((Resource Processor))
    RP -. Detects .-> HOST2{{host}}
    RC -. Detects .-> HOST{{host}}
    RC -. Detects .-> SERVICE{{service}}

ここでは、SDKがあるマシン(物理または仮想)上で稼働しています。 SDKは、自身が稼働しているホストを検出するように設定されています。 Collectorもまた、あるマシン(物理または仮想)上で稼働しています。 SDKとCollectorの両方が(同じアイデンティティを持つ)host エンティティを検出します。

振る舞いは以下のようになります。

  • デフォルトでは、Collectorは自身の host エンティティから欠落している記述属性を、host エンティティおよびリソースに追記します。
  • Collectorのプロセッサーが override: true に設定されている場合、SDKからの host エンティティは破棄され、Collectorの host エンティティが採用されます。 元のエンティティとリソースからのすべての識別属性・記述属性が除去され、Collectorで検出されたものがそれに取って代わります。

これにより、Collectorは送信元の アイデンティティ を変えることなく、リソース属性をエンリッチ/強化できます。

SDKとCollector - 競合を伴うエンティティの協調

SDKとCollectorの間でアイデンティティの競合がある状況における、リソースとエンティティの相互作用を考えてみましょう。

flowchart LR
    SDK["`**SDK**`"] -->|OTLP| COLLECTOR["`**Collector**`"]
    COLLECTOR -->|OTLP| BACKEND["`**Backend**`"]
    SDK -.- RC((Resource Provider))
    COLLECTOR -.- RP((Resource Processor))
    RP -. Detects .-> HOST2{{host 2}}
    RC -. Detects .-> HOST{{host 1}}
    RC -. Detects .-> SERVICE{{service}}

ここでは、SDKがあるマシン(物理または仮想)上で稼働しています。 SDKは、自身が稼働しているホストを検出するように設定されています。 Collectorもまた、あるマシン(物理または仮想)上で稼働しています。 SDKとCollectorの両方が host エンティティを検出します。 しかし、この host エンティティは、SDKとCollectorの間で 異なるアイデンティティ を持っています。

振る舞いは以下のようになります。

  • デフォルトでは、エンティティのアイデンティティが一致しないため、Collectorによって検出されたエンティティは 破棄 されます。 これは、たとえばCollectorからのホストの記述属性が、OTLP内のリソースに追加され ない ことを意味します。
  • Collectorのプロセッサーが override: true に設定されている場合、SDKからの host エンティティは破棄され、Collectorの host エンティティが採用されます。 元のエンティティとリソースからのすべての識別属性・記述属性が除去され、Collectorで検出されたものがそれに取って代わります。

このデフォルトの振る舞いは、SDKとCollectorが異なるマシン上で実行されている場合に有用です。 今日のリソース検出とは異なり、これはSDKによって検出されなかった host の記述属性がリソースに追加されることを防ぐことができます。

override の振る舞いはまた、一緒に検出・報告されるべき属性が一緒に置き換えられることを保証できます。 今日では、Collectorが、SDKからの属性の一部を検出・上書きし、すべてを上書きしないという状況が起こり得ます。

SDKとCollector - バージョンをまたいだエンティティの協調

システム内のコンポーネント間でセマンティックバージョンの違いが発生しうる場合の、SDKとCollectorの協調を見てみましょう。

flowchart LR
    SDK["`**SDK**`"] -->|OTLP| COLLECTOR["`**Collector**`"]
    COLLECTOR -->|OTLP| BACKEND["`**Backend**`"]
    SDK -.- RC((Resource Provider))
    COLLECTOR -.- RP((Resource Processor))
    RP -. Detects .-> POD{{"`k8s.pod
    *schema: 1.26.0*
    `"}}
    RP -. Detects .-> DEPLOYMENT{{k8s.deployment}}
    RC -. Detects .-> POD2{{"`k8s.pod
    *schema: 1.25.0*
    `"}}
    RC -. Detects .-> SERVICE{{service}}

ここでは、SDKがCollectorと通信しています。 SDKとCollectorはともに、エンティティを通じたリソース検出に参加していますが、インストールされているソフトウェアは、CollectorとSDKの間で異なる標準バージョンを利用しています。

理想的には、私たちは以下のような解決策を望んでいます。

  • ユーザーが、以前は発見されていなかったが関連する(具体的には k8s.deployment の)エンティティにのみ関連する属性が、リソースに追加されることを確実にできること。
  • ユーザーが、スキーマバージョン 1.26.01.25.0 が同じエンティティに対して異なる属性を持ちうる問題に対処できること。
  • リソース内で望ましい属性を達成するために、ユーザーが必要とする設定やカスタマイズが最小限で済む、デフォルトのルールとマージが存在すること。

使用されているリソース検出器と属性の一覧

  • Collector
    • system
      • host.arch
      • host.name
      • host.id
      • host.ip
      • host.mac
      • host.cpu.vendor.id
      • host.cpu.family
      • host.cpu.model.id
      • host.cpu.model.name
      • host.cpu.stepping
      • host.cpu.cache.l2.size
      • os.description
      • os.type
    • Docker
      • host.name
      • os.type
    • heroku
      • cloud.provider
      • heroku.app.id
      • heroku.dyno.id
      • heroku.release.commit
      • heroku.release.creation_timestamp
      • service.instance.id
      • service.name
      • service.version
    • GCP
      • gke
        • cloud.provider
        • cloud.platform
        • cloud.account.id
        • cloud.region
        • cloud.availability_zone
        • k8s.cluster.name
        • host.id
        • host.name
      • gce
        • cloud.provider
        • cloud.platform
        • cloud.account.id
        • cloud.region
        • cloud.availability_zone
        • host.id
        • host.name
        • host.type
        • (オプション)gcp.gce.instance.hostname
        • (オプション)gcp.gce.instance.name
    • AWS
      • ec2
        • cloud.provider
        • cloud.platform
        • cloud.account.id
        • cloud.region
        • cloud.availability_zone
        • host.id
        • host.image.id
        • host.name
        • host.type
      • ecs
        • cloud.provider
        • cloud.platform
        • cloud.account.id
        • cloud.region
        • cloud.availability_zone
        • aws.ecs.cluster.arn
        • aws.ecs.task.arn
        • aws.ecs.task.family
        • aws.ecs.task.id
        • aws.ecs.task.revision
        • aws.ecs.launchtype(V4のみ)
        • aws.log.group.names(V4のみ)
        • aws.log.group.arns(V4のみ)
        • aws.log.stream.names(V4のみ)
        • aws.log.stream.arns(V4のみ)
      • elastic_beanstalk
        • cloud.provider
        • cloud.platform
        • deployment.environment
        • service.instance.id
        • service.version
      • eks
        • cloud.provider
        • cloud.platform
        • k8s.cluster.name
      • lambda
        • cloud.provider
        • cloud.platform
        • cloud.region
        • faas.name
        • faas.version
        • faas.instance
        • faas.max_memory
        • aws.log.group.names
        • aws.log.stream.names
    • Azure
      • cloud.provider
      • cloud.platform
      • cloud.region
      • cloud.account.id
      • host.id
      • host.name
      • azure.vm.name
      • azure.vm.size
      • azure.vm.scaleset.name
      • azure.resourcegroup.name
    • Azure aks
      • cloud.provider
      • cloud.platform
      • k8s.cluster.name
    • Consul
      • cloud.region
      • host.id
      • host.name
      • 展開されたConsulのメタデータ
    • K8s Node
      • k8s.node.uid
    • Openshift
      • cloud.provider
      • cloud.platform
      • cloud.region
      • k8s.cluster.name
  • Javaのリソース検出
    • SDK-Default
      • service.name
      • telemetry.sdk.version
      • telemetry.sdk.language
      • telemetry.sdk.name
    • process
      • process.pid
      • process.command_line
      • process.command_args
      • process.executable.path
    • host
      • host.name
      • host.arch
    • container
      • container.id
    • os
      • os.type
    • AWS
      • EC2
        • host.id
        • cloud.availability_zone
        • host.type
        • host.image.id
        • cloud.account.id
        • cloud.region
        • host.name
      • ECS
        • cloud.provider
        • cloud.platform
        • aws.log.group.names
        • aws.log.stream.names
      • EKS
        • cloud.provider
        • cloud.platform
        • k8s.cluster.name
        • container.id
      • Lambda
        • cloud.platform
        • cloud.region
        • faas.name
        • faas.version
    • GCP
      • cloud.provider
      • cloud.platform
      • cloud.account.id
      • cloud.availability_zone
      • cloud.region
      • host.id
      • host.name
      • host.type
      • k8s.pod.name
      • k8s.namespace.name
      • k8s.container.name
      • k8s.cluster.name
      • faas.name
      • faas.instance
    • Go
      • container
        • container.id
      • host
        • host.id
      • os
        • os.name
      • process
        • process.pid
        • process.executable.name
        • process.executable.path
        • process.command_line
        • process.command_args
        • process.owner
      • built-in
        • service.instance.id
        • service.name
  • OTel operatorによって注入される環境変数
    • service.instance.id
    • service.name
    • service.version
    • k8s.namespace.name
    • k8s.pod.name
    • k8s.node.name
    • k8s.container.name

影響

影響に関する初期の考察をいくつか挙げます。

AWS、Azure、GCP、Herokuなどは、いずれも以下のリソースの「バンドル」を提供します。

  • cloud.*
  • faas.*(該当する場合)
  • host.*(該当する場合)
  • k8s.cluster.*(該当する場合)
  • service.*(該当する場合)
  • container.*(一部のK8sプロバイダーの場合)

「system」検出は以下を提供します。

  • host.*
  • os.*
  • process.*(SDKの場合)
  • container.*(Dockerイメージの場合)

SDK固有の検出は以下を提供します。

  • sdk.*
  • service.*

K8sのためのOTel operatorは、環境変数を通じて以下を提供します。

  • k8s.namespace.*
  • k8s.node.*
  • k8s.pod.*
  • k8s.container.*
  • service.*

リソースに属するエンティティを選択する際、これは何を意味しうるか

Kubernetes、特にEKS上で稼働しているコンテナの例を見てみましょう。

OTel operator、SDK、Collectorがすべて使用されている場合、以下の属性がリソース上に行き着きます。

  • service.* - SDKとOTel operatorから
  • sdk.* - SDKから
  • process.* - SDKから
  • host.* - 注: Collector上のsystem検出器から
  • container.* - SDK上のEKS検出器から
  • k8s.namespace.* - OTel operatorから
  • k8s.node.* - OTel operatorから
  • k8s.pod.* - OTel operatorから
  • k8s.container.* - OTel operatorから
  • k8s.cluster.* - SDKまたはCollector上のEKS検出器から
  • cloud.* - SDKまたはCollector上のEKS検出器から

これから導かれる、あるエンティティをリソースに含めるべきかどうかを判断するためのシンプルなリトマステストは、次のとおりです。 「生成されたテレメトリーに関連するあらゆるエンティティが含まれるべきである」。

しかし、これはさらに洗練できます。 今日のリソースは、いくつかの主要な機能を提供します。

  • アイデンティティを提供する - データの発生源を一意に識別する。
  • 「ナビゲーション」を提供する - ユーザーが自身のo11yやインフラのツール内でデータの発生源を見つけられるようにする。
  • 興味深いドメインでのデータの集約/スライシングを可能にする。

どのエンティティをリソースに含めるべきかを判断するためのリトマステストは、以下のようになるべきです。

  • そのエンティティは、データの発生源/起源であるか。
  • そのエンティティは、データの発生源へのナビゲーションを助けるか(たとえば、k8s.cluster.*k8s.container.* を見つける助けになるなど)。
  • そのエンティティが提供する軸に沿って、容易にスライス/集約したいと考えるか(たとえば、過負荷のノードを見つけるために、クラスター全体のすべてのCPUコンテナ使用率メトリクスを素早くフィルタリングするなど)。

これらの質問のいずれかに対する答えがイエスであれば、そのエンティティをリソースに含めるべきです。