この記事はAll About Group(株式会社オールアバウト) Advent Calendar 2022 の20日目の記事です。
はじめに
OpenTelemetryについて調べていて、Node.jsだけでなくブラウザにも対応していることを知りました。
これらを使えばブラウザからアプリケーションサーバー、APIの一連の処理をトレースできそう?と考えたので試してみました。
ローカル環境で試しましたが、この記事では解決できていないことが多くあります。
実際にOpenTelemetryを利用するときは公式ドキュメントをお読み下さい。
OpenTelemetryの概要
オブザーバビリティのキーワードで調べると主要な3要素として「ログ」「分散トレーシング」「メトリクス」が出てきます。OpenTelemetryでは、これらの3要素の計測からデータのエクスポートまでを標準化してツールの提供を進めています。
この記事では「分散トレーシング」を試してみます。
分散トレーシングは処理の全体像を理解したり、性能のボトルネックを特定することに役立てることができます。特に複数のサービスをまたいだリクエストの流れを追跡できるのが特徴的です。
OpenTelemetryの各言語ごとの開発進行状況はStatusのページをご確認ください。
OpenTelemetryの範囲外
OpenTelemetryは収集したテレメトリーデータの格納やWeb UIによる可視化は行いません。
トレーシングでは、JaegerやCloud Traceといった別のサービスと組み合わせて利用します。
TraceとSpan
開始時間と終了時間を持つ「Span」と呼ばれる処理単位の一連の流れ(Trace)を追うことで、
どのような処理が、どこから呼ばれて、どれくらいの時間をかけて進んだのかを把握することができます。
1つのTraceに複数の親子関係を持つSpanが関連づけられます。
Trace
|------------------------- RootSpan (100ms) -------------------------->
|------------- /users (60ms)------------> |---- /search (40ms) --->
|---DB (30ms)---> |---DB (20ms)---> |---- DB (30ms) ---->
Automatic Instrumentation(自動計装)
Traceを開始し、Spanを計測したいポイントごとに一つ一つ手動で実装することでデータを収集できますが、さまざまなユースケースに向けた「Automatic Instrumentation」と呼ばれるライブラリ群の開発も進められています。
参考:JavaScriptのAutomatic Instrumentation
Trace Context
各サービスでTraceやSpanを作るだけでなく、複数のサービス間で情報を伝搬する必要があります。
Trace Context Propagationと呼ばれる仕組みを利用して、TraceIDやParentSpanIDといったコンテキスト情報をサービス間で伝搬することで、分散環境下でも複数サービスのTraceを関連づけることができます。
ローカル環境で試してみる
下記のような流れをローカル環境で試してみました。
- ブラウザからアプリサーバーにリクエスト
- アプリサーバーからAPIサーバーにリクエスト
- 各トレースデータをOtel Collectorを経由してJaegerにエクスポートする
Request
[Browser] --> [Application Server] --> [API Server]
| ↓ |
+-------> [Otel Collector] <--------+
↓ Exporter
[Jaeger]
試してみて理解したこと
今回はHTTPリクエストの自動計装に、ブラウザではFetchInstrumentation、サーバー(Node.js)ではHttpInstrumentationを利用しました。
これらのInstrumentationにはサービス間のContextの自動受け渡し処理が組み込まれています。
Contextの受け渡しのための処理は特に記述しなくても、Auto Instrumentationを利用すれば、ブラウザ、Webアプリ、APIの間でトレースを紐づけることができました。
サーバーサイドの例(Node.js)
const sdk = new opentelemetry.NodeSDK({
serviceName: 'Application_Service',
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4318/v1/traces'
}),
// ↓↓↓ 自動計測「HttpInstrumentation」を登録 ↓↓↓
instrumentations: [new HttpInstrumentation()],
});
sdk.start();
app.get('/message', async (req, res) => {
const response = await axios.get('http://api:8080');
res.json(response.data);
})
ブラウザの例
const provider = new WebTracerProvider();
provider.register({ contextManager: new ZoneContextManager() });
const exporter = new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' });
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
// ↓↓↓ 自動計測「FetchInstrumentation」を登録 ↓↓↓
registerInstrumentations({
instrumentations: [
new FetchInstrumentation()
]
});
fetch('http://localhost:8080/message');
分からなかったこと
システム時刻の環境差異を吸収する方法が分からなかった
環境ごとのシステム時刻の調整が上手くいきませんでした。
特にブラウザは、ユーザーによって利用環境が異なることが想定されますが、環境差異があるままでは不整合が起きてしまいます。筆者のローカル環境では利用するブラウザによっても挙動が変わることがありました。
OpenTelemetryにカバーする仕組みがあるかもしれませんが、今回は見つけられていません。
ブラウザとサーバー間のトレース紐づけは、この問題の解決方法が分かってからにしたほうが良さそうです。