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

1. はじめに

5af4a7e43b0feaab.png

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

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

オブザーバビリティと OpenTelemetry

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

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

分散トレース

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

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

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

adbd3ecd69d410cb.png

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

作成するアプリの概要

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

68873c018a7be7de.png

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

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

学習内容

  • 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

[新しいプロジェクト] をクリックします。] ボタンをクリックし、新しいプロジェクトを作成します。

7136b3ee36ebaf89.png

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

90977ce514204b51.png

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

6d9573e346e930b4.png

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

次に、Google Cloud リソースを使用して Cloud Trace API を有効にするために、Cloud Console で課金を有効にします(まだ有効にしていない場合)。

eb5325f65619ad6a.png

この Codelab をすべて実行しても費用はかかりませんが、より多くのリソースを使用する場合や実行したままにする場合は、コストが高くなる可能性があります(このドキュメントの最後にある「クリーンアップ」セクションをご覧ください)。Google Cloud Trace、Google Kubernetes Engine、Google Artifacat Registry の料金については、公式ドキュメントに記載されています。

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

Google Cloud Shell のセットアップ

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

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

Cloud コンソールから 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 コンソール ダッシュボードで確認します。

a3e716fc9e7454e9.png

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

echo $GOOGLE_CLOUD_PROJECT

コマンドの出力

<PROJECT_ID>

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

gcloud config set compute/zone us-central1-f

さまざまなゾーンを選択できます。詳しくは、リージョンとゾーン

Python の設定

この Codelab では「詩」を使用します。パッケージのバージョンを厳密に管理できます。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 コンテナとデプロイ コンテナ用の Container Registry を準備します。このステップでは、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

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

&quot;us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab&quot;コピーされました

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 を設定する
  • Container Registry 用の Artifact Registry リポジトリを作成する
  • Container Registry を使用するように skaffold を設定する
  • Codelab マイクロサービスが実行される Kubernetes クラスタを作成した

次のステップ

次のステップでは、マイクロサービスを構築、push、クラスタにデプロイします。

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
  • マニフェスト: 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 の Deployment が自動的に開始されます。

コマンド出力

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 しようとしているかどうかを確認してください。その場合は、「–default-repo」を追加してみてください。「skaffold run」オプション下にあります

$ 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

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

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

OpenTelemetry を使用すると、次のことが可能になります。

  • スパン ID とトレース ID を生成できます。
  • トレース ID とスパン ID をバックエンドにエクスポートします。
  • トレース コンテキストを他のサービスに伝播

最初のスパンを計測する

計測負荷生成サービス

Cloud Shell エディタを開くには、Cloud Shell の右上にあるボタン 776a11bfb2122549.png を押します。左側のペインにあるエクスプローラから 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 関数を呼び出すループがあります。現在の実装では、sectoin に関数呼び出しの開始と終了を記録する 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 Contenxt とエクスポータの設定を処理する 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 であるため、すべてのリクエストを記録してバックエンドに送信するようにトレーサーを構成します。(SimpleSpanProcessor())これは本番環境には適していないため、本番環境アプリケーションを計測可能にする場合はこの部分を必ず変更してください。

Tracer でスパンを計測できるようになりました。ここでのポイントは、スパンを明示的に生成する必要があるということです。Span にイベント メタデータを追加する 2 行がありますが、一意のトレース ID とスパン 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

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

f7440360551980e.png

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

67596a4a313738.png

サーバー サービスのスパンが表示されない場合があります。正解です。サーバー サービスでスパンを計測することはできません。

概要

このステップでは、負荷生成サービスとクライアント サービスを計測可能にし、サービス間でトレース コンテキストを正常に伝播し、両方のサービスから Cloud Trace にスパン情報をエクスポートできることを確認しました。

次のステップ

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

5. gRPC のインストルメンテーション

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

c4dec3e741c3ab4f.png

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

OpenTelemetry のエコシステムには、デベロッパーがアプリケーションを計測する際に役立つライブラリが多数用意されています。前のステップでは、「requests」に対して自動計測を使用しました。説明します。このステップでは、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()

クライアント サービスの場合、計測用に行う必要がある操作はごくわずかです。これから、gRPC を介して現在の Span のトレース ID とスパン ID を組み合わせたトレース コンテキストを伝播します。そのため、ハンドラ関数の 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 の後、Google Kubernetes Engine、Google Cloud Trace、Google Artifact Registry で予期しない請求が発生しないように、Kubernetes クラスタを停止し、プロジェクトを削除してください。

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

skaffold delete

コマンド出力

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

クラスタを削除したら、メニューペインで [IAM と管理&gt;[設定]、[シャットダウン] の順にクリックします] ボタンを離します。

578ca2b72a161e9d.png

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