> Source: https://www.ymotongpoo.com/works/oteps/otep-0111/


# OTEP-0111: リソースの自動検出

リソースの自動検出をサポートするメカニズムを導入します。

## 動機 {#motivation}

リソース情報、すなわちテレメトリーを生成するエンティティに関連付けられた属性は、現在トレーサープロバイダーやメータープロバイダーに供給したり、カスタムエクスポーターの中で追加したりすることができます。
これに加えて、ホストから（たとえば環境変数から、あるいはAWS、GCPなどのメタデータから）リソース情報を自動的に検出し、これをあらゆる種類のテレメトリーに適用するメカニズムがあると有用です。
これにより、多くの場合、ユーザーがリソース情報を手動で設定する必要がなくなります。

すでにSDKの中にこれを実現している実装がいくつかあることに注意してください（[後述](#prior-art-and-alternatives)を参照）。
しかし、現在のところ仕様には何も定められていません。

## 解説 {#explanation}

自動検出されたリソース情報をあらゆる種類のテレメトリーに適用するためには、ユーザーはどのリソース検出器を実行したいかを設定する必要があります（たとえば、AWS EC2検出器など）。

複数の検出器が設定されており、そのうち複数がリソースの検出に成功した場合、リソースは仕様にすでに定義されているMergeインターフェースに従ってマージされます。
つまり、最も早くマッチしたリソースの属性が優先されます。
各検出器は並行して実行されるかもしれませんが、決定論的な結果を保証するために、リソースは検出器が追加された順序でマージされなければなりません。

`OTEL_RESOURCE` 環境変数からリソースデータを読み取る検出器のデフォルト実装がSDKに含まれます。
この環境変数はキーと値のペアのリストで構成され、これらは[W3C Baggage](https://github.com/w3c/baggage/blob/main/baggage/HTTP_HEADER_FORMAT.md#header-content)と似た形式で表現されることが期待されます。
ただし、セミコロン区切りの追加のメタデータはサポートされません。
つまり次のような形式です。
`key1=value1,key2=value2`。
ユーザーがリソースを指定しない場合、この検出器がデフォルトで実行されます。

特定の環境（たとえば特定のクラウドベンダーなど）に関連するカスタムリソース検出器は、コアSDKとは別のパッケージとして実装されなければならず、ユーザーはこれらを別途インポートする必要があります。

## 内部の詳細 {#internal-details}

上記で説明したとおり、以下がリソースSDK仕様に追加されます。

- リソース情報を取得するための「検出器」のインターフェース
- 一連の検出器から返されたリソースをマージするためのグローバル関数の仕様
- 上記で説明した「環境変数からの」検出器実装の詳細
- デフォルトの検出（環境変数から）は起動時に一度だけ実行され、カスタムリソースが供給されない場合はすべてのトレーサー・メータープロバイダーがデフォルトでこれを使用するという仕様

### 使用方法 {#usage}

以下は、AWSまたはGCPから自動検出されたリソース情報を使用するトレーサープロバイダーとメータープロバイダーを作成するGoの例です。

`otel/api` 、`otel/sdk` 、`otel/awsdetector` 、`otel/gcpdetector` パッケージへの依存関係がすでに追加されていることを前提とします。

```go
resource, _ := sdkresource.Detect(ctx, 5 * time.Second, awsdetector.ec2, gcpdetector.gce)
tp := sdktrace.NewProvider(sdktrace.WithResource(resource))
mp := push.New(..., push.WithResource(resource))
```

### コンポーネント {#components}

#### 検出器 {#detector}

`Detector` インターフェースは、単純にResourceを返す `Detect` 関数を含みます。

`Detect` 関数には、操作をタイムアウトさせキャンセルするためのメカニズムが含まれる _べきです_ （SHOULD）。
検出器がリソースを検出できない場合、各 `Detect` 呼び出しの結果がマージできるように、初期化されていないリソースを返さなければなりません（MUST）。

#### グローバル関数 {#global-function}

SDKはグローバルな `Detect` 関数も提供します。
これはタイムアウトの時間と、冒頭で説明したように順序どおりに実行してマージすべき一連の検出器を受け取り、リソースを返します。

### エラー処理 {#error-handling}

1つ以上の検出器がエラーを発生させた場合、2つの合理的な選択肢があります。

1. その検出器を無視し、警告付きで処理を継続する（多くの場合、期待していたリソース情報なしで処理を継続することを意味します）
2. アプリケーションをクラッシュさせる（パニックを発生させる）

ユーザーは、失敗からどのように回復するかを決めることができます。

## トレードオフと緩和策 {#trade-offs-and-mitigations}

- このOTEPでは、ベンダーのリソース検出パッケージをSDKの外部に保存することを提案しています。
  これにより、SDKがベンダー固有のコードを含まないことが保証されます。
  リソース検出を実行するために一般的に必要とされるコードの量が比較的単純かつ最小限であり、クラウドプロバイダーの数も比較的少ないことを踏まえると、代わりにすべてのリソース検出コードをSDK内に直接置くことを許容する、という決定をしてもよいかもしれません。
  - ベンダーのリソース検出パッケージをSDK内に許容する場合、おそらくそれらが非自明なライブラリを使用できないよう制限する必要があります。
- このOTEPでは、デフォルトでは環境変数によるリソース検出のみを実行することを提案しています。
  クラウドプロバイダーの数が比較的少ないことを踏まえると、代わりにデフォルトですべての検出器を実行することを許容する、という決定をしてもよいかもしれません。
  これは、そのために何らかの制限を設ける必要があるかどうか、そして将来クラウドプロバイダーの数が増えた場合にこれをどう扱うかという問題を提起します。
  デフォルトでこれらを実行することを一度始めてしまうと、それは破壊的変更につながるため、後から撤回するのは難しいでしょう。
- このOTEPでは、ユーザーが実行したい検出器を渡してグローバル関数を呼び出し、そのうえでユーザーがそれらをプロバイダーに渡すことを想定した提案をしています。
  代替案（このOTEPで以前に提案されていたもの）としては、静的なリソースの代わりに、あるいはそれに加えるオプションとして、検出器の集合をメトリクスプロバイダーやトレースプロバイダーに直接供給するという方法があります。
  これにより、ユーザーが自分で `AutoDetect` を呼び出す必要がなくなり、セットアップコードがわずかに単純になります。
  このアプローチのもう一つの利点は、デフォルトの検出器を指定し、ユーザーが提供したい静的リソースとは別にそれらを上書きすることが容易になる点です。
  一方で、このアプローチには、静的リソースが提供された場合に検出されたリソースとマージする処理を扱う複雑さが加わるという欠点があります。
  また、検出器が各プロバイダーごとに設定されることになるため、検出器が複数回実行されるのを避ける方法についても、多くの複雑さが加わる可能性があります。
  トレーサー・メータープロバイダーに対して検出器を指定する必要をなくすことが、結局のところこのオプションを採用しなかった主な理由です。
- 属性のprotoは現在、配列とマップをサポートしています。
  Correlation Contextがセミコロン区切りのキーおよびキーと値のペアのリストをサポートするのと同様の方法で、`OTEL_RESOURCE` 環境変数からこれを解析することをサポートすることもできますが、誰かがこれについて良いユースケースを持っていない限り、追加される複雑さはおそらく実装する価値がありません。
- リソース検出時にエラーが発生した場合の別の選択肢として、何らかの戦略に従ってリトライするバックグラウンドスレッドを開始する方法もありますが、これを行うことに大きな価値があるかどうかは明確ではなく、それはかなりの不要な複雑さを加えることになります。

## 先行技術と代替技術 {#prior-art-and-alternatives}

この提案は、既存のOpenCensus仕様、OpenCensus Go実装、およびOpenTelemetry JS実装に大きく着想を得ています。
参考として、[OpenCensus仕様](https://github.com/census-instrumentation/opencensus-specs/blob/master/resource/Resource.md#populating-resources)の該当セクションを参照してください。

### 既存のOpenTelemetry実装 {#existing-opentelemetry-implementations}

- JS SDKにおけるリソース検出の実装は[こちら](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-resources)です。
  JSの実装はこの提案と非常によく似ています。
  この提案では、SDKがユーザーによって明示的に呼び出され渡される必要があるグローバルな `DetectResources` 関数を持つだけでなく、検出器をテレメトリープロバイダーに直接渡せるようにすることを述べています。
  さらに、ベンダー固有のリソース検出コードは現在JSのresourceパッケージに含まれているため、これは分離される必要があります。
- Java SDKにおける環境変数によるリソース検出は[こちら](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java)です。
  この実装には現在検出器のインターフェースは含まれていませんが、トレーサー・メータープロバイダーによってデフォルトで使用されます。

## 未解決の問題 {#open-questions}

- これは、リソースに関連する今後の他の仕様変更と干渉しますか。
- カスタム検出器がコアリポジトリの外部に置かれる必要がある場合、それらがどこにホストされるべきかについての期待はどのようなものですか。
- [トレードオフと緩和策](#trade-offs-and-mitigations)のセクションも参照してください。

## 今後の課題 {#future-possibilities}

Collectorがエージェントとして実行される場合、Go SDKと共有される同一のインターフェースを使って、ホストから検出されたリソース情報をProcessor内であらゆる種類のテレメトリーに追加することができるでしょう（おそらく既存のResource Processorへの拡張として）。
これには、SDKのリソースからCollectorの内部的なリソース表現への変換が必要になります。

