「Odigos」でノーコードの分散トレーシングを実現する

2024年3月14日(木)
下村 俊貴
第4回の今回は、アプリケーションのソースコードを変更せずに分散トレーシングを実現できる「Odigos」について紹介します。

はじめに

こんにちは。3-shakeの下村俊貴(@toshikish)です。今回は、アプリケーション(以下、アプリ)のソースコード変更不要で分散トレーシングができる「Odigos」について紹介します。

OSS InsightのKubernetes Tooling コレクションでは、2023年10月のスター増加数で1位、プルリクエストやイシューの増加数でも上位にランクインしており、注目度が高まっています。

分散トレーシングと OpenTelemetry

まず、分散トレーシングおよびOpenTelemetryについておさらいしましょう。

分散システムのトレーシング

マイクロサービスアーキテクチャのような分散システムでは、個々のサービスが協調しながら全体のシステムを構成しています。個々のサービスごとに監視を設定していると、そのサービス内の現象を把握することはできますが、リクエストは複数のサービスにまたがって処理されるため、障害が発生した際に被疑箇所を把握することは容易ではありません。

そこで分散トレーシングという仕組みが登場しました。分散トレーシングは、複数のサービスにまたがるリクエストに対して、それぞれのサービス間でどのように呼び出されてどれくらいの時間で応答したかなどを追跡しやすくする仕組みのことです。

OpenTelemetryの登場

OpenTelemetryはトレース・メトリクス・ログのテレメトリ(遠隔計測)データを生成・管理する標準化された枠組みおよびツール群で、CNCFのプロジェクトになっています。特定のベンダーやツールに依存しないため、OpenTelemetryに準拠していれば利用者が所望のオブザーバビリティツールを選択できます。

OpenTelemetryでは、まずアプリがテレメトリデータを出力する状態にする必要があります。これは計装(instrumentation)と呼ばれます。その中でも想像しやすいのは、アプリに対してトレースしたい箇所にコードを追加する方法です。これは手動計装(manual instrumentation)と呼ばれます。特にマイクロサービスの数が多い場合は手動計装の工数がかなり大きくなり、分散トレーシングの導入のハードルが高くなりやすいと言えます。

自動計装による省力化

実は計装するのに必ずしもコードを追加する必要はありません。OpenTelemetryでは、いくつかの言語でコードの変更不要でテレメトリデータを取得できます。これは自動計装(automatic instrumentation)と呼ばれます。現時点で対応している言語は.NET、Java、JavaScript、PHP、Pythonです。Goはまだ正式版になっていませんが、eBPFを活用して自動計装する実験的プロジェクトが存在します。

自動計装の実現方法は言語によって異なります。ランタイム言語(一般的に実行の際にランタイムを必要とする言語)では各言語の機能を使いながら実行時に計装用のコードを注入します。Goのようなコンパイル言語ではコンパイルされてバイナリファイルとなり実行時に計装コードの注入ができないため、eBPFを活用して自動計装を試みます。

また、自動で取れるものは取りつつ、細かく把握したいものはコードを追加して計装するというように、自動計装と手動計装を組み合わせることもできます。

Odigosの概要

OdigosはKeyval社が開発する、Kubernetes上で動くアプリに対して、自動計装により分散トレーシングを実現するOSSです。本記事公開時点の最新版はv1.0.46で、以下の特徴があります。

  • Java、Python、.NET、GoおよびJavaScriptで作られたアプリに対応しています。
  • 収集したデータはOpenTelemetryに対応した主要なオブザーバビリティツールに送信できます。
  • セルフホストできるOSS版が公開されているほか、将来的にSaaS版も提供されるようです。
  • OSS版はApache License 2.0で提供されており、商用利用が可能です。

なお、v1に到達していますが、公式ドキュメントのArchitectureページでは 「Odigos is still in beta」と書かれており、また現状は後述のように不具合も含まれているので、本番利用する際は注意が必要です。

digosの仕組み

OdigosはInstrumentor、Autoscaler、Scheduler、UIの4個のマイクロサービスで構成されています。

Odigosのアーキテクチャ(公式ドキュメントより)

Instrumentorはアプリに手動でのコード追加不要で、自動で計装する役割を担っています。アプリのファイル構造や環境変数名から言語を特定し、Python、Java、.NET、JavaScriptなどのランタイム言語であればOpenTelemetryに準拠した言語ごとの自動計装を設定し、Goなどのコンパイル言語であればeBPFを使って計装します。

  • 具体的には、DaemonSetを利用してNodeごとに配置したOdigletから、アプリの言語検出・計装・収集を行っています。
  • Goから生成されたバイナリへの自動計装に使われているGo automatic instrumentationは、Odigosの開発元であるKeyvalによりCNCFに寄贈されたようです(前章で紹介したものと同じ)。

Autoscalerはアプリからのデータを収集してバックエンドに送るコレクタを管理します。データ量の変動に応じてコレクタをオートスケールさせます。Scheduler は自動計装されたアプリとコレクタを紐づけます。

UIは対象のアプリを選択したりデータの送信先バックエンドを選択したりする画面を提供します。odigos-uiという名前のServiceをポートフォワードしてWebブラウザで表示します。

自動計装されるトレースの内容とカスタマイズ

自動計装で追加されるトレースのスパンは、言語ごとに以下の様々なライブラリに対応しているため、HTTP・gRPC通信やデータベース(SQL)クエリを追うことができます。

  • HTTPクライアント・サーバー
  • gRPCクライアント・サーバー
  • データベースクライアント

また、公式ブログの記事のようにOpenTelemetry APIを用いて注目したいスパンを手動で計装すると、自動計装のスパンと合わせてトレースを表示できます。

Odigosのインストール

KubernetesクラスタへのOdigosのインストールは、Odigos CLIodigos installコマンドで簡単にできます。

ただし、コンテキストは~/.kube/configファイルに指定されたものが利用されるため、kubieなどのように環境変数で指定する場合は意図しないクラスタにインストールされることがあるので注意が必要です。コンテキストが異なる場合はkubectl config set-contextコマンドで切り替えるか、--kubeconfigオプションでconfigファイルを直接指定する必要があります。

GitHub上ではHelm Chartも用意されています。かつては公式のインストール方法として提供されていたようですが、CLIに移行してからはメンテされなくなり、ドキュメント上でも言及されなくなりました。実用するには手を加える必要がありますが、Issueによるとコントリビューションは歓迎のようです。

OpenTelemetryの自動計装と比較したOdigosの特徴

Kubernetes上にデプロイするアプリに計装する場合を考えます。

OpenTelemetryとOdigosでは、最終的に自動計装してデータを収集するという目的はどちらも達成できます。どちらも手動計装と組み合わせが可能で、自動計装対象の言語も変わりません。

OpenTelemetryの場合、Operatorを利用することになります。カスタムリソースであるCollector、Instrumentationを定義し、アプリのPodごとに自動計装対象のアノテーションを付与する必要があります。

Odigosの場合、自動計装対象のアプリ選択とデータ送信先バックエンドの設定がGUIで完結します。言語検出機能があるので、言語ごとのアノテーション付与も不要です。データ収集までの手間は比較的少ないと言えます。

サンプルアプリを使ったOdigosの検証

それでは、実際に動かしてみます。筆者はAmazon EC2インスタンス上のAmazon Linux 2023にkindをインストールして動作させてみました。なお、前述のOdigosのインストールができているものとします。検証に使用したバージョンはv1.0.9でした。

まず、複数のマイクロサービスで構成されるサンプルアプリをインストールします。次に、実行中のアプリについてOdigosでメトリクス・ログ・トレースを生成し、各要素の集約ツールであるPrometheusGrafana LokiGrafana Tempoで受け取り、Grafanaで可視化する流れです。

サンプルアプリのインストール

まずはサンプルアプリをインストールします。これは公式で用意されているもので、Google Cloudがデモ用に提供するものから計装用のコードが取り除かれています。

kubectl apply -f https://raw.githubusercontent.com/keyval-dev/microservices-demo/master/release/kubernetes-manifests.yaml

Grafanaスタックのインストール

次に、Prometheus、Loki、Tempo、Grafanaをインストールします。少し前のバージョンですが、これらも公式で用意されているものを利用します。

helm repo add observability https://keyval-dev.github.io/charts
helm install observability oss-observability -n observability --create-namespace

こちらも起動が完了するまで待ちます。

kubectl get pos -n observability
NAME                                               READY   STATUS    RESTARTS   AGE
observability-grafana-696488b9d9-7nf6d             1/1     Running   0          88s
observability-loki-0                               1/1     Running   0          88s
observability-prometheus-server-6c65777696-9mmkh   2/2     Running   0          88s
observability-tempo-0                              2/2     Running   0          88s

これらを自前で設定する場合は、設定ファイルを参考にしてください。例えば、Prometheusでは、以下のようなフィーチャーフラグを有効にする必要があります。

  • --web.enable-remote-write-receiver:Prometheusのメトリクス収集は通常Pull型ですが、今回はOdigosからPrometheusに直接書き込むPush型になります。
  • --enable-feature=exemplar-storage:メトリクスにトレースIDを紐付けるために必要です。

Odigosの設定

それでは、Odigosの設定に移ります。まず、odigos uiコマンドを実行します。ローカル環境からブラウザで http://localhost:3000 にアクセスすると、ダッシュボードが表示されます。

「Choose Source」でアプリの一覧が表示されるので、収集対象のアプリをクリックして選択します。

次に、データの送信先を設定します。[Next]をクリックすると「Choose Destination」に進み、送信先を選択できるようになります。

最終的に下表の3種類を設定しますが、セットアップ画面では1種類しか設定できないので、まずPrometheusを設定します。Destination NameとEndpointを入力します。

選択する
Destination
Destination Name Endpoint
Prometheus prometheus observability-prometheus-server.observability:80
Loki loki observability-loki.observability:3100
Tempo tempo observability-tempo.observability

[Create Destination]をクリックすると送信先の作成が完了し、Overview画面に遷移します。左側にあるサイドバーの[Destinations]をクリックし、右上の [Add New Destination]をクリックして、残り2種類(Loki、Tempo)も作成します。

最終的にOverview画面に戻ると、次のような表示になります。

ちなみに、以上の手順で設定した内容はConfigMapに保存されています。設定方法のドキュメントがないためUIを使わずに記述するのは難しそうですが、もしかすると開発環境のUI上で作成した設定を手直しして本番環境に持ってくるような使い方はできるかもしれません(未検証)。

Grafanaでの可視化

設定後しばらくすると、loadgeneratorというアプリが様々なリクエストを発行したデータが蓄積されるため、Grafanaで可視化できるようになります。

GrafanaのServiceをポートフォワードし、ローカル環境からブラウザで http://localhost:8080 にアクセスします。

kubectl port-forward -n observability svc/observability-grafana 8080:80

ログイン画面が表示されるので、ユーザー名はadminを、パスワードは以下の実行結果を入力し、ログインします。

kubectl get secret -n observability observability-grafana -o jsonpath={.data.admin-password} | base64 --decode

サービス間グラフ

ログイン後、サイドバーの[Explore]をクリックし、データソースとしてプルダウンリストから「Tempo」を選択します。Query typeとして「Service Graph」を選択し、右上の[Run query]をクリックすると、画面中央下部に「Node graph」としてアプリのサービス間通信のグラフが表示されます。

実際に通信が発生しているサービスのみノードとして表示されるので、Odigosで選択したアプリすべてが表示されているわけではないことに注意してください。また、不具合によりNode.jsで作られたアプリの自動計装に失敗しているため、それも表示されていません。

メトリクス

サービス間グラフから、関連する一部のメトリクスが参照できます。グラフ中のサービスのノード(例えば、cartservice)をクリックするとメニューが表示されます。[Request rate]を選択すると、右側にPrometheusのデータソースでそのサービスに対するリクエストレートが表示されます。

その他にも下表のようなメトリクスが取れ、Prometheusデータソースから閲覧できます。

分類 メトリクス例 メトリクス名接頭辞の例
アプリ HTTPリクエスト、レイテンシー、DB接続数 trace_
ランタイム GC、スレッド、ヒープ process_runtime_
ホスト CPU、メモリ、ディスク k8s_node_、k8s_pod_、container_

トレース

今度は、サービス間グラフのメニューから[Request histogram]を選択します。クエリ下の「Options」を開き[Exemplars]をオンにすると、グラフ上にリクエスト中の代表となる点が複数表示されます。特に外れ値というわけではなく、直近のトレースが選定されるようです。

いずれかの点にマウスオーバーすると、そのリクエストに関する情報が表示されます。「traceID」の横に表示される[Query with Tempo]ボタンをクリックすると、Tempoデータソースでトレースが表示されます。

GrafanaのTempoデータソースにはトレースからログへの連携(トレースの横に該当ログへのリンクを表示する)機能があり、以下のような設定を書くと各トレースの右にドキュメントアイコンが表示され、ログへのリンクとして使えるようになります。これは前述の「Grafanaスタックのインストール」で紹介した設定ファイルに書かれています。

# /grafana/datasources/datasources.yaml/datasources/2
      - name: Tempo
        type: tempo
        uid: Tempo
        access: proxy
        url: "http://observability-tempo.observability:3100"
        jsonData:
          httpMethod: GET
          tracesToLogs:
            datasourceUid: 'Loki'
            tags: ['k8s.pod.name']
            mappedTags: [{ key: 'k8s.pod.name', value: 'k8s_pod_name' }]
            mapTagNamesEnabled: true
            spanStartTimeShift: '-1h'
            spanEndTimeShift: '1h'
            filterByTraceID: true
            filterBySpanID: false
          serviceMap:
            datasourceUid: 'Prometheus'
          search:
            hide: false
          nodeGraph:
            enabled: true
          lokiSearch:
            datasourceUid: 'Loki'
Grafanaのvalues.yamlの例

しかし、現状取得できているトレースのTagsにPod名など絞り込みに使える共通のタグキーがないので、トレースからログへの遷移ができていません。

ログ

Tempoトレースからの遷移はできていませんが、Lokiで直接手動でトレースIDに紐づくログを検索することはできます。Lokiデータソースを選択し、トレースIDをコピーした上で、以下のようなクエリを実行すれば該当するログが出力されます。

{k8s_namespace_name="default"} |= "検索したいトレースID"

現状アプリの標準出力と標準エラー出力がログとして収集されるようです。

おわりに

今回はOdigosについて紹介しました。自動計装によりコード追加なしで基本メトリクスを取得したりトレースをグラフで表示したりできるのに加え、言語検出機能により設定が簡単なので、まだ分散トレーシングを導入していない場合は、工数を抑えながら導入するのも選択肢としてありだと思いました。

一方で、未実装機能や不具合も散見されるので、安定した動作はまだ先になりそうです。

また、自動計装できないアプリ独自のメトリクスやログの出力は、引き続き手動で実装する必要があります。自動計装できるものは任せて、それ以外は従来通り手動で実装する、という使い分けもできるかもしれません。

【参考】

株式会社スリーシェイク Sreake事業部
Webバックエンドエンジニア、自社サービスのクラウドアーキテクトを経て、2021年3月にスリーシェイクに入社。主にAWS環境においてSRE支援業務に携わっている。

連載バックナンバー

仮想化/コンテナ技術解説
第4回

「Odigos」でノーコードの分散トレーシングを実現する

2024/3/14
第4回の今回は、アプリケーションのソースコードを変更せずに分散トレーシングを実現できる「Odigos」について紹介します。
仮想化/コンテナ技術解説
第3回

「Inspektor Gadget」でKubernetesクラスタをデバッグする

2024/1/24
第3回の今回は、eBPFを利用してKubernetesクラスタをデバッグする「Inspektor Gadget」について紹介します。
仮想化/コンテナ技術解説
第2回

ドメインを考慮した柔軟なPodの配置を実現する「Balancer」

2023/12/6
第2回目の今回は、ドメインを考慮した柔軟なPodの配置を実現する「Balancer」について紹介します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています