# OBI による分散トレース

> OBI の分散トレースサポートについて学びます。

---

Site [llms.txt](/ja/llms.txt)

---

## はじめに {#introduction}

OBI はアプリケーションの分散トレースをサポートしますが、いくつかの制限とカーネルバージョンの制約があります。

分散トレーシングは [W3C `traceparent`](https://www.w3.org/TR/trace-context/) ヘッダー値の伝搬を通じて実装されています。
OBI は受信コンテキストを自動的に読み取ります。
送信側のネットワークレベルでのコンテキスト伝搬はデフォルトで無効になっており、以下の説明にしたがって有効にする必要があります。

適用される伝搬モードを有効にすると、OBI は受信トレースコンテキストを読み取り、プログラムの実行を追跡し、送信される HTTP または gRPC リクエストに `traceparent` を追加します。
アプリケーションがすでに `traceparent` を追加している場合、OBI は自前で生成したコンテキストではなく、その値を使用します。
受信コンテキストが見つからない場合、OBI は W3C 仕様にしたがって新たに生成します。

スレッド、goroutine、タスク、イベントループをまたいで処理が移動する際に OBI がどのように親リクエストを選択するかについては、[トレースコンテキストの関連付け](../context-propagation/)を参照してください。

## 互換性 {#compatibility}

OBI は以下の構成で分散トレーシングとコンテキスト伝搬をサポートします。

| 分野                                       | サポートされるバージョンや環境                                          | 備考                                                                                                                                                                               |
| :----------------------------------------- | :---------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ネットワークレベル HTTP/1 伝搬             | [OBI 互換性要件](/docs/zero-code/obi/#compatibility)を満たす Linux 環境 | プログラミング言語を問わず動作します。HTTPS の場合、伝搬は他の OBI で計装されたサービス間に限定され、プロキシや L7 ロードバランサーによって途切れる可能性があります。              |
| ネットワークレベル HTTP/2 および gRPC 伝搬 | 平文 HTTP/2(gRPC `1.0+` を含む)                                         | 言語を問わずストリームごとの HPACK `traceparent` ヘッダーを使用します。暗号化された汎用 HTTP/2 ではネットワークレイヤーでコンテキストを注入できません。                            |
| Go ライブラリレベルのコンテキスト伝搬      | Go `1.18+`                                                              | 最大 6 階層のネストされた goroutine までコンテキスト伝搬をサポートします。この分散トレーシング機能は、一般的な Go ライブラリレベル計装よりも最小バージョン要件が高くなっています。 |
| Node.js の async hooks                     | Node.js `12.17+`(`13.0`〜`13.9` を除く)                                 | `SIGUSR1` のカスタムハンドリングがコンテキスト伝搬を妨げる可能性があります。                                                                                                       |
| Ruby Puma                                  | Puma `5.0+` で提供される Ruby アプリケーション                          | コンテキスト伝搬サポートには Puma サーバーが必要です。                                                                                                                             |
| Java スレッドプール                        | JDK `8+`                                                                | 文書化された追加のランタイム制約はありません。                                                                                                                                     |
| Python asyncio                             | GIL 有効の 64 ビット CPython `3.9`〜`3.14`                              | 標準の asyncio イベントループと `uvloop` をサポートします。制限事項は以下を参照してください。                                                                                      |

ここに記載のバージョンは、OBI が分散トレース機能で明示的にサポートするバージョンです。
これ以外のバージョンでも動作する可能性はありますが、特に明記されない限り、文書化されたサポート範囲には含まれません。
特に、ここでの Go `1.18+` 要件は分散トレーシングとコンテキスト伝搬に適用されるものであり、他の OBI Go ライブラリレベル計装の最小バージョンはこれより低い値です。

## 実装 {#implementation}

トレースコンテキストの伝搬は、2 つの異なる方法で実装されています。

1. 送信ヘッダー情報をネットワークレベルで書き込む
2. Go の場合は、ヘッダー情報をライブラリレベルで書き込む

サービスのプログラミング言語に応じて、OBI はこれらのコンテキスト伝搬アプローチのいずれか、または両方を使用します。
eBPF でメモリに書き込むことはカーネル設定や OBI に付与された Linux システムケーパビリティに依存するため、複数のアプローチを使ってコンテキスト伝搬を実装しています。
このトピックの詳細については、KubeCon NA 2024 のトーク [So You Want to Write Memory with eBPF?](https://www.youtube.com/watch?v=TUiVX-44S9s) を参照してください。

**ネットワークレベル**のコンテキスト伝搬はデフォルトで**無効**になっており、環境変数 `OTEL_EBPF_BPF_CONTEXT_PROPAGATION=all` を設定するか、OBI 設定ファイルを次のように変更することで有効化できます。

```yaml
ebpf:
  context_propagation: 'all'
```

### ネットワークレベルでのコンテキスト伝搬 {#context-propagation-at-network-level}

ネットワークレベルでのコンテキスト伝搬は、送信される HTTP ヘッダーおよび TCP/IP パケットレベルにトレースコンテキスト情報を書き込むことで実装されています。
HTTP のコンテキスト伝搬は、他の OpenTelemetry ベースのトレーシングライブラリと完全に互換性があります。
つまり、OBI で計装されたサービスは、OpenTelemetry SDK で計装されたサービスとの間で送受信されるトレース情報を正しく伝搬できます。
ネットワークパケットの調整には [Linux Traffic Control (TC)](<https://en.wikipedia.org/wiki/Tc_(Linux)>) を使用しているため、Linux Traffic Control を使う他の eBPF プログラムが OBI と適切にチェーンする必要があります。
Cilium CNI に関する特別な考慮事項については、[Cilium 互換性](../cilium-compatibility/)ガイドを参照してください。

TLS で暗号化されたトラフィック (HTTPS) では、OBI は送信される HTTP ヘッダーにトレース情報を注入できないため、かわりに TCP/IP パケットレベルに情報を注入します。
この制限により、OBI は他の OBI で計装されたサービスにのみトレース情報を送信できます。
L7 プロキシやロードバランサーは元のパケットを破棄して下流で再送するため、TCP/IP のコンテキスト伝搬を妨げます。
OpenTelemetry SDK で計装されたサービスからの受信トレースコンテキスト情報の解析は引き続き機能します。

gRPC を含む平文 HTTP/2 の場合、OBI はプログラミング言語を問わずストリームごとの `traceparent` HPACK ヘッダーを注入・読み取りします。
TCP オプションはコネクション単位のスコープであり、多重化されたストリームを表現できません。
暗号化された汎用 HTTP/2 ではネットワークレイヤーでコンテキストを注入できません。Go のライブラリレベル計装であれば暗号化前に注入できます。

さらに細かい制御が必要な場合、`context_propagation` には `headers`、`tcp`、`headers,tcp` も指定できます。
以前存在した `http` エイリアスは削除されました。
非推奨の `ip` 値は効果がありません。

この種類のコンテキスト伝搬は任意のプログラミング言語で機能し、OBI を `privileged` モードで実行したり `CAP_SYS_ADMIN` を付与したりする必要はありません。
詳細については、[分散トレースとコンテキスト伝搬](../configure/metrics-traces-attributes/)の設定セクションを参照してください。

#### Kubernetes での設定 {#kubernetes-configuration}

ネットワークレベルの分散トレーシングをサポートする状態で OBI を Kubernetes 上にデプロイする推奨方法は、`DaemonSet` としてデプロイすることです。

次の `Kubernetes` 設定を使用する必要があります。

- OBI はホストネットワークアクセス (`hostNetwork: true`) 付きの `DaemonSet` としてデプロイする必要があります。
- ホストの `/sys/fs/cgroup` パスをローカルの `/sys/fs/cgroup` パスとしてボリュームマウントする必要があります。
- OBI コンテナに `CAP_NET_ADMIN` ケーパビリティを付与する必要があります。

次の YAML スニペットは OBI デプロイ設定の例です。

```yaml
spec:
  serviceAccount: obi
  hostPID: true # <-- 重要。DaemonSet モードでは OBI がすべての監視対象プロセスを検出するために必要
  hostNetwork: true # <-- 重要。DaemonSet モードでは OBI がすべてのネットワークパケットを参照するために必要
  dnsPolicy: ClusterFirstWithHostNet
  containers:
    - name: obi
      resources:
        limits:
          memory: 120Mi
      terminationMessagePolicy: FallbackToLogsOnError
      image: 'docker.io/otel/ebpf-instrument:v0.15.2'
      imagePullPolicy: 'Always'
      env:
        - name: OTEL_EXPORTER_OTLP_ENDPOINT
          value: 'http://otelcol:4318'
        - name: OTEL_EBPF_KUBE_METADATA_ENABLE
          value: 'autodetect'
        - name: OTEL_EBPF_CONFIG_PATH
          value: '/config/obi-config.yml'
      securityContext:
        runAsUser: 0
        readOnlyRootFilesystem: true
        capabilities:
          add:
            - BPF # <-- 重要。ほとんどの eBPF プローブが正しく動作するために必要。
            - SYS_PTRACE # <-- 重要。OBI がコンテナの名前空間にアクセスし、実行可能ファイルを検査することを許可。
            - NET_RAW # <-- 重要。OBI が HTTP リクエスト用のソケットフィルターを使用することを許可。
            - CHECKPOINT_RESTORE # <-- 重要。OBI が ELF ファイルを開くことを許可。
            - DAC_READ_SEARCH # <-- 重要。OBI が ELF ファイルを開くことを許可。
            - PERFMON # <-- 重要。OBI が BPF プログラムをロードすることを許可。
            - NET_ADMIN # <-- 重要。OBI が HTTP および TCP のコンテキスト伝搬情報を注入することを許可。
      volumeMounts:
        - name: cgroup
          mountPath: /sys/fs/cgroup # <-- 重要。OBI が新規ソケットを監視して送信リクエストを追跡することを許可。
        - mountPath: /config
          name: obi-config
  tolerations:
    - effect: NoSchedule
      operator: Exists
    - effect: NoExecute
      operator: Exists
  volumes:
    - name: obi-config
      configMap:
        name: obi-config
    - name: cgroup
      hostPath:
        path: /sys/fs/cgroup
```

OBI の `DaemonSet` でローカルボリュームパスとして `/sys/fs/cgroup` がマウントされていない場合、一部のリクエストでコンテキスト伝搬が行われないことがあります。
このボリュームパスは、新しく作成されたソケットをリッスンするために使用されます。

#### カーネルバージョンの制限 {#kernel-version-limitations}

ネットワークレベルでのコンテキスト伝搬の受信ヘッダー解析は、BPF ループの追加と利用のため、通常はカーネル 5.17 以降を必要とします。

RHEL 9.2 などの一部のパッチ済みカーネルでは、この機能がバックポートされている場合があります。
`OTEL_EBPF_OVERRIDE_BPF_LOOP_ENABLED` を設定すると、お使いのカーネルがこの機能を含むものの 5.17 より低いバージョンである場合に、カーネルチェックをスキップできます。

### ライブラリレベルで計装する Go のコンテキスト伝搬 {#go-context-propagation-by-instrumenting-at-library-level}

この種類のコンテキスト伝搬は Go アプリケーションでのみサポートされており、eBPF のユーザーメモリ書き込みサポート (`bpf_probe_write_user`) を使用します。
このアプローチの利点は、HTTP および HTTPS で動作することです。
HTTP/2 と gRPC については、新規および再利用された HTTP/2・gRPC 接続でコンテキストを注入できます。
`bpf_probe_write_user` の使用には OBI に `CAP_SYS_ADMIN` を付与するか、privileged コンテナとして実行する必要があります。

#### Go Trace API を使用するアプリケーションの計装 {#instrument-applications-that-use-the-go-trace-api}

OBI v0.11.0 以降、OBI は SDK を登録せずに OpenTelemetry Go Trace API を使用するアプリケーションを計装できます。
この統合が有効な場合、OBI は Trace API の呼び出しを検出し、生成された手動スパンを eBPF スパンとともにエクスポートします。
すでに OpenTelemetry SDK を登録しているアプリケーションは、独自の SDK テレメトリーの管理とエクスポートを引き続き行います。
`otel.SetTracerProvider(auto.TracerProvider())` の呼び出しを含め、グローバルな `TracerProvider` を登録すると、この自動的な有効化は抑止されます。

v0.15.2 以降、OBI は `go.opentelemetry.io/otel/trace` に組み込まれた Auto SDK を通じたスパンもキャプチャします。
このパスは、v1.35.0 から v1.46.0 までの `.0` リリースにおける、置き換えのない正規の `otel` および `otel/trace` モジュールを受け入れます。`go.opentelemetry.io/auto/sdk` モジュールは任意です。
空のコンテキストからのプロバイダー検索、および非記録のローカルまたはリモートスパンコンテキストをサポートします。
任意のリモート親の抽出とコンテキストハンドオフには制限があります。

OBI がこの統合を有効にするのは、以下の条件がすべて満たされた場合のみです。

- アプリケーションがサポートされた OpenTelemetry モジュールバージョンとチェックサムの組み合わせを使用しており、モジュールの置き換えがないこと。
- 実行可能ファイルとホストがサポートされた64ビットアーキテクチャを使用していること。
- OBI が必要なシンボルとフィールドレイアウトを解決できること。
- OBI に `bpf_probe_write_user` の使用権限があること。

いずれかのチェックに失敗した場合、Auto SDK は無効のままとなり、グローバル Trace API を通じて生成されたスパンは記録されません。
OBI の eBPF 計装は引き続き独立して動作します。
OBI が Trace API の呼び出しを検出できる場合、スパン名、親子関係、ステータス、および一部のプリミティブ属性を含む部分的な合成スパンをエクスポートできます。
これらのスパンには計装スコープ、イベント、リクエストされたスパンの種類は含まれません。

OBI v0.15.2 では、Auto SDK を通じてエクスポートされる各スパンのエンコード済みペイロードは 16 KiB を超えてはなりません。
OBI は統合を有効化した際やオーバーサイズのペイロードを破棄した際に、メトリクスやログメッセージを出力しません。

既知の制限と今後の作業には、[ヘッドサンプリング](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/issues/2793)、[コンテキストハンドオフ](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/issues/2794)、[外部およびリモートの親と `TraceState`](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/issues/2959)、[より大きなペイロードと破棄の可観測性](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/issues/2958)、および[ログエンリッチメント](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/issues/2932)が含まれます。

サポートされるモジュールバージョン、チェックサム、アーキテクチャの組み合わせについては、[有効化の適格性マトリクス](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/blob/v0.15.2/SUPPORT_MATRIX.md#go-global-trace-api-and-auto-sdk-activation)を参照してください。
上流の [Go Trace API の例](https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation/tree/v0.15.2/examples/go-trace-api)と [Auto SDK](/docs/zero-code/go/autosdk) のドキュメントも参照してください。

#### カーネル integrity モードの制限 {#kernel-integrity-mode-limitations}

送信される HTTP/gRPC リクエストヘッダーに `traceparent` 値を書き込むため、OBI は [**bpf_probe_write_user**](https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html) eBPF ヘルパーを使ってプロセスメモリに書き込む必要があります。
カーネル 5.14 以降 (5.10 系にもフィックスがバックポート) では、Linux カーネルが `integrity` **lockdown** モードで動作している場合、このヘルパーは保護され(BPF プログラムから利用不可)になります。
カーネル integrity モードは、カーネルで [**セキュアブート**](https://wiki.debian.org/SecureBoot)が有効になっている場合は通常デフォルトで有効になりますが、手動で有効化することもできます。

OBI は `bpf_probe_write_user` ヘルパーを利用できるかを自動的に確認し、カーネル設定が許可している場合にのみコンテキスト伝搬を有効にします。
Linux カーネルの **lockdown** モードは次のコマンドで確認できます。

```shell
cat /sys/kernel/security/lockdown
```

このファイルが存在し、モードが `[none]` 以外である場合、ユーザーメモリに書き込む Go ライブラリレベルのコンテキスト伝搬は利用できません。
ネットワークレベルの伝搬は、他の要件が満たされていれば引き続き動作します。

#### コンテナ化環境(Kubernetes を含む)における Go の分散トレーシング {#distributed-tracing-for-go-in-containerized-environments-including-kubernetes}

カーネル **lockdown** モードの制限により、**OBI の Docker コンテナ**ではホストシステムから `/sys/kernel/security/` ボリュームを Docker や Kubernetes の設定ファイルでマウントする必要があります。
これにより、OBI は Linux カーネルの **lockdown** モードを正しく判定できます。
以下に、OBI が **lockdown** モードを判定するのに十分な情報を持つようにする Docker Compose 設定の例を示します。

```yaml
services:
  ...
  obi:
    image: 'docker.io/otel/ebpf-instrument:v0.15.2'
    environment:
      OTEL_EBPF_CONFIG_PATH: "/configs/obi-config.yml"
    volumes:
      - /sys/kernel/security:/sys/kernel/security
      - /sys/fs/cgroup:/sys/fs/cgroup
```

`/sys/kernel/security/` ボリュームがマウントされていない場合、OBI は Linux カーネルが integrity モードで動作していないものとみなします。

### Go チャネルスパンリンク {#go-channel-span-links}

OBI は、Go チャネルを介したサポート対象のワークハンドオフに対して、実験的なレシーバー側のスパンリンクを発行します。
送信側と受信側の両方に OBI が生成したアクティブなスパンがある場合、受信側のスパンは送信側のスパンにリンクされます。
OBI はトレース ID、親子関係、送信側のスパンを変更しません。

この動作は、OBI がターゲットバイナリのチャネルランタイムオフセットを解決できる場合、Go 固有のトレーシングとともに自動的に有効になります。
`runtime.chansend1`、`runtime.chanrecv1`、`runtime.chanrecv2` を通じたバッファなしおよびバッファありの直接的なハンドオフがサポートされます。
`select` を通じたチャネル操作はサポートされません。
これらのプローブを無効にするには Go 固有のトレーサーを無効にしてください。チャネルリンク専用のオプションはありません。

OBI は `OTEL_SPAN_LINK_COUNT_LIMIT` を尊重し、無効、重複、自己参照のリンクを破棄します。

### Node.js の手動スパンのキャプチャ {#capture-nodejs-manual-spans}

OBI v0.12.1 以降、OBI はアプリケーションが OpenTelemetry SDK を登録していない場合に、Node.js アプリケーションが `@opentelemetry/api` を通じて生成するスパンをキャプチャできます。
OBI はこれらの手動スパンをトレースパイプラインを通じてエクスポートし、自動的にキャプチャされたサーバースパンと関連付けます。
アプリケーションが SDK を登録している場合、OBI はスパンの生成とエクスポートをその SDK に委ねます。

この機能はデフォルトで無効です。
既存の Config v1 デプロイメントでは `nodejs.manual_spans: true` または `OTEL_EBPF_NODEJS_MANUAL_SPANS=true` で有効にできます。
Config v2 は同等のフィールドを公開していません。
この機能のためだけに Config v1 を維持するのではなく、デプロイメントの Config v2 への移行を継続してください。

OBI が Node.js インスペクターに到達できる必要があり、プロセスが独自の `SIGUSR1` ハンドラーを登録してはなりません。
CommonJS ローダーが到達できないバンドルされた `@opentelemetry/api` のコピーはキャプチャされません。
自動クライアントスパンは現在、アクティブな手動スパンの子ではなく、同じサーバースパンの下で手動スパンの兄弟として扱われます。

### uvloop を使用した Python asyncio {#python-asyncio-with-uvloop}

OBI は、標準のイベントループまたは [`uvloop`](https://github.com/MagicStack/uvloop) を使用する Python asyncio のワークロードに対するコンテキスト伝搬をサポートしています。
GIL 有効の 64 ビット CPython 3.9 から 3.14 をサポートします。
フリースレッドビルドはサポートされていません。

ネットワークレベルでのコンテキスト伝搬は、標準のイベントループまたは `uvloop` 上で動作する Python アプリケーションに適用され、OBI は非同期処理に対してトレースコンテキストを自動的に計装・伝搬できます。
コンテキスト伝搬を有効にすること以外に、追加の設定は不要です(詳細は[はじめに](#introduction)を参照)。

`uvloop` では、`asyncio.start_server()` は accept パスがプローブ対象のタスクコンテキストなしに C で実行されるため、関連付けされません。
