OpenTelemetry を使用してトレース情報を計測する

1. はじめに

5af4a7e43b0feaab.png

最終更新日: 2021 年 3 月 5 日

アプリケーションのオブザーバビリティ

オブザーバビリティと OpenTelemetry

オブザーバビリティは、システムの属性を表す用語です。オブザーバビリティを備えたシステムでは、チームがシステムを積極的にデバッグできます。このコンテキストでは、オブザーバビリティの 3 つの柱であるログ、指標、トレースは、システムがオブザーバビリティを取得するための基本的な計測です。

OpenTelemetry は、オブザーバビリティに必要なテレメトリー データ(ログ、指標、トレース)の計測とエクスポートを加速する仕様と SDK のセットです。OpenTelemetry は、CNCF の下にあるオープン スタンダードで、コミュニティ主導のプロジェクトです。プロジェクトとそのエコシステムが提供するライブラリを利用することで、デベロッパーはベンダーに依存しない方法で、複数のアーキテクチャに対してアプリケーションを計測できます。

分散トレース

ログ、指標、トレースのうち、トレースはシステムのプロセス内の特定の部分のレイテンシを示すテレメトリーです。特にマイクロサービスの時代には、分散トレースは分散システム全体のレイテンシ ボトルネックを見つけるための強力な推進力となります。

分散トレースを分析する場合、トレースデータの可視化は、システム全体のレイテンシを一目で把握するための鍵となります。分散トレースでは、複数のスパンを含むトレースの形式で、システム エントリポイントへの単一のリクエストを処理するための一連の呼び出しを処理します。

スパンは、分散システムで行われた個々の作業単位を表し、開始時刻と終了時刻を記録します。スパンには階層関係があることがよくあります。下の図では、すべての小さなスパンが大きな /messages スパンの子スパンであり、システム内の作業パスを示す 1 つのトレースにまとめられています。

adbd3ecd69d410cb.png

Google Cloud Trace は、分散トレース バックエンドのオプションの 1 つであり、Google Cloud の他のプロダクトとよく統合されています。

作成するアプリの概要

この Codelab では、Google Kubernetes Engine で実行されている Kubernetes クラスタで実行される「Shakesapp」というサービスでトレース情報を計測します。Shakesapp のアーキテクチャは次のとおりです。

68873c018a7be7de.png

  • クライアントがクエリ文字列をサーバーに送信する
  • サーバーはクライアントからクエリを受け取り、Google Cloud Storage からテキスト形式のシェイクスピアの作品をすべて取得し、クエリを含む行を検索して、一致した行の数をクライアントに返します。

リクエスト全体でトレース情報を計測します。

学習内容

  • Python プロジェクトで OpenTelemetry Trace ライブラリを使用する方法
  • ライブラリを使用してスパンを作成する方法
  • アプリ コンポーネント間でスパン コンテキストを伝播する方法
  • トレースデータを Google Cloud Trace に送信する方法
  • Google Cloud Trace でトレースを分析する方法

この Codelab では、マイクロサービスを計測する方法について説明します。わかりやすくするために、この例には 3 つのコンポーネント(ロード ジェネレータ、クライアント、サーバー)のみが含まれていますが、この Codelab で説明する同じプロセスを、より複雑で大規模なシステムに適用できます。

必要なもの

  • Python 3 の知識

2. 設定と要件

セルフペース型の環境設定

Google アカウント(Gmail または Google Apps)をお持ちでない場合は、1 つ作成する必要があります。Google Cloud Platform のコンソール(console.cloud.google.com)にログインし、新しいプロジェクトを作成します。

すでにプロジェクトが存在する場合は、コンソールの左上にあるプロジェクト選択プルダウン メニューをクリックします。

15b8b6ac4d917005.png

表示されたダイアログで [NEW PROJECT] ボタンをクリックして、新しいプロジェクトを作成します。

7136b3ee36ebaf89.png

まだプロジェクトが存在しない場合は、次のような最初のプロジェクトを作成するためのダイアログが表示されます。

90977ce514204b51.png

続いて表示されるプロジェクト作成ダイアログでは、新しいプロジェクトの詳細を入力できます。

6d9573e346e930b4.png

プロジェクト ID を忘れないようにしてください。プロジェクト ID は、すべての Google Cloud プロジェクトを通じて一意の名前にする必要があります(上記の名前はすでに使用されているため使用できません)。以降、この Codelab では PROJECT_ID と呼びます。

次に、Google Cloud リソースを使用し、Cloud Trace API を有効にするために、Developers Console で課金を有効にする必要があります。

eb5325f65619ad6a.png

この Codelab の操作をすべて行っても、費用は数ドル程度です。ただし、その他のリソースを使いたい場合や、実行したままにしておきたいステップがある場合は、追加コストがかかる可能性があります(このドキュメントの最後にある「クリーンアップ」セクションをご覧ください)。Google Cloud Trace、Google Kubernetes Engine、Google Artifact Registry の料金は、公式ドキュメントに記載されています。

Google Cloud Platform の新規ユーザーの皆さんには、$300 の無料トライアルをご利用いただけます。その場合は、この Codelab を完全に無料でご利用いただけます。

Google Cloud Shell の設定

Google Cloud と Google Cloud Trace はノートパソコンからリモートで操作できますが、この Codelab では、Google Cloud Shell(Cloud 上で動作するコマンドライン環境)を使用します。

この Debian ベースの仮想マシンには、必要な開発ツールがすべて用意されています。仮想マシンは Google Cloud で稼働し、永続的なホーム ディレクトリが 5 GB 用意されているため、ネットワークのパフォーマンスと認証が大幅に向上しています。つまり、この Codelab に必要なのはブラウザだけです(Chromebook でも動作します)。

Cloud Console から Cloud Shell を有効にするには、[Cloud Shell をアクティブにする] gcLMt5IuEcJJNnMId-Bcz3sxCd0rZn7IzT_r95C8UZeqML68Y1efBG_B0VRp7hc7qiZTLAF-TXD7SsOadxn8uadgHhaLeASnVS3ZHK39eOlKJOgj9SJua_oeGhMxRrbOg3qigddS2A をクリックします(環境のプロビジョニングと接続に若干時間を要します)。

ff81d016724c4f67.png

fbe156ee6edfbb2e.png

Cloud Shell に接続すると、すでに認証は完了しており、プロジェクトに各自の PROJECT_ID が設定されていることがわかります。

gcloud auth list

コマンド出力

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

コマンド出力

[core]
project = <PROJECT_ID>

なんらかの理由でプロジェクトが設定されていない場合は、次のコマンドを実行します。

gcloud config set project <PROJECT_ID>

PROJECT_ID が見つからない場合は、設定手順で使用した ID を確認するか、Cloud Console ダッシュボードで検索します。

a3e716fc9e7454e9.png

Cloud Shell では、デフォルトで環境変数もいくつか設定されます。これらの変数は、以降のコマンドを実行する際に有用なものです。

echo $GOOGLE_CLOUD_PROJECT

コマンド出力

<PROJECT_ID>

最後に、デフォルトのゾーンとプロジェクト構成を設定します。

gcloud config set compute/zone us-central1-f

さまざまなゾーンを選択できます。詳細については、リージョンとゾーンをご覧ください。

Python の設定

この Codelab では、「poetry」を使用してパッケージ バージョンを厳密に管理します。Cloud Shell で次のコマンドを実行します。

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 -
source $HOME/.poetry/env

Google Kubernetes クラスタを設定する

この Codelab では、Google Kubernetes Engine(GKE)でマイクロサービスのクラスタを実行します。この Codelab のプロセスは次のとおりです。

  1. ベースライン プロジェクトを Cloud Shell にダウンロードする
  2. マイクロサービスをコンテナにビルドする
  3. コンテナを Google Artifact Registry(GAR)にアップロードする
  4. コンテナを GKE にデプロイする
  5. トレース計測用にサービスのソースコードを変更する
  6. ステップ 2 に進みます

Kubernetes Engine を有効にする

まず、Shakesapp が GKE で実行される Kubernetes クラスタを設定します。そのため、GKE を有効にする必要があります。[Kubernetes Engine] メニューに移動し、[有効にする] ボタンを押します。

56c680e93e169731.png

これで、Kubernetes クラスタを作成する準備が整いました。

Kubernetes クラスタを作成する

Cloud Shell で次のコマンドを実行して、Kubernetes クラスタを作成します。Artifact Registry リポジトリの作成に使用したリージョンに、ゾーンの値が含まれていることを確認してください。リポジトリ リージョンがゾーンをカバーしていない場合は、ゾーン値 us-central1-f を変更します。

gcloud container clusters create otel-trace-codelab --zone us-central1-f \
--num-nodes 1 \
--machine-type e2-highcpu-4

コマンド出力

Creating cluster otel-trace-codelab in us-central1-f... Cluster is being health-checked (master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/psychic-order-307806/zones/us-central1-f/clusters/otel-trace-codelab].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab?project=psychic-order-307806
kubeconfig entry generated for otel-trace-codelab.
NAME                LOCATION       MASTER_VERSION    MASTER_IP        MACHINE_TYPE  NODE_VERSION      NUM_NODES  STATUS
otel-trace-codelab  us-central1-f  1.18.12-gke.1210  104.154.162.176  e2-medium     1.18.12-gke.1210  3          RUNNING

Artifact Registry と skaffold の設定

これで、デプロイの準備が整った Kubernetes クラスタができました。次に、コンテナを push してデプロイするためのコンテナ レジストリを準備します。この手順では、GAR と skaffold を設定して使用する必要があります。

Artifact Registry の設定

[Artifact Registry] のメニューに移動し、[有効にする] ボタンを押します。

f7493243bae0cdf7.png

しばらくすると、GAR のリポジトリ ブラウザが表示されます。[リポジトリを作成] ボタンをクリックして、リポジトリの名前を入力します。

f97f337f5476651.png

この Codelab では、新しいリポジトリに trace-codelab という名前を付けます。アーティファクトの形式は「Docker」、ロケーション タイプは「リージョン」です。Google Compute Engine のデフォルト ゾーンに設定したゾーンに近いリージョンを選択します。たとえば、上記の例では「us-central1-f」を選択したので、ここでは「us-central1(アイオワ)」を選択します。[作成] ボタンをクリックします。

2f04143077ca56db.png

リポジトリ ブラウザに「trace-codelab」が表示されます。

7a3c1f47346bea15.png

後でここに戻ってレジストリ パスを確認します。

Skaffold の設定

Skaffold は、Kubernetes で実行されるマイクロサービスの構築に取り組む際に便利なツールです。一連のコマンドで、アプリケーションのコンテナのビルド、push、デプロイのワークフローを処理します。Skaffold はデフォルトで Docker Registry をコンテナ レジストリとして使用するため、コンテナを push するときに GAR を認識するように Skaffold を構成する必要があります。

Cloud Shell をもう一度開き、Skaffold がインストールされていることを確認します。(Cloud Shell はデフォルトで環境に skaffold をインストールします)。次のコマンドを実行して、skaffold のバージョンを確認します。

skaffold version

コマンド出力

v1.20.0

これで、skaffold が使用するデフォルトのリポジトリを登録できます。レジストリ パスを取得するには、Artifact Registry ダッシュボードに移動し、前の手順で設定したリポジトリの名前をクリックします。

55173fe922f40327.png

ページの上部にパンくずリストが表示されます。e157b1359c3edc06.png アイコンをクリックして、レジストリ パスをクリップボードにコピーします。

a9b0fa44c37e0178.png

コピーボタンをクリックすると、ブラウザの下部に次のようなメッセージが表示されたダイアログが表示されます。

「us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab」がコピーされました

Cloud Shell に戻ります。ダッシュボードからコピーした値を使用して skaffold config set default-repo コマンドを実行します。

skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab

コマンド出力

set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox

また、レジストリを Docker 構成に構成する必要があります。次のコマンドを実行します。

gcloud auth configure-docker us-central1-docker.pkg.dev --quiet

コマンド出力

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev

これで、次のステップに進み、GKE で Kubernetes コンテナを設定できます。

概要

この手順では、Codelab 環境を設定します。

  • Cloud Shell を設定する
  • コンテナ レジストリ用の Artifact Registry リポジトリを作成しました
  • コンテナ レジストリを使用するように Skaffold を設定する
  • Codelab のマイクロサービスが実行される Kubernetes クラスタを作成した

次のステップ

次のステップでは、マイクロサービスをビルドしてクラスタにプッシュし、デプロイします。

3. マイクロサービスをビルド、push、デプロイする

Codelab の資料をダウンロードする

前の手順では、この Codelab の前提条件をすべて設定しました。これで、これらの上にマイクロサービス全体を実行する準備が整いました。Codelab の資料は GitHub でホストされているため、次の git コマンドを使用して Cloud Shell シェル環境にダウンロードします。

cd ~
git clone https://github.com/GoogleCloudPlatform/opentelemetry-trace-codelab-python.git

プロジェクトのディレクトリ構造は次のようになります。

shakesapp-python
├── LICENSE
├── manifests
│   ├── client.yaml
│   ├── loadgen.yaml
│   └── server.yaml
├── proto
│   └── shakesapp.proto
├── skaffold.yaml
└── src
    ├── client
    ├── loadgen
    └── server
  • manifests: Kubernetes マニフェスト ファイル
  • proto: クライアントとサーバー間の通信の proto 定義
  • src: 各サービスのソースコードのディレクトリ
  • skaffold.yaml: skaffold の構成ファイル

skaffold コマンドを実行する

これで、作成した Kubernetes クラスタにコンテンツ全体をビルド、push、デプロイする準備が整いました。複数の手順が含まれているように見えますが、実際には skaffold がすべてを実行します。次のコマンドで試してみましょう。

cd shakesapp-python
skaffold run --tail

コマンドを実行するとすぐに、docker build のログ出力が表示され、レジストリに正常に push されたことを確認できます。

コマンド出力

...
---> Running in c39b3ea8692b
 ---> 90932a583ab6
Successfully built 90932a583ab6
Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1
The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice]
cc8f5a05df4a: Preparing
5bf719419ee2: Preparing
2901929ad341: Preparing
88d9943798ba: Preparing
b0fdf826a39a: Preparing
3c9c1e0b1647: Preparing
f3427ce9393d: Preparing
14a1ca976738: Preparing
f3427ce9393d: Waiting
14a1ca976738: Waiting
3c9c1e0b1647: Waiting
b0fdf826a39a: Layer already exists
88d9943798ba: Layer already exists
f3427ce9393d: Layer already exists
3c9c1e0b1647: Layer already exists
14a1ca976738: Layer already exists
2901929ad341: Pushed
5bf719419ee2: Pushed
cc8f5a05df4a: Pushed
step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001

すべてのサービス コンテナの push が完了すると、Kubernetes デプロイが自動的に開始されます。

コマンド出力

sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997
Tags used in deployment:
 - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe
 - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8
 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a
Starting deploy...
 - deployment.apps/clientservice created
 - service/clientservice created
 - deployment.apps/loadgen created
 - deployment.apps/serverservice created
 - service/serverservice created

注意: 「No push access to specified image repository」のようなエラーが表示された場合は、skaffold コマンドが skaffold のデフォルト リポジトリの構成に関係なく、Docker Hub(docker.io)にイメージを push しようとしているかどうかを確認してください。その場合は、次のように「skaffold run」に「--default-repo」オプションを追加してみてください。

$ skaffold run –tail –default-repo=us-central1-docker.pkg.dev/[プロジェクト ID]/[リポジトリ名]

デプロイ後、各コンテナの stdout に出力された実際のアプリケーション ログは次のようになります。

コマンド出力

[server] {"event": "starting server: 0.0.0.0:5050", "severity": "info", "timestamp": "2021-03-17T05:25:56.758575Z"}
[client] [2021-03-17 05:25:54 +0000] [1] [INFO] Starting gunicorn 20.0.4
[client] [2021-03-17 05:25:54 +0000] [1] [INFO] Listening at: http://0.0.0.0:8080 (1)
[client] [2021-03-17 05:25:54 +0000] [1] [INFO] Using worker: threads
[client] [2021-03-17 05:25:54 +0000] [7] [INFO] Booting worker with pid: 7
[client] {"event": "server address is serverservice:5050", "severity": "info", "timestamp": "2021-03-17T05:25:54.888627Z"}
[client] {"event": "request to server with query: world", "severity": "info", "timestamp": "2021-03-17T05:26:11.550923Z"}
[server] {"event": "query: world", "severity": "info", "timestamp": "2021-03-17T05:26:11.567048Z"}
[loadgen] {"event": "check connectivity: http://clientservice:8080/_healthz", "severity": "info", "timestamp": "2021-03-17T05:26:11.533605Z"}
[loadgen] {"event": "/_healthz response: ok", "severity": "info", "timestamp": "2021-03-17T05:26:11.544267Z"}
[loadgen] {"event": "confirmed connection ot clientservice", "severity": "info", "timestamp": "2021-03-17T05:26:11.544527Z"}

これで、サービスの分散トレース用に OpenTelemetry を使用してアプリケーションの計測を開始する準備が整いました。

概要

このステップでは、環境で Codelab の資料を準備し、skaffold が想定どおりに実行されることを確認しました。

次のステップ

次のステップでは、トレース情報を計測するように loadgen サービスのソースコードを変更します。

4. HTTP の計測

トレース計測と伝播のコンセプト

ソースコードを編集する前に、分散トレースの仕組みを簡単な図で説明します。

c8c659deaa9c9091.png

この例では、Trace と Span の情報を Cloud Trace にエクスポートし、loadgen サービスからサーバー サービスへのリクエスト全体でトレース コンテキストを伝播するようにコードを計測します。

Cloud Trace が同じトレース ID を持つすべてのスパンを 1 つのトレースに組み立てるには、アプリケーションがトレース ID やスパン ID などのトレース メタデータを送信する必要があります。また、アプリケーションは、処理しているトレース コンテキストを認識できるように、ダウンストリーム サービスをリクエストするときにトレース コンテキスト(親スパンのトレース ID とスパン ID の組み合わせ)を伝播する必要があります。

OpenTelemetry は、次のことに役立ちます。

  • 一意のトレース ID とスパン ID を生成する
  • トレース ID とスパン ID をバックエンドにエクスポートする
  • 他のサービスにトレース コンテキストを伝播する

最初のスパンを計測する

ロード ジェネレータ サービスを計測する

Cloud Shell の右上にあるボタン 776a11bfb2122549.png を押して、Cloud Shell エディタを開きます。左側のペインのエクスプローラから src/loadgen/loadgen.py を開き、main 関数を見つけます。

src/loadgen/loadgen.py

def main():
    ...
    # start request loop to client service
    logger.info("start client request loop")
    addr = f"http://{target}"
    while True:
        logger.info("start request to client")
        call_client(addr)
        logger.info("end request to client")
        time.sleep(2.0)

main 関数では、ループ内で関数 call_client を呼び出しています。現在の実装では、このセクションに、関数呼び出しの開始と終了を記録する 2 つのログ行があります。次に、関数呼び出しのレイテンシを追跡するために、Span 情報を計測します。

まず、一意のトレース ID とスパン ID を持つスパンを作成する必要があります。OpenTelemetry は、これに便利なライブラリを提供します。次の行を追加して、OpenTelemetry ライブラリをコードにインポートします。

 import structlog
+from opentelemetry import propagate, trace
+from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.instrumentation.requests import RequestsInstrumentor
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.propagators.cloud_trace_propagator import CloudTraceFormatPropagator

ロード ジェネレータは requests モジュールを介して HTTP でクライアント アプリケーションを呼び出すため、requests の拡張機能パッケージを使用して計測を有効にします。

 from opentelemetry.propagators.cloud_trace_propagator import CloudTraceFormatPropagator
+
+RequestsInstrumentor().instrument()

次に、Trace Context とエクスポーターの設定を処理する Tracer インスタンスを設定します。

     target = os.environ.get("CLIENT_ADDR", "0.0.0.0:8080")

+    exporter = CloudTraceSpanExporter()
+    trace.get_tracer_provider().add_span_processor(SimpleSpanProcessor(exporter))
+    tracer = trace.get_tracer(__name__)
+    propagate.set_global_textmap(CloudTraceFormatPropagator())
+    trace.set_tracer_provider(TracerProvider())
+
     # connectivity check to client service
     healthz = f"http://{target}/_healthz"
     logger.info(f"check connectivity: {healthz}")

これはトレース計測の仕組みを理解するための Codelab であるため、すべてのリクエストを記録してバックエンドに送信するように Tracer を構成します。(SimpleSpanProcessor())これは本番環境には適していません。本番環境のアプリケーションを計測するときは、この部分を必ず変更してください。

Tracer を使用してスパンを計測できるようになりました。ここで重要なのは、Span を明示的に生成するだけでよいということです。イベント メタデータを Span に追加する行が 2 つありますが、一意のトレース ID と Span ID を手動で生成して Span に埋め込む必要はありません。

     logger.info("start client request loop")
     addr = f"http://{target}"
     while True:
-        logger.info("start request to client")
-        call_client(addr)
-        logger.info("end request to client")
+        with tracer.start_as_current_span("loadgen") as root_span:
+            root_span.add_event(name="request_start")
+            logger.info("start request to client")
+            call_client(addr)
+            root_span.add_event(name="request_end")
+            logger.info("end request to client")
         time.sleep(2.0)

Docker ビルドで必要な OpenTelemetry パッケージを取得するには、次のコマンドを実行します。

poetry add "opentelemetry-exporter-gcp-trace=^1.0.0rc0"
poetry add "opentelemetry-propagator-gcp=^1.0.0rc0"
poetry add "opentelemetry-instrumentation-requests=^0.20b0"

対応する依存関係の説明が pyproject.toml に記述されていることを確認できます。

計測器のクライアント サービス

前のセクションでは、下の図の赤い四角で囲まれた部分を計測しました。ロード ジェネレータ サービスでスパン情報を計測しました。負荷生成ツールのサービスと同様に、クライアント サービスを計測する必要があります。ロード ジェネレータ サービスとの違いは、クライアント サービスが HTTP ヘッダーのロード ジェネレータ サービスから伝播されたトレース ID 情報を抽出し、その ID を使用してスパンを生成する必要があることです。

ae074d4513c9931f.png

Cloud Shell エディタを開き、ロード ジェネレータ サービスの場合と同様に必要なモジュールを追加します。

src/client/client.py

 import flask
 import grpc
 import structlog
+from opentelemetry import propagate, trace
+from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
+from opentelemetry.instrumentation.flask import FlaskInstrumentor
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.propagators.cloud_trace_propagator import \
+    CloudTraceFormatPropagator

 import shakesapp_pb2
 import shakesapp_pb2_grpc

FlaskInstrumentor をインポートしたことに気づくでしょう。これにより、ユーザーに代わって Flask アプリケーションの自動計測を有効にし、HTTP ヘッダーを抽出して、1 行のコードでトレース コンテキストを取得できます。OpenTelemetry コミュニティは、他の主要なライブラリとの同様の便利な統合を提供しています。詳細については、公式ドキュメントをご覧ください。

 app = flask.Flask(__name__)
+FlaskInstrumentor().instrument_app(app)

計測を開始する前に、ロード ジェネレータ サービスで行ったのと同様に、Tracer インスタンスを準備する必要があります。

 logger.info(f"server address is {SERVER_ADDR}")

+exporter = CloudTraceSpanExporter()
+trace.get_tracer_provider().add_span_processor(SimpleSpanProcessor(exporter))
+propagate.set_global_textmap(CloudTraceFormatPropagator())
+trace.set_tracer_provider(TracerProvider())

 @app.route("/")
 def main_handler():
    ....

これで、ハンドラに計測手法を追加する準備が整いました。main_handler() を見つけて、gRPC リクエストをサーバー サービスにスローする部分を変更します。

@app.route("/")
def main_handler():
    q, count = random.choice(list(queries.items()))

    # get Tracer
    tracer = trace.get_tracer(__name__)

    with tracer.start_as_current_span("client") as cur_span:
        channel = grpc.insecure_channel(SERVER_ADDR)
        stub = shakesapp_pb2_grpc.ShakespeareServiceStub(channel)
        logger.info(f"request to server with query: {q}")
        cur_span.add_event("server_call_start")
        resp = stub.GetMatchCount(shakesapp_pb2.ShakespeareRequest(query=q))
        cur_span.add_event("server_call_end")
        if count != resp.match_count:
            raise UnexpectedResultError(
                f"The expected count for '{q}' was {count}, but result was {resp.match_count } obtained"
            )
        result = str(resp.match_count)
        logger.info(f"matched count for '{q}' is {result}")
    return result

負荷生成ツールのサービスと同様に、次のコマンドで必要なパッケージを pyproject.toml に追加します。

poetry add "opentelemetry-exporter-gcp-trace=^1.0.0rc0"
poetry add "opentelemetry-propagator-gcp=^1.0.0rc0"
poetry add "opentelemetry-instrumentation-flask=^0.20b0"

次に、skaffold run コマンドでアプリケーションを起動し、Cloud Trace ダッシュボードに何が表示されるかを確認します。

skaffold run --tail

ビルド、プッシュ、デプロイのメッセージが表示されたら、JSON 形式のアプリケーション ログが表示されます。Cloud Trace > トレースリストに移動して、トレース情報が取得されているかどうかを確認します。ロード生成サービスはクライアント サービスに定期的にリクエストを送信し、すべてのリクエストのトレースを有効にしているため、トレースリストに多くのドットが表示されます。

f7440360551980e.png

いずれかをクリックすると、リクエストとレスポンスのプロセスにおける各部分のレイテンシを示す次のようなウォーターフォール グラフが表示されます。[イベントを表示] の横にあるチェックボックスをオンにすると、ウォーターフォール グラフ内にアノテーションが表示されます。これらのアノテーションは、span.add_event() メソッドでコードに計測を追加したものです。

67596a4a313738.png

サーバー サービスのスパンが表示されないことがあります。サーバー サービスでスパンをまったく計測していないため、正解です。

概要

このステップでは、ロードジェネレータ サービスとクライアント サービスを計測し、サービス間で Trace コンテキストを正常に伝播し、両方のサービスから Cloud Trace に Span 情報をエクスポートできることを確認しました。

次のステップ

次のステップでは、クライアント サービスとサーバー サービスを計測して、gRPC 経由でトレース コンテキストを伝播する方法を確認します。

5. gRPC の計測手法

前のステップでは、このマイクロサービスのリクエストの前半を計測しました。このステップでは、クライアント サービスとサーバー サービス間の gRPC 通信を計測します。(下の図の緑と紫の長方形)

c4dec3e741c3ab4f.png

gRPC クライアントの自動計測

OpenTelemetry のエコシステムには、デベロッパーがアプリケーションを計測するのに役立つ便利なライブラリが多数用意されています。前のステップでは、「リクエスト」モジュールに自動計測を使用しました。このステップでは、gRPC を介してトレース コンテキストを伝播しようとしているため、そのためのライブラリを使用します。

src/client/client.py

 import flask
 import grpc
 import structlog
 from opentelemetry import propagate, trace
 from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
 from opentelemetry.instrumentation.flask import FlaskInstrumentor
+from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient
 from opentelemetry.sdk.trace import TracerProvider
 from opentelemetry.sdk.trace.export import SimpleSpanProcessor
 from opentelemetry.propagators.cloud_trace_propagator import \
     CloudTraceFormatPropagator
 import shakesapp_pb2
 import shakesapp_pb2_grpc


 app = flask.Flask(__name__)
 FlaskInstrumentor().instrument_app(app)
+GrpcInstrumentorClient().instrument()

クライアント サービスの場合、計測に必要な作業はごくわずかです。ここでやりたいことは、現在のスパンのトレース ID とスパン ID の組み合わせであるトレース コンテキストを gRPC 経由で伝播することです。ハンドラ関数の gRPC クライアントがトレース コンテキストを HTTP ヘッダーに埋め込めるように、GrpcInstrumentatorClient.instrument() を呼び出します。

poetry add コマンドを使用して、pyproject.toml に新しい依存関係を追加してください。

poetry add "opentelemetry-instrumentation-grpc=^0.20b0"

gRPC サーバーの自動計測

gRPC クライアントの場合と同様に、gRPC サーバーの自動計測を呼び出します。次のようなインポートを追加し、ファイルの先頭で GrpcInstrumentationServer().instrument() を呼び出します。

注意: 必ず電話してください。

GrpcInstrumentationServe() 

このステップでは、

GrpcInstrumentationClient()

.

src/server/server.py

 import grpc
 import structlog
 from google.cloud import storage
 from grpc_health.v1 import health_pb2, health_pb2_grpc
+from opentelemetry import propagate, trace
+from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
+from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.propagators.cloud_trace_propagator import CloudTraceFormatPropagator

 import shakesapp_pb2
 import shakesapp_pb2_grpc


 BUCKET_NAME = "dataflow-samples"
 BUCKET_PREFIX = "shakespeare/"

+# enable auto gRPC server trace instrumentation
+GrpcInstrumentorServer().instrument()
+

次に、トレース情報を Cloud Trace バックエンドに送信するエクスポータを追加します。serve() 関数に次のコードを追加します。

def serve():
+    # start trace exporter
+    trace.set_tracer_provider(TracerProvider())
+    trace.get_tracer_provider().add_span_processor(
+        SimpleSpanProcessor(CloudTraceSpanExporter())
+    )
+    propagators.set_global_textmap(CloudTraceFormatPropagator())
+
+    # add gRPC services to server
     server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
     service = ShakesappService()
     shakesapp_pb2_grpc.add_ShakespeareServiceServicer_to_server(service, server)
     health_pb2_grpc.add_HealthServicer_to_server(service, server)

新しく追加したパッケージをサーバー サービスに追加してください。

poetry add "opentelemetry-exporter-gcp-trace=^1.0.0rc0"
poetry add "opentelemetry-instrumentation-grpc=^0.20b0"
poetry add "opentelemetry-propagator-gcp=^1.0.0rc0"
poetry add "opentelemetry-instrumentation=^0.20b0"

マイクロサービスを実行してトレースを確認する

次に、skaffold コマンドを使用して変更したコードを実行します。

skaffold run --tail

Cloud Trace のトレースリスト ページに、多数のトレースが表示されます。トレースのいずれかをクリックすると、ロード ジェネレータ サービスからサーバー サービスへのリクエストにまたがるスパンが表示されます。

141cb620245b689d.png

概要

このステップでは、OpenTelemetry エコシステム ライブラリのサポートを使用して、gRPC ベースの通信を計測しました。また、ロード ジェネレータ サービスで生成されたトレース コンテキストがサーバー サービスに正常に配信されたことも確認しました。

6. 完了

OpenTelemery で分散トレースを作成し、Google Cloud Trace でマイクロサービス全体のリクエスト レイテンシを確認しました。

拡張演習については、次のトピックを各自で試してください。

  • 現在の実装では、ヘルスチェックによって生成されたすべてのスパンが送信されます。これらのスパンを Cloud Trace から除外するにはどうすればよいですか?ヒントはこちらをご覧ください。
  • イベントログとスパンを関連付け、Google Cloud Trace と Google Cloud Logging での動作を確認します。ヒントはこちらをご覧ください。
  • 一部のサービスを別の言語のサービスに置き換え、その言語の OpenTelemetry で計測してみる

注意: Google Kubernetes Engine と Google Artifact Registry はリソースを常に消費します。

クリーンアップ

この Codelab の後、Kubernetes クラスタを停止し、プロジェクトを削除して、Google Kubernetes Engine、Google Cloud Trace、Google Artifact Registry で予期しない料金が発生しないようにしてください。

まず、次のコマンドでクラスタを削除します。

skaffold delete

コマンド出力

Cleaning up...
 - deployment.apps "clientservice" deleted
 - service "clientservice" deleted
 - deployment.apps "loadgen" deleted
 - deployment.apps "serverservice" deleted
 - service "serverservice" deleted

クラスタを削除したら、メニュー ペインから [IAM と管理] > [設定] を選択し、[シャットダウン] ボタンをクリックします。

578ca2b72a161e9d.png

次に、ダイアログのフォームにプロジェクト ID(プロジェクト名ではない)を入力し、シャットダウンを確認します。