Istio+Jaeger / Istio+LightStep による分散トレーシング #1

Kenta Kosugi
18 min readDec 12, 2022

--

前回のエントリでマイクロサービス間の呼び出しの関係を図示する方法を試しました。Service Graph と言われているものです。

その際エントリの最後では istio-injection: true の時の Kubernetes 上の Pod の動きについて少し触れました。

本家サイトには、この点に関してのアーキテクチャ図が掲載されており、わかりやすかったので転載します。

Istio がない状態のアプリケーションの図

上記の図が Istio をインストールしていない状態のアーキテクチャ図です。サービスごとに開発言語が異なっています。実はこれは意図してそうなっています(Polyglot Programming と呼ばれているものです)。

Istio をインストールすると以下のような図になります。

Istio をインストールした状態で展開したアプリケーションの図

前回 default ネームスペースに istio-injection: true を設定したのですが、この設定をおこなうと、上記の画像のように 各 Pod に Envoy と呼ばれる Proxy コンテナがインジェクションされます。黒い四角形がそれにあたります。この Envoy は各コンテナに届いたリクエストをインターセプトします。そして本来処理するべきコンテナに代理でリクエストを渡しているのです。

前回のエントリでご紹介した Service Graph は Envoy がリクエスト・レスポンスを代理で処理しているからこそ描画できるというわけです。

Service Graph

Istio の他の機能

Envoy を注入する Istio は Service Graph だけが機能ではありません。別の機能もあります。

Circuit Breaker

Service Graph

たとえば、上記の図において、ratings アプリケーションがスタックし、 応答を返却できなくなった場合のことを考えてみましょう。そうすると、ratings を呼び出している reviews サービスもスタックし、さらに reviews サービスを呼び出している productpage もスタックしてしまいます。結果的にサービス全体が応答不能になってしまいます。

これは REST API や gRPC がリクエストの応答を待ち受ける性質を持っているために発生します。何も考えずアーキテクチャを描いて実装してしまうとこのように障害が伝播するような構成になってしまいます。

障害が伝播しないように、スタックした ratings サービスは現在は応答を受け付けられないよという意味のエラーを返す必要があります。これが Circuit Breaker です。

ライブラリとして有名なのは Netflix が開発した Spring Cloud Netflix Hystrix です。

しかし、Netflix の Circuit Breaker は Java でしか採用できません。すべてのサービスに必要な機能なのにもかかわらず、言語ごとに Circuit Breaker を実現可能なライブラリを調査して実装するのはコストがかかります。

そこで Istio の登場です。通信の代理を担っている Envoy がスタックを検知した場合、代わりにエラーを返却することで、障害の伝播を防ぐのです。開発者は何もする必要がありません。

少し脇にそれますが、Lean と DevOps の科学の書籍にもある通り、チームごとにツールや開発言語を自由に採用できるケイパビリティも組織のパフォーマンスを向上させる要因です。

多くの場合、アプリケーションとサービスは、監視、ログ記録、構成、ネットワーク サービスなどの関連する機能を必要とします。 これらの周辺タスクを、独立したコンポーネントまたはサービスとして実装できます。

このようにサービスにとって共通の機能を別のコンテナとして用意して横に並べることを Side-car パターンと呼びます。

ロードバランシング

ロードバランシングについては、こちらの Envoy のページを参照ください。reviews の動作を見ていれば、負荷分散の動きをしているのが直感的に理解できたはずです。

流量制御

前回のエントリで Kubernetes 上には同じアプリケーションであってもバージョンが異なるものをデプロイ可能だということを記載しました。実際、Istio を試してみるサンプルアプリでは、reviews アプリケーションは v1, v2, v3 の3バージョンデプロイされています。

仮説、検証型のビジネス、つまりリーン型のビジネスを推進している場合には、この複数のバージョンをデプロイするというのが非常に重要になってきます。

ABテスト、Blue Green デプロイメント、カナリアテストという用語を聞いたことはありますでしょうか。

新しいバージョンを試したい時に、同時に複数バージョンをデプロイし、そのうち新しいバージョンには 5 % の人だけ試して欲しいといったビジネス上の要求に答えるのがこの流量制御になります。

http://heidloff.net/article/sample-app-manage-microservices-traffic-istio/

流量制御を実施している場合の Service Graph

また、特定の人物のみ最新バージョンを表示させたいという要望、つまり Request Routing も HTTP のヘッダーをもとに簡単に実現することが可能です。

DX プラットフォームにはこの AB テスト、Blue-Green デプロイ、カナリアテストを実施できる機能は必須です。

Fault Injection

障害を意図的に注入することが可能となります。なんの得があるんだ?と思う方もいらっしゃるかもしれませんが、実際に Site Reliability Engineer (SRE) が本番の障害に対処できるのか等を試すことが可能です。

本番環境で意図的に障害を注入する手法をカオスエンジニアリングと呼びます。

特に Istio ではネットワークのアプリケーション層の障害を注入する際に利用可能です。アプリケーションに手を入れず、タイムアウト等の擬似的な障害を意図的に発生させます。

カオステスト は、弱点を明らかにし、フォールトトレランスを向上させるために、サービスを意図的に壊すプロセスです。カオステストでは、エラーを返す代わりに、クライアント側のバグを明らかにしたり、エラーを返すのではなくキャッシュされた結果を表示したいと思うようなユーザーが直面する問題の状況を特定したりすることができます。

Kubernetes環境では、Podをランダムに削除したり、ノード全体をシャットダウンしたりするなど、さまざまなレイヤーでのカオステストに取り組むことができます。

ただし、障害はアプリケーション層でも発生します。無限ループ、壊れたクライアントライブラリ-アプリケーションコードは無限に失敗する可能性があります!ここで Istio fault injection の出番です。Istio VirtualServicesを使用して、実際にアプリのコードを更新せずに、タイムアウトまたはHTTPエラーをサービスに起こすことにより、アプリケーションレイヤーでカオステストを実行できます。方法を見てみましょう。

認証・認可のインジェクション

Kubernetes への内部ネットワークは通常 Kubernetes 外からアクセスすることができません。しかし、だからと言って安心することはできません。特に内部犯の割合が他国に比べて圧倒的に多いのが日本です。

システムの内部通信であっても、完全には信用しないというゼロ・トラストセキュリティの考えのもと、マイクロサービス間の通信も暗号化するべきという考え方もあります。

ラベル istio-injection:true を設定すると、設定した Namespace 上のコンテナの通信を肩代わりする Envoy が Pod に自動注入されるという話をしました。Envoy 同士の通信を外部の SSO サービスと連携することで TLS 相互認証化することが可能になります。

https://www.slideshare.net/KentaKosugi/red-hat-integration/87

上記は認証・認可プロバイダとして Keycloak を利用しているケースです。

Open Telemetry

やっと本題です。

ドメイン駆動設計等を使用して、巨大なドメインを分割してサービス化すると、従来のモノリスシステムと比較して管理、運用が複雑化していきます。サービス間のつながりがすぐにわからなかったり、どのサービスのどの部分がボトルネックになっているのか、特定に時間を要することも容易に想像できます。

この時に必要になるのが OpenTelemetry です。

OpenTelemetry は、ツール、API、および SDK のコレクションです。これを使用してテレメトリ データ (メトリクス、ログ、およびトレース) を計測、生成、収集、およびエクスポートし、ソフトウェアのパフォーマンスと動作の分析に役立てます。

サービスからトレース、メトリクス、ログの情報を収集して、効率的に分析できるようになっています。

各サービスからトレース、メトリクス、ログの情報を収集する機能を Side-car として後からインジェクションするのが Istio の役目、実際に情報を収集して分析をするのが Observability Platform になります。

今回は OpenTelemetry のうち、分散トレーシングに注目してみたいと思います。実は前回の Istio の試行の際に、分散トレーシングを可視化するための OSS Jaeger も構成されています。今回は、この Jaeger を中心に触り、次回は OSS ではなく、商用の Lightstep による可視化をご紹介します。

分散トレーシング

分散トレーシング(Distributed Tracing) はマイクロサービスとして分散されたサービスの流れをエンドツーエンドで可視化して分析するための仕組みになります。

分散トレースとは、分散システムを介して伝播するリクエストを監視する方法を指します。これは、個々のユーザー要求を処理するために一連のサービスがどのように調整されているかを明らかにする診断手法です。単一のトレースは、通常、監視対象のアプリケーション内の個々のトランザクションまたはリクエストのアクティビティを、ブラウザまたはモバイル デバイスからデータベースに至るまで示します。全体として、トレースのコレクションは、ユーザー エクスペリエンスに影響を与えるため、どのバックエンド サービスまたはデータベースがパフォーマンスに最大の影響を与えているかを示すことができます。

概要を説明するよりも実際の画面を見た方が早いので、早速試していきます。

次のコマンドを実行して、Jaeger UI を開きます。

$ istioctl dashboard jaeger
http://localhost:16686

コマンド投入と同時に Jaeger が開きます。なんだか見たことのあるキャラクターが出現しました。Go 言語のマスコットキャラである Gopher なんでしょうか?

[Search] -> [Service] の箇所を [istio-ingressgateway.istio-system] に設定し、[Find Traces] を選択してみましょう。

service の特定

すると、いくつかの検索結果が出力されました。出現しない方は、前回のエントリの for 文を使用してリクエストを何回か呼び出す部分を再度実行してみてください。

左下に 8 spans とあったり、2 spans とあるものがあります。

In distributed tracing, a trace is a view into a request as it moves through a distributed system. Multiple spans represent different parts of the workflow and are pieced together to create a trace. A span is a named, timed operation that represents a piece of the workflow.

分散トレースでは、トレースは分散システム内を移動するリクエストのビューとなります。複数のスパンはワークフローの異なる部分を表し、トレースを作成するためにつなぎ合わされます。スパンとは、ワークフローの一部を表す、名前付きの時間指定された操作のことです。

トレースとスパンの関係は Lightstep のブログから画像を拝借すると以下のようになります。

このうち、8 spans と記載されているものをクリックしてみましょう。

すると、左側に以下のサービス名を確認することができます。基本的には <Service 名.namespace 名>で構成されていることがわかります。

  1. istio-ingressgateway.istio-system
  2. productpage.default
  3. details.default
  4. reviews.default
  5. ratings.default

一番上の istio-ingressgateway.istio-system をクリックしてみましょう。

istio-ingressgatway.istio-system

guid : x-request-id = 6526a479–94ac-95b4-a209–267eea8fc009 を確認することができます。これは HTTP のヘッダーに付与されているものの一つです。このヘッダーを後続の reviews や ratings の呼び出しの際も付与させることで、スパンがトレースの一部であることを示しています。

Duration が 26.61ms となっていることもここからわかります。

productpage.default のスパンが二つあることが確認できますが、一つ目のスパンは productpage.html 自体の描画であることに対して、二つ目の productpage.default のスパンは http://review:9080/review/0 という REST API 呼び出しであることがわかります。

これまで確認していた画面は Trace Timeline という画面でしたが、Jaeger の画面右上のプルダウンから「Trace Graph」を選択してみましょう。

画面左上を見ると実験的なラベルが貼ってあることがわかりますが、Trace Graph を描画することが可能です。

Trace Graph

同様に Trace Spans Table も確認します。各スパンごとの Duration を確認できるようになっています。

いかがでしたでしょうか。Istio + Jaeger を利用した分散トレーシングを体験できたのではないでしょうか。分散トレーシングを活用すると、どのサービスのどの部分がボトルネックになっているのかがすぐに特定できます。また複雑化した(複数バージョンがデプロイされた)マイクロサービスの運用において問題が発生した場合に、どの呼び出しで問題が発生したのかも特定することができるようになります。

次回は Jaeger ではなく、Lightstep を利用した分散トレーシングを試してみます。

--

--

Kenta Kosugi

Javaアプリケーションサーバーの開発からCORBA製品のサポート、QA、証券外務員(第一種免許)、ストレージ屋、アーキテクト、SaaS屋と一貫性のない道を歩んでいます。Red Hatに復帰しました。