マイクロサービス アプリケーションの分散トレース

更新日: 2023 年 10 月 24 日

このドキュメントは、マイクロサービスの設計、構築、デプロイに関する 4 部構成のシリーズの第 4 部です。このシリーズでは、マイクロサービス アーキテクチャのさまざまな要素について説明します。このシリーズには、マイクロサービス アーキテクチャ パターンの利点と欠点、およびその適用方法に関する情報が含まれています。

  1. マイクロサービスの概要
  2. モノリスのマイクロサービスへのリファクタリング
  3. マイクロサービス設定でのサービス間通信
  4. マイクロサービス アプリケーションでの分散トレース(このドキュメント)

このシリーズは、モノリシック アプリケーションをマイクロサービス アプリケーションにリファクタリングするための移行を設計して実装するアプリケーション デベロッパーとアーキテクトを対象としています。

分散システムでは、リクエストが 1 つのサービスから別のサービスにどのように移動するか、各サービスでタスクを実行するのにかかる時間を理解することが重要です。前のドキュメント(モノリスのマイクロサービスへのリファクタリング)でデプロイしたマイクロサービス ベースの Online Boutique アプリケーションを検討します。アプリケーションは複数のサービスで構成されています。たとえば、次のスクリーンショットは、フロントエンド、レコメンデーション、広告サービスから情報を取得する商品の詳細ページを示しています。

商品の詳細ページ。

商品の詳細ページをレンダリングするために、次の図に示すように、フロントエンド サービスはレコメンデーション サービスと広告サービスと通信します。

フロントエンド サービスは、レコメンデーション サービス、商品カタログ、広告サービスと通信します。

図 1. さまざまな言語で記述されたサービス。

図 1 では、フロントエンド サービスが Go で記述されています。Python で記述されたレコメンデーション サービスは、gRPC を使用してフロントエンド サービスと通信します。Java で記述された広告サービスも gRPC を使用してフロントエンド サービスと通信します。サービス間の通信方式は、gRPC 以外にも REST HTTP の場合もあります。

このような分散システムを構築する際、オブザーバビリティ ツールで次のような分析情報を取得する必要があります。

  • リクエストが行われたサービス。
  • リクエストが低速である場合に遅延が発生した場所。
  • リクエストが失敗した場合にエラーが発生した場所。
  • リクエストの実行がシステムの通常の動作とどのように異なったか。
  • リクエスト実行の違いがパフォーマンスに関連しているかどうか(一部のサービス呼び出しに通常より時間がかかったかどうか)。

目標

  • kustomize マニフェスト ファイルを使用してインフラストラクチャを設定する。
  • Online Boutique のサンプル アプリケーションを Google Kubernetes Engine(GKE)にデプロイする。
  • Cloud Trace を使用して、サンプル アプリケーションでのユーザー シナリオを確認する。

料金

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントの完了後に作成したリソースを削除すれば、今後課金は発生しません。詳しくは、クリーンアップをご覧ください。

始める前に

このシリーズの前のドキュメント、マイクロサービスの設定でのサービス間通信を完了することによってプロジェクトをすでに設定している場合、プロジェクトを再利用できます。追加の API を有効にして環境変数を設定するには、次の手順を行います。

  1. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

  2. Google Cloud プロジェクトで課金が有効になっていることを確認します

  3. Google Cloud コンソールで、「Cloud Shell をアクティブにする」をクリックします。

    Cloud Shell をアクティブにする

  4. Compute Engine、GKE、Cloud SQL、Artifact Analysis、Trace、Container Registry の API を有効にします。

     gcloud services enable \
       compute.googleapis.com \
       sql-component.googleapis.com \
       servicenetworking.googleapis.com\
       container.googleapis.com \
       containeranalysis.googleapis.com \
       containerregistry.googleapis.com \
       sqladmin.googleapis.com
    

分散トレース

分散トレースは、各リクエストにコンテキスト メタデータを接続し、リクエスト間でメタデータを共有するようにします。トレース ポイントを使用して、分散トレースをインストルメント化します。たとえば、商品の詳細を表示するクライアント リクエストを処理するために、サービス(フロントエンド、レコメンデーション、広告)を 2 つのトレース ポイントでインストルメント化できます。1 つは、リクエストを送信するトレース ポイント、もう 1 つはレスポンスを受信するトレース ポイントです。次の図は、このトレース ポイントのインストルメンテーションの仕組みを示しています。

2 つのトレース ポイントがあるトレース ポイント インストルメンテーション。

図 2.各サービス間呼び出しには、リクエストとレスポンスのペアで構成される 2 つのトレース ポイントがあります。

サービスが呼び出されたときに実行するリクエストを理解するトレース ポイントの場合、元のサービスは実行フローに沿ってトレース ID を渡します。トレース ID を渡すプロセスは、メタデータ伝播または分散コンテキスト伝播と呼ばれます。分散アプリケーションのサービスが特定のリクエストの実行中に相互に通信すると、コンテキスト伝播によってネットワーク呼び出しを介してメタデータが転送されます。次の図は、メタデータの伝播を示しています。

メタデータの伝播では、トレース ID が渡されます。

図 3.トレースのメタデータがサービス間で渡されます。メタデータには、どのサービスがどのサービスを呼び出すか、そのタイムスタンプなどの情報が含まれます。

Online Boutique の例では、ユーザーが商品の詳細を取得する最初のリクエストを送信すると、トレースが開始されます。新しいトレース ID が生成され、後続の各リクエストには、元のリクエストに戻るコンテキスト メタデータを含むヘッダーが付けられます。

エンドユーザーのリクエストを満たすものの一部として呼び出される個々のオペレーションは、スパンと呼ばれます。送信側サービスは、独自のスパン ID と親スパンのトレース ID で各スパンにタグを付けます。次の図は、トレースのガントチャートの可視化を示しています。

個々のオペレーションはスパンとしてタグ付けされます。

図 4.親スパンには、子スパンのレスポンス時間が含まれます。

図 4 は、フロントエンド サービスがレコメンデーション サービスと広告サービスを呼び出すトレースツリーを示しています。フロントエンド サービスは、エンドユーザーが観測するレスポンス時間が記述されている親スパンです。子スパンには、レスポンス時間の情報など、レコメンデーション サービスと広告サービスの呼び出しと応答の方法が記述されています。

Istio などのサービス メッシュを使用すると、専用のインストルメンテーションを必要とせずにサービス間のトラフィックを分散トレースできます。ただし、トレースをきめ細かく制御する必要がある場合や、サービス メッシュ内で実行されていないコードをトレースする必要がある場合もあります。

このドキュメントでは、OpenTelemetry を使用して、分散マイクロサービス アプリケーションのインストルメンテーションを有効にし、トレースと指標を収集します。OpenTelemetry では、指標とトレースを収集し、Prometheus、Cloud Monitoring、Datadog、Graphite、Zipkin、Jaeger などのバックエンドにエクスポートできます。

OpenTelemetry を使用したインストルメンテーション

以下のセクションでは、コンテキストの伝播を使用して、複数のリクエストからのスパンを 1 つの親トレースに追加する方法を説明します。

この例では、OpenTelemetry の JavaScriptPythonGo の各ライブラリを使用して、支払いサービス、レコメンデーション サービス、フロントエンド サービスのトレース実装をインストルメント化します。インストルメンテーションの詳細度によっては、トレースデータがプロジェクトのコスト(Cloud Trace の課金)に影響する可能性があります。コストに関する懸念を低減するために、ほとんどのトレース システムでは、さまざまな形式のサンプリングを使用して、観測されたトレースの特定の割合のみをキャプチャします。本番環境では、サンプリングする対象とその理由は、組織によって異なる場合があります。サンプリング戦略は、コストの管理、関心のあるトレースへのフォーカス、またはノイズの除去に基づいてカスタマイズできます。サンプリングの詳細については、OpenTelemetry サンプリングをご覧ください。

このドキュメントでは、Trace を使用して分散トレースを可視化します。OpenTelemetry エクスポータを使用して、トレースを Trace に送信します。

トレース エクスポータを登録する

このセクションでは、マイクロサービス コードに行を追加して、各サービスでトレース エクスポータを登録する方法を示します。

フロントエンド サービス(Go で記述)の場合、次のコードサンプルでエクスポータを登録します。

[...]
exporter, err := otlptracegrpc.New(
        ctx,
        otlptracegrpc.WithGRPCConn(svc.collectorConn))
    if err != nil {
        log.Warnf("warn: Failed to create trace exporter: %v", err)
    }
tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithSampler(sdktrace.AlwaysSample()))
    otel.SetTracerProvider(tp)

レコメンデーション サービス(Python で記述)の場合、次のコードサンプルでエクスポータを登録します。

if os.environ["ENABLE_TRACING"] == "1":
    trace.set_tracer_provider(TracerProvider())
    otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317")
    trace.get_tracer_provider().add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(
            endpoint = otel_endpoint,
            insecure = True
            )
        )
    )

支払いサービス(JavaScript で記述)の場合、次のコードサンプルでエクスポータを登録します。

provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({url: collectorUrl})));
provider.register();

コンテキスト伝播を設定する

トレース システムでは、サービス間でトレース コンテキストを伝播する形式を定義するトレース コンテキスト仕様に従う必要があります。伝播形式の例には、Zipkin の B3 形式X-Google-Cloud-Trace があります。

OpenTelemetry は、グローバル TextMapPropagator を使用してコンテキストを伝播します。この例では、W3C traceparent 形式を使用するトレース コンテキスト プロパゲータを使用します。OpenTelemetry の HTTP ライブラリや gRPC ライブラリなどのインストルメンテーション ライブラリは、グローバル プロパゲータを使用して、トレース コンテキストをメタデータとして HTTP リクエストまたは gRPC リクエストに追加します。コンテキストの伝播を成功させるには、クライアントとサーバーで同じ伝播形式を使用する必要があります。

HTTP を介したコンテキスト伝播

フロントエンド サービスは、HTTP リクエスト ヘッダーにトレース コンテキストを挿入します。バックエンド サービスがトレース コンテキストを抽出します。次のコードサンプルは、フロントエンド サービスをインストルメント化してトレース コンテキストを構成する方法を示しています。

otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))

if os.Getenv("ENABLE_TRACING") == "1" {
    log.Info("Tracing enabled.")
    initTracing(log, ctx, svc)
} else {
    log.Info("Tracing disabled.")
}

...

var handler http.Handler = r
handler = &logHandler{log: log, next: handler}     // add logging
handler = ensureSessionID(handler)                 // add session ID
handler = otelhttp.NewHandler(handler, "frontend") // add OpenTelemetry tracing

gRPC によるコンテキスト伝播

ユーザーが選択した商品に基づいて決済サービスが注文を執行するフローについて考えてみましょう。これらのサービスは gRPC を介して通信します。

次のコードサンプルでは、gRPC 呼び出しのインターセプタを使用して、発信呼び出しをインターセプトし、トレース コンテキストを挿入します。

var srv *grpc.Server

// Propagate trace context always
otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))
srv = grpc.NewServer(
    grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
    grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)

支払いサービスまたは商品カタログ サービス(ListProducts)は、リクエストを受信すると、リクエスト ヘッダーからコンテキストを抽出し、親トレース メタデータを使用して子スパンを生成します。

次のセクションでは、Online Boutique のサンプル アプリケーションに対して分散トレースを設定して確認する方法について詳しく説明します。

アプリケーションのデプロイ

このシリーズの前のドキュメント、マイクロサービス設定でのサービス間通信で完了した実行中のアプリケーションがすでに存在する場合は、次のセクションのトレースの確認に進みます。それ以外の場合は、次の手順を実行して、Online Boutique のサンプルをデプロイします。

  1. インフラストラクチャを設定するには、Cloud Shell で GitHub リポジトリのクローンを作成します。

    git clone https://github.com/GoogleCloudPlatform/microservices-demo.git
    
  2. 新しいデプロイでは、環境変数をリセットします。

    PROJECT_ID=PROJECT_ID
    REGION=us-central1
    GSA_NAME=microservices-sa
    GSA_EMAIL=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com
    

    次のように置き換えます。

    • PROJECT_ID: プロジェクト ID の識別子。
  3. 省略可: 新しいクラスタを作成するか、既存のクラスタが存在する場合はそれを再利用します。

    gcloud container clusters create-auto online-boutique --project=${PROJECT_ID}
      --region=${REGION}
    
  4. Google サービス アカウントを作成します。

    gcloud iam service-accounts create $GSA_NAME \
      --project=$PROJECT_ID
    
  5. API を有効にします。

    gcloud services enable \
    monitoring.googleapis.com \
    cloudtrace.googleapis.com \
    cloudprofiler.googleapis.com \
      --project ${PROJECT_ID}
    
  6. Cloud Trace に必要なロールを GSA に付与します。

    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudtrace.agent
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/monitoring.metricWriter
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudprofiler.agent
    
    gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]"
    
  7. Google IAM サービス アカウントを使用するように、Kubernetes サービス アカウント(デフォルトの名前空間の場合は default/default)にアノテーションを付けます。

    kubectl annotate serviceaccount default \
        iam.gke.io/gcp-service-account=${GSA_EMAIL}
    
  8. Cloud Operations for GKE の構成を有効にします。トレースが有効になります。

    cd ~/microservices-demo/kustomize && \
    kustomize edit add component components/google-cloud-operations
    
  9. これにより、次のように kustomize/kustomization.yaml ファイルが更新されます。

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    resources:
    - base
    components:
    - components/google-cloud-operations
    [...]
    
  10. マイクロサービスをデプロイします。

    kubectl apply -k .
    
  11. デプロイのステータスを確認します。

    kubectl rollout status deployment/frontend
    kubectl rollout status deployment/paymentservice
    kubectl rollout status deployment/recommendationservice
    kubectl rollout status deployment/adservice
    

    各コマンドの出力は次のようになります。

    Waiting for deployment "" rollout to finish: 0 of 1 updated replicas are available...
    deployment "" successfully rolled out
    
  12. デプロイされたアプリケーションの IP アドレスを取得します。

    kubectl get service frontend-external | awk '{print $4}'
    

    ロードバランサの IP アドレスが公開されるまで待機します。コマンドを終了するには、Ctrl+C を押します。ロードバランサの IP アドレスをメモしてから、URL http://IP_ADDRESS にあるアプリケーションにアクセスします。ロードバランサが正常な状態になり、トラフィックの通過が開始されるまでに時間がかかることがあります。

Cloud Trace を使用したトレースの確認

Online Boutique アプリケーションでのユーザーの購入プロセスの流れは次のとおりです。

  • ランディング ページに商品カタログがユーザーに表示されます。
  • ユーザーは [購入] をクリックして購入します。
  • ユーザーは商品の詳細ページにリダイレクトされ、カートに商品を追加します。
  • ユーザーは購入手続きページにリダイレクトされ、そこで注文を完了できます。

商品の詳細ページを読み込む際に長くなるレスポンス時間をトラブルシューティングする必要があるシナリオについて考えてみましょう。前述のように、商品の詳細ページは複数のマイクロサービスで構成されています。高レイテンシが発生する場所と理由を特定するには、分散トレースグラフを表示し、さまざまなサービスにわたるリクエスト全体のパフォーマンスを確認します。

分散トレースグラフを確認するには、次の操作を行います。

  1. アプリケーションにアクセスして任意の商品をクリックします。商品の詳細ページが表示されます。
  2. Google Cloud コンソールで [トレースリスト] ページに移動し、タイムラインを確認します。
  3. 分散トレース結果を表示するには、URI 列で [Frontend] をクリックします。
  4. Trace のウォーターフォール ビューには、URI に関連付けられているスパンが表示されます。

    Trace のウォーターフォール ビューにはスパンが表示されます。

    上記のスクリーンショットでは、商品のトレースに次のスパンが含まれています。

    • フロントエンド スパンは、商品の詳細ページの読み込み中にクライアントが確認したエンドツーエンドのレイテンシ(150.349 ミリ秒)をキャプチャします。
    • レコメンデーション サービススパンは、商品に関連するレコメンデーションの取得でのバックエンド呼び出しのレイテンシ(4.246 ミリ秒)をキャプチャします。
    • 広告サービススパンは、商品ページに関連する広告の取得でのバックエンド呼び出しのレイテンシ(4.511 ミリ秒)をキャプチャします。

長いレスポンス時間をトラブルシューティングする場合は、サービスの依存関係がサービスレベル目標(SLO)を満たしていないときの外れ値のリクエストのレイテンシ分布グラフを含む分析情報を確認できます。また、Cloud Trace を使用してパフォーマンスの分析情報を取得し、サンプリング データから分析レポートを作成することもできます。

トラブルシューティング

アプリケーション パフォーマンス管理のトレースが表示されない場合は、ログ エクスプローラで権限拒否エラーを確認します。権限拒否は、トレースをエクスポートするアクセス権がサービス アカウントに付与されていない場合に発生します。Cloud Trace に必要なロールを付与する手順を確認し、サービス アカウントに正しい名前空間のアノテーションを付けてください。その後、opentelemetrycollector を再起動します。

```
kubectl rollout restart deployment opentelemetrycollector
```

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

プロジェクトの削除

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

リソースを削除する

このクイックスタートで使用した Google Cloud プロジェクトを残しておく場合は、個々のリソースを削除します。

  • Cloud Shell で、リソースを削除します。

    gcloud container clusters delete online-boutique --project=${PROJECT_ID} --region=${REGION}
    

次のステップ