Dapr の非同期ワークフローオブザーバビリティの改善
この記事では、クラウドネイティブコミュニティのコントリビューターたちが協力して Dapr の OpenTelemetry 統合を強化した経緯、特に非同期ワークフローに関する取り組みについて紹介します。 また、OpenTelemetry Weaver を使用して Dapr を OpenTelemetry セマンティック規約に準拠させる継続的な取り組みや、このコラボレーションが他の CNCF プロジェクトにとって有用な事例となりうることについても触れます。 この作業は正式なイニシアチブとして始まったわけではありません。 議論、実験、そしてテレメトリーをより理解しやすく、エコシステム全体でより一貫性のあるものにするという共通の目標を通じて生まれたものです。
複雑なオーケストレーション全体にわたるトレース伝搬の課題
Dapr のワークフローエンジンは、長時間実行される同期・非同期オーケストレーションを実装するための簡単な方法を提供します。 ワークフローオーケストレーションは Dapr サイドカー内で実行され、ワークフローとアクティビティのコードは Dapr SDK を使用してアプリケーション内で実行されます。 これらの間の通信は、長時間維持される gRPC ストリームを介して行われます。
これは効率的ですが、W3C Trace Contextの伝搬を困難にします。 HTTP やUnary gRPC 呼び出しは、各リクエストに traceparent および tracestate ヘッダーを自然に運びます。 長時間維持されるストリームはそうではありません。 ストリームが開かれると、ワークフローのステップは新しいメタデータを付与できません。 これは、ワークフローエンジンがサイドカー内で正しいコンテキストを持つスパンを作成できたとしても、アクティビティメッセージが親コンテキストなしでアプリケーションに到達することを意味します。 その結果、アクティビティ内のアウトバウンド呼び出しは独自のトレースを作成します。
結果として断片化が生じます。 ワークフローのスパン、アクティビティのスパン、ユーザーレベルのスパンがトレーシングバックエンドに表示されますが、一貫した階層を形成しません。

この図は問題を示しています。 ワークフローエンジンがスパンを生成しているにもかかわらず、gRPC ストリーミング境界でコンテキストが途切れるため、アクティビティコードはワークフローのトレースに接続できません。
典型的なワークフローオーケストレーションの複雑さ
ワークフローは表面上は単純に見えますが、トレースを始めると、構成要素の多さが明らかになります。 ワークフローは単一のリクエストではありません。 決定と状態遷移の長期にわたるシーケンスであり、それぞれが異なるダウンストリームシステムと連携する可能性があります。 これらの連携の一部は同期的です。 たとえば、アクティビティが外部サービスを呼び出して結果を待つ場合です。 その他は非同期的で、作業のスケジューリング、外部シグナルの待機、タイマーの発火待ちなどがあります。 これらの違いは、リクエストがシステムをどのように流れるかを変え、トレースがどのように見えるべきかを決定するため、重要です。
すべてのワークフローは独自のアイデンティティも持っています。 ワークフローインスタンス ID は、時間的に離れていてもすべてのステップを概念的に結びつけます。 ワークフローは数秒から数時間にわたって実行される場合があります。 障害後に再ハイドレードされたり、サーバー再起動後に再開されたりすることもあります。 永続的実行とは、ワークフローエンジンが状態を永続化し、イベントをリプレイし、外部条件が変化したときにワークフローを進めることを意味します。 トレーシングの観点からすると、これは通常のリクエストやバックグラウンドジョブとは大きく異なります。 単一のワークフロー実行は、多くのネットワーク呼び出し、待機期間、リトライ、部分的な進行にまたがることがあります。
したがって、ワークフローのトレースとは、即座に実行される同期呼び出しと、ワークフローエンジンが作業をスケジュールまたは再開する非同期境界の両方をキャプチャすることを意味します。 あるプロセスで実行されるユーザーコードと、別のプロセスで実行されるオーケストレーションロジックを関連付けることを意味します。 そして、永続的な境界を越えてコンテキストを維持し、一連の孤立した操作ではなく、ワークフローの実際のライフサイクルをトレースが反映するようにすることを意味します。
これらの特性がワークフローのトレースを困難にしています。 同時に、価値あるものにもしています。 トレースがオーケストレーションを正確に表現すると、ワークフローが時間の経過とともにどのように動作し、各ステップがどのように影響し合うかを理解するための強力なツールになります。
ワークフロー境界を越えたコンテキストの復元
この問題に対処するには、複数のリポジトリにまたがる協調的な変更が必要でした。 最初のステップは、ワークフローオーケストレーションの追跡に使用されるライブラリである durabletask-go が、ストリームを介して送信する前にワークフローアクティビティメッセージに W3C コンテキストをシリアライズできるようにすることでした。
traceparent と tracestate を直接埋め込むことで、ワークフローエンジンはメッセージごとの gRPC メタデータに頼らずにコンテキストを伝搬できます。
アプリケーション側では、durabletask-java SDK が、このコンテキストを読み取り、アクティビティコードの実行前に復元するように更新されました。 この実装例は探索的ブランチで確認できます。
アプリケーション内でコンテキストが復元されると、OpenTelemetry の Java 自動計装が効果を発揮します。 OpenTelemetry Java エージェントが有効な親コンテキストを認識するため、アウトバウンド HTTP 呼び出しやその他の操作を自動的に計装できます。 アプリケーションコードの変更は不要です。
OpenTelemetry Operator は、これらの実験において、ワークフローアプリケーションコンテナに Java エージェントを自動的に注入することで重要な役割を果たしました。 これにより、計装を手動で設定することなく、コンテキスト伝搬の理解と改善に集中できました。
最後に、Dapr ランタイムは、開発者にとっての明確さを向上させることを目的として、ワークフロースパン間の明確な親子関係を優先するように改善されました。 特定の非同期関係をスパンリンクでより適切に表現できる箇所の評価には、さらなる作業が必要です。
Jaeger での新しい表示
これらの変更が揃うと、Jaeger での改善は非常に明確でした。 以前は、インバウンドリクエスト、ワークフローオーケストレーター、各アクティビティのアウトバウンド呼び出しが別々のトレースとして表示されていました。
変更後、Jaeger のウォーターフォールは単一の連続したトレースを表示します。 インバウンドリクエストが最上部にまたがります。 ワークフローオーケストレーターのスパンがその下に表示されます。 各アクティビティはワークフロースパンの下にネストされます。 アウトバウンド呼び出しは対応するアクティビティの下に表示されます。
ワークフローはようやく1つのまとまったライフサイクルとして表示されるようになりました。

連続したウォーターフォールにより、ワークフローの構造だけでなくペースも理解しやすくなります。 待機期間、リトライ、状態遷移、長時間実行される操作が、あるべき場所に正確に表示されます。
トレースの連続性からセマンティックな整合性へ
Dapr のワークフローが完全なトレースを生成するようになると、新たな疑問が浮かびました。 これらのスパンは何を表すべきか、そしてどのように名前を付けるべきか。 Dapr はタイマー、状態ストア、Pub/Sub ブローカー、バインディング、シークレット、構成 API など多くのコンポーネントと連携します。 安定したセマンティクスがなければ、異なる SDK やランタイムコンポーネントが類似の操作を異なる方法で表現する可能性があります。
これに対処するため、Dapr はセマンティック規約を機械可読な形式で保存できる OpenTelemetry Weaver の採用を進めています。 この初期 PR は Weaver を Dapr に導入するものです。
Weaver は、Dapr がワークフロー、状態の操作、コンポーネント呼び出し、Pub/Sub 配信などのテレメトリー属性を定義する単一の場所を提供します。 これにより、SDK 間の動作を統一し、Dapr をより広範な OpenTelemetry セマンティックモデルに整合させることができます。 これはこの採用の始まりに過ぎず、残りの Dapr の操作を整合させるための作業がまだあります。
非同期動作のより良いモデリング
改善されたワークフロートレースをレビューする中で、Dapr がスパン種別を使用して非同期実行をより適切に表現する方法についても検討を始めました。 これまで、ワークフローエンジンはワークフロー関連のスパンをほぼすべてクライアントスパンとして出力していました。 これはエンジンが操作を呼び出しているという事実を反映していますが、作業のスケジューリングと実際の実行の違いを明確に表現していません。 この違いは、ワークフロートレースが連続的で読みやすい階層を形成するようになると、より重要になります。
非同期システムでは、作業がスケジュールされる際にはプロデューサーセマンティクスがより明確なシグナルを提供し、その作業が実行される時点ではコンシューマーセマンティクスが表現できます。 アクティビティコード内で行われるアウトバウンド呼び出しはクライアントスパンのままであり、内部のワークフロー遷移はインターナルスパンのままです。 このアプローチにより、トレーシングツールがワークフローの動作をより自然に伝えることができます。
これらの変更は現在の Dapr には含まれていません。 これはトレースの改善から生まれた初期の探索であり、Dapr のメンテナーと議論を始めた方向性です。 目標は Dapr のテレメトリーを再設計することではなく、非同期の動作をより正確に反映し、OpenTelemetry のセマンティックガイダンスに沿ったモデルの形成を始めることです。 より多くのコントリビューターが参加する中で、この作業は協力的に発展していくことが期待されます。
クラウドネイティブエコシステム全体でこの作業が重要な理由
このコラボレーションは Dapr に焦点を当てたものでしたが、根本的な課題は多くのクラウドネイティブプロジェクトに共通しています。 プロキシ、サービスメッシュ、イベントルーター、ワークフローエンジン、コントローラーは、同様の問題に取り組んでいます。 サイドカーや非 HTTP パスを通じた W3C コンテキストの伝搬方法、セマンティック規約の定義方法、非同期動作のモデリング方法、SDK の一貫性の確保方法です。
プロジェクト間のコラボレーションがこれらの問題の解決に役立ちます。 Linkerd や Traefik でも同様の改善作業が行われています。 OpenTelemetry セマンティクスに整合する新しいプロジェクトが増えるたびに、複数のコンポーネントからのシグナルに依存するオペレーターの全体的な体験が向上します。
今後の展望
CNCF のエコシステム全体で OpenTelemetry の採用が増加する中、プロジェクトメンテナーをどのように支援するかについて、OpenTelemetry コミュニティでより広範な議論を行う時期に来ています。 多くのメンテナーがテレメトリーを改善したいと考えていますが、どこから始めればよいかわからない場合があります。 スパン種別の選定、Weaver の採用、非同期操作のモデリングなどのトピックに関するガイダンスを必要としているメンテナーもいます。
この議論は正式なワーキンググループとして始める必要はありません。 非公式な場でも、プロジェクトがアプローチを比較し、ベストプラクティスを共有し、すでに他で解決されたパターンを再発明することを避ける助けになります。 Dapr と OpenTelemetry のコントリビューター間の作業は、コミュニティが協力することでどれだけ前進できるかを示しています。
CNCF プロジェクトのメンテナーやコントリビューターで、OpenTelemetry 統合を強化したいと考えている方は、ぜひ議論に参加してください。 共有されたテレメトリーは、より予測可能で、より解釈しやすく、より運用しやすい分散システムの構築に貢献します。