[[272944]] 現在、ますます多くのアプリケーションがマイクロサービスに基づくクラウドネイティブ アーキテクチャに移行しています。マイクロサービス アーキテクチャは非常に強力ですが、特にアプリケーションのデバッグ方法や複数のサービス間の呼び出し関係とステータスの監視方法など、多くの課題も伴います。マイクロサービス アーキテクチャを効果的に監視する方法は、マイクロサービス アーキテクチャの運用と保守を成功させる鍵となります。ソフトウェア アーキテクチャの用語では、マイクロサービス アーキテクチャの可観測性を強化することを意味します。
マイクロサービスの監視には、主に次の 3 つの側面が含まれます。 - ログを収集してシステムや各サービスの稼働状況を監視する
- メトリクスを収集してシステムと各サービスのパフォーマンスを監視する
- 分散トレースを使用すると、さまざまな分散コンポーネントでサービス要求がどのように処理されるかを詳細に追跡できます。
ログとメトリックの収集と監視について、誰もがより詳しく知ることができます。一般的なログ収集アーキテクチャには、Fluentd を使用してシステム ログを収集し、ELK または Splunk を使用してログを分析することが含まれます。パフォーマンス監視の場合、Prometheus が一般的で人気のある選択肢です。 分散トレースはますます多くのアプリケーションに採用されています。分散トレースは、マイクロサービスの呼び出しチェーンを追跡することで、サービス要求からマイクロサービス間のやり取りまでの呼び出しプロセス全体のビューを構築できます。ユーザーは、アプリケーション呼び出しの待ち時間、ネットワーク呼び出し (HTTP、RPC) のライフ サイクル、システム パフォーマンスのボトルネックなどの情報を知ることができます。では、分散トレースはどのように実装されるのでしょうか? 1. 分散トレースの概念 2010 年 4 月、Google は「Dapper、大規模分散システム トレース インフラストラクチャ」(http://1t.click/6EB) というタイトルの論文を公開し、分散トレースの概念を紹介しました。
分散トレースには、主に次の概念があります。 - トレース: 分散マイクロサービスコラボレーションによってサポートされるトランザクションです。トランザクションを処理した各サービス要求を含むトレース。
- Span: Span はトランザクション内のワークフローです。 Span には、タイムスタンプ、ログ、タグの情報が含まれます。スパンは親子関係、またはマスターとスレーブ (フォローアップ) 関係を含みます。
- Span コンテキスト: Span コンテキストは、分散トレースをサポートするための鍵です。呼び出されたサービス間で渡すことができます。コンテキストの内容には、あるサービスから別のサービスまでの時間、トレース ID、スパン ID、および上流サービスから下流サービスに渡す必要があるその他の情報が含まれます。
2. OpenTracing 標準の概念 OpenTracing (http://1t.click/6tC) は、Google が提案したオープンな分散トレース標準を定義する概念です。 スパンは分散トレースの基本的な構成要素であり、分散システムにおける単一の作業単位を表します。各 Span には他の Span への参照を含めることができます。複数のスパンが一緒になってトレースを形成します。
OpenTracing 仕様では、各 Span には次の内容が含まれると定義されています。 - 操作名。操作の内容を示します。
- タグ: タグは名前と値のペアです。ユーザーは追跡に意味のあるあらゆる情報を追加できます。
- ログ、ログも名前と値のペアとして定義されます。デバッグ情報や関連スパンの関連情報をキャプチャするために使用されます
- スパンコンテキスト (SpanContext) はどうでしょうか? SpanContext は、サブマイクロサービス システムの境界を越えてデータを転送する役割を担います。主に 2 つの部分から構成されます。
- トレースIDやスパンIDなど、実装に関係のないステータス情報
- 手荷物。マイクロサービスの呼び出しをある都市から別の都市へのフライトに例えると、SpanContext は飛行機で運ばれるコンテンツと考えることができます。 Trace ID と Span ID はフライト番号のようなもので、手荷物項目は発送される荷物のようなものです。各サービスコールごとに、ユーザーは異なる手荷物を送ることを決めることができます。
以下は Span の例です。 - t=0 操作名: db_query t=x
- +
- | · · · · · · · · · · · スパン · · · · · · · · · · · · |
- +
- タグ:
- - db.instance:"jdbc:mysql://127.0.0.1:3306/customers
- - db.statement: "SELECT * FROM mytable WHERE foo='bar';"
- ログ:
- - メッセージ: 「'127.0.0.1'(10061) の MySQL サーバーに接続できません」
- スパンコンテキスト:
- - トレースID: "abc123"
- - スパンID: "xyz789"
- - 手荷物:
- - 特別ID: "vsid1738"
分散トレースを実現するには、SpanContext をどのように渡すかが鍵となります。 OpenTracing は、SpanContext を挿入および抽出するための Inject と Extract という 2 つのメソッドを定義します。
疑似コードを挿入する - span_context = ...
- アウトバウンドリクエスト = ...
- # (組み込みの) HTTP_HEADERS キャリア形式を使用します。私たちは
- #キャリア事前分布として空のマップを使用して開始します に
- # `tracer.inject`を呼び出します。
- キャリア = {}
- tracer.inject(span_context、opentracing.Format.HTTP_HEADERS、キャリア)
- # `carrier` には、渡す(不透明な)キー:値のペアが含まれるようになりました
- # すでに使用しているワイヤ プロトコルを介して通信します。
- のために キャリア内のキー、値:
- outbound_request.headers[キー] =エスケープ(値)
ここでの注入プロセスは、すべてのコンテキスト情報を Carrier と呼ばれる辞書に書き込み、辞書内のすべての名前と値のペアを HTTP ヘッダーに書き込むことです。 擬似コードを抽出する - 受信リクエスト = ...
- # ここでも (組み込みの) HTTP_HEADERS キャリア形式を使用します。によると
- # HTTP_HEADERSドキュメントでは、余分なデータを含むマップを使用できます
- #そこにOpenTracing実装がサブセットを探すようにします
- #の 必要なキー:値のペア。
- #
- #そのため、キー:値`inbound_request.headers`を直接使用します
- #キャリアとしてマップします。
- キャリア = inbound_request.headers
- span_context = tracer.extract(opentracing.Format.HTTP_HEADERS、キャリア)
- # span_context を指定してトレースを続行します。例えば、
- span = tracer.start_span( "..." 、child_of=span_context )
- # (`carrier` がトレースデータを保持している場合は、`span` が使用できるようになります。)
抽出プロセスは、注入の逆のプロセスであり、キャリア、つまり HTTP ヘッダーから SpanContext を構築します。 全体のプロセスは、クライアントとサーバー間で送信されるデータのシリアル化および逆シリアル化のプロセスに似ています。ここでのキャリア辞書は、文字列型のキーと文字列またはバイナリ形式 (バイト) の値をサポートします。 3. エネルギーをどのように使うのか? さて、私は多くの概念について話しました。プログラマーとして、あなたはすでにせっかちです。関係のないことについて話さないでください。コードを見てみましょう。心配しないでください。Tracing の使い方を見てみましょう。 OpenTracing の仕組みを説明するために、プログラマーの間で人気のある「hello world」を出力する Python アプリケーションを使用します。 クライアントコード - 輸入リクエスト
- インポートシステム
- インポート時間
- lib.tracingからinit_tracerをインポートする
- opentracing.extからタグをインポート
- opentracing.propagationからのインポート形式
- def say_hello(hello_to):
- tracer.start_active_span( 'say-hello' )をスコープとして設定:
- スコープ.span.set_tag( 'hello-to' , hello_to)
- hello_str = フォーマット文字列(hello_to)
- print_hello(hello_str)
- def format_string(hello_to):
- tracer.start_active_span( 'format' )をスコープとして設定:
- hello_str = http_get(8081, 'フォーマット' , 'helloTo' , hello_to)
- scope.span.log_kv({ 'イベント' : '文字列形式' , '値' : hello_str})
- hello_strを返す
- def print_hello(hello_str):
- tracer.start_active_span( 'println' )をスコープとして:
- http_get(8082, 'publish' , 'helloStr' , hello_str) を実行します。
- スコープ.span.log_kv({ 'イベント' : 'println' } )
- def http_get(ポート、パス、パラメータ、値):
- url = 'http://localhost:%s/%s' % (ポート、パス)
- スパン = トレーサー.active_span
- span.set_tag(tags.HTTP_METHOD, 'GET' )を設定します。
- span.set_tag(タグ.HTTP_URL, url)
- span.set_tag(タグ.SPAN_KIND、タグ.SPAN_KIND_RPC_CLIENT)
- ヘッダー = {}
- tracer.inject(span, Format.HTTP_HEADERS, ヘッダー)
- r = リクエスト.get(url, パラメータ = {パラメータ: 値}, ヘッダー = ヘッダー)
- r.status_code == 200 をアサートする
- r.textを返す
- #主要
- len(sys.argv) == 2をアサートする
- トレーサー = init_tracer( 'hello-world' )
- hello_to = sys.argv[1]
- こんにちはと言う(hello_to)
- #スパンのフラッシュにはIOLoopを使用します
- 時間.sleep(2)
- トレーサーを閉じる()
クライアントは次のタスクを完了します。 - トレーサーを初期化します。トレース名は「hello-world」です。
- クライアント操作say_helloを作成し、その操作を「say-hello」という名前のSpanに関連付け、span.set_tagを呼び出してタグを追加します。
- say_hello 操作では、最初の HTTP サービス A (format_string) が呼び出されます。この操作は、「format」という名前の別の Span を関連付け、span.log_kv を呼び出してログを追加します。
- 次に別のHTTPサービスB、print_helloを呼び出します。この操作は「println」という名前の別のSpanに関連付けられており、span.log_kvを呼び出してログを追加します。
- 各 HTTP リクエストに対して、http メソッド、http URL、および span の種類を示すタグが Span に追加されます。そして、tracer.inject を呼び出して、SpanContext を http ヘッダーに挿入します。
サービスAコード - FlaskからFlaskをインポート
- フラスコのインポートリクエストから
- lib.tracingからinit_tracerをインポートする
- opentracing.extからタグをインポート
- opentracing.propagationからのインポート形式
- アプリ = Flask(__name__)
- トレーサー = init_tracer( 'フォーマッタ' )
- @app.route( "/format" )
- デフフォーマット():
- span_ctx = tracer.extract(Format.HTTP_HEADERS、リクエスト.headers)
- span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
- tracer.start_active_span( 'format' , child_of=span_ctx, tags=span_tagsの場合):
- hello_to = request.args.get( 'helloTo' )リクエストの引数を取得します。
- 戻る 「こんにちは、%s!」 % こんにちは
- __name__ == "__main__"の場合:
- app.run(ポート=8081)
サービス A はフォーマット要求に応答し、tracer.extract を呼び出して http ヘッダーから情報を抽出し、spanContext を構築します。 サービスBコード - FlaskからFlaskをインポート
- フラスコのインポートリクエストから
- lib.tracingからinit_tracerをインポートする
- opentracing.extからタグをインポート
- opentracing.propagationからのインポート形式
- アプリ = Flask(__name__)
- トレーサー = init_tracer( 'パブリッシャー' )
- @app.route( "/publish" )
- デフパブリッシュ():
- span_ctx = tracer.extract(Format.HTTP_HEADERS、リクエスト.headers)
- span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
- tracer.start_active_span( 'publish' , child_of=span_ctx, tags=span_tagsの場合):
- hello_str = リクエスト.args.get( 'helloStr' )
- 印刷(hello_str)
- 戻る 「公開」
- __name__ == "__main__"の場合:
- app.run(ポート=8082)
サービス B はサービス A と似ています。 すると、分散トレースをサポートするソフトウェア UI (次の図は Jaeger UI) 上で、次の図のようなトレース情報が表示されます。サービス hello-word と 3 つの操作 say-hello/format/println の詳細なトレース情報を確認できます。
現在、Jaeger、LightStep、Instanna、Apache SkyWalking、inspectIT、stagemonitor、Datadog、Wavefront、Elastic APM など、多くの分散トレース ソフトウェアが OpenTracing をサポートしています。その中でも、オープン ソース ソフトウェアである Zipkin (http://1t.click/6Ec) と Jaeger (http://1t.click/6DY) が最も人気があります。 ジプキン Zipkin (http://1t.click/6Ec) は、Dapper をベースに Twitter が開発した分散トレース システムです。設計アーキテクチャは次のとおりです。
- 青いエンティティは Zipkin がトレースするターゲット コンポーネントであり、非インストルメント サーバーはトレース API を直接呼び出さないマイクロサービスを表します。インストルメントされたクライアントを介して非インストルメント サーバーから情報を収集し、Zipkin コレクターに送信します。 Instrumented Server は Tracing API を直接呼び出し、データを Zipkin コレクターに送信します。
- トランスポートは、HTTP 経由またはメッセージ/イベント キューを介して Zipkin に直接送信できる転送チャネルです。
- Zipkin 自体は Java アプリケーションであり、次のものが含まれます。Collector はデータ収集を担当し、外部へのデータ インターフェイスを提供します。ストレージ; API と UI。
Zipkin のユーザー インターフェイスは次のようになります。
Zipkin は、C#、Go、Java、JavaScript、Ruby、Scala、PHP の言語のクライアントを公式にサポートしています。オープンソース コミュニティでは他の言語もサポートされています。 Zipkin は 4 年近く開発されており、比較的成熟したプロジェクトです。 イェーガー Jaeger (http://1t.click/6DY) はもともと Uber によって分散トレース システムとして開発され、Dapper の設計コンセプトに基づいています。 Jaeger は現在、CNCF (Cloud Native Computing Foundation) のプロジェクトです。 CNCF 組織について少しでも知っていれば、このプロジェクトは Kubernetes と非常に緊密に統合されるはずだと推測できます。 Jaeger は分散アーキテクチャ設計に基づいており、主に次のコンポーネントが含まれています。 - Jaeger クライアントは、クライアント側でトレース情報を収集する役割を担います。
- Jaeger エージェントは、クライアントとのコミュニケーションと収集した追跡情報の Jaeger コレクターへの報告を担当します。
- Jaeger Colletorは収集したデータをデータベースまたはその他のストレージに保存します
- Jaeger Queryは追跡データのクエリを担当します
- Jaeger UIはユーザーインタラクションを担当します
このアーキテクチャは ELK と非常によく似ています。 Collector は Logstash に似ており、データの収集を担当します。Query は Elastic に似ており、検索を担当します。UI は Kibana に似ており、ユーザー インターフェイスとインタラクションを担当します。この分散アーキテクチャにより、Jaeger のスケーラビリティが向上し、必要に応じてさまざまなデプロイメントを構築できるようになります。 分散トレースの新星として、Jaeger はクラウド ネイティブと K8s の普及によりますます人気が高まっています。公式の K8s デプロイメント テンプレート (http://1t.click/6DU) を使用すると、ユーザーは独自の k8s クラスターに Jaeger を迅速にデプロイできます。 4. 分散追跡システム - 製品比較 もちろん、OpenTracing 標準をサポートする製品以外にも、分散トレース製品があります。参考までに、他のブロガーの分析をいくつか紹介します。 - コールチェーンの選択: Zipkin、Pinpoint、SkyWalking、CAT (http://1t.click/6tY)
- 分散呼び出しチェーンの調査 (pinpoint、skywalking、jaeger、zipkin など) (http://1t.click/6DK)
- 分散トレースシステム - 製品比較 (http://1t.click/6ug)
5. まとめ マイクロサービスが普及し、クラウドネイティブがアーキテクチャ設計の主流になるにつれて、ログ、メトリック、トレースなどのマイクロサービス システムの監視がシステム エンジニアリングにおける最優先事項になりました。 OpenTracing は Dapper の分散トレース設計コンセプトに基づいており、分散トレースの実装標準を定義します。オープンソース プロジェクトの中では、Zipkin と Jaeger が比較的優れた選択肢です。特に、Jaeger はクラウド ネイティブ フレームワークとの優れた統合により、マイクロサービス トレース システムを構築するための不可欠なツールです。 |