チュートリアル - Google Kubernetes Engine 上の Java アプリケーションのトレースを有効にする

概要

このチュートリアルでは、Google Kubernetes Engine (GKE) 上のクラスターにインストールされたサンプル Java アプリケーションでトレースを有効にするための手順を説明します。このシナリオでは、Datadog Agent もクラスターにインストールされています。

ホスト、コンテナ、他のクラウドインフラストラクチャー、他の言語で書かれたアプリケーションなど、他のシナリオについては、他のトレース有効化のチュートリアルを参照してください。

Java の一般的なトレース設定ドキュメントについては、Java アプリケーションのトレースを参照してください。

前提条件

  • Datadog のアカウントと組織の API キー
  • Git
  • Kubectl
  • GCloud - 以下のコマンドを実行して、USE_GKE_GCLOUD_AUTH_PLUGIN 環境変数を設定し、GCloud プロジェクトに追加のプロパティを構成します。
    export USE_GKE_GCLOUD_AUTH_PLUGIN=True  
    gcloud config set project <PROJECT_NAME>
    gcloud config set compute/zone <COMPUTE_ZONE>
    gcloud config set compute/region <COMPUTE_REGION>
  • Helm - 以下のコマンドを実行してインストールします。
    curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
    chmod 700 get_helm.sh
    ./get_helm.sh
    以下のコマンドを実行し、Helm を構成します。
    helm repo add datadog-crds https://helm.datadoghq.com
    helm repo add kube-state-metrics https://prometheus-community.github.io/helm-charts
    helm repo add datadog https://helm.datadoghq.com
    helm repo update

サンプルの Kubernetes Java アプリケーションをインストールします。

このチュートリアルのコードサンプルは、GitHub の github.com/DataDog/apm-tutorial-java-host にあります。まずは、このリポジトリを複製してください。

git clone https://github.com/DataDog/apm-tutorial-java-host.git

リポジトリには、Kubernetes クラスター内で動作するようにあらかじめ構成されたマルチサービスの Java アプリが含まれています。サンプルアプリは基本的なメモアプリで、データの追加や変更を行うための REST API が用意されています。Kubernetes のポッド用コンテナを作成するための docker-compose YAML ファイルは docker ディレクトリに配置されています。このチュートリアルでは、アプリケーション用のコンテナをビルドする service-docker-compose-k8s.yaml ファイルを使用します。

notescalendar の各ディレクトリには、アプリケーションをビルドするための Dockerfile が、Maven と Gradle の 2 つのセットで用意されています。このチュートリアルでは Maven を使用しますが、Gradle に慣れている場合は、ビルドコマンドを変更することで、Maven の代わりに Gradle を使用することができます。

notes アプリ、calendar アプリ、Datadog Agent の Kubernetes の構成ファイルは、kubernetes ディレクトリにあります。

サンプルアプリケーションを取得するまでの流れは、docker フォルダからイメージをビルドし、レジストリにアップロードし、kubernetes フォルダから kubernetes リソースを作成する、というものです。

クラスターを起動する

  1. 再利用したい GKE クラスターがまだない場合は、以下のコマンドを実行し、<VARIABLES> を使用したい値に置き換えて、クラスターを作成します。

    gcloud container clusters create <CLUSTER_NAME> --num-nodes=1 --network=<NETWORK> --subnetwork=<SUBNETWORK>

    : 利用可能なネットワークとサブネットワークの一覧は、次のコマンドを使用してください。

    gcloud compute networks subnets list

  2. 以下を実行してクラスターに接続します。

    gcloud container clusters get-credentials <CLUSTER_NAME>
    gcloud config set container/cluster <CLUSTER_NAME>
  3. デプロイされるアプリケーションとの通信を容易にするため、GKE クラスターがポート 3008030090 の TCP トラフィックを許可するように、ネットワークのファイアウォールルールを編集します。

アプリケーションイメージの構築とアップロード

Google Container Registry (GCR) に馴染みがない方は、Container Registry のクイックスタートを読むとよいかもしれません。

サンプルプロジェクトの /docker ディレクトリで、以下のコマンドを実行します。

  1. 以下を実行することで GCR で認証します。

    gcloud auth configure-docker

  2. サンプルアプリの Docker イメージを構築し、プラットフォーム設定を合わせます。

    DOCKER_DEFAULT_PLATFORM=linux/amd64 docker-compose -f service-docker-compose-k8s.yaml build notes

  3. コンテナに GCR 宛先のタグを付けます。

    docker tag docker-notes:latest gcr.io/<PROJECT_ID>/notes-tutorial:notes

  4. コンテナを GCR レジストリにアップロードします。

    docker push gcr.io/<PROJECT_ID>/notes-tutorial:notes

アプリケーションはコンテナ化され、GKE クラスターがプルできるようになります。

アプリケーションをローカルに構成してデプロイする

  1. kubernetes/notes-app.yaml を開き、image の項目を、上記ステップ 4 でコンテナをプッシュした GCR イメージの URL で更新します。

        spec:
          containers:
            - name: notes-app
              image: gcr.io/<PROJECT_ID>/notes-tutorial:notes
              imagePullPolicy: Always

  2. /kubernetes ディレクトリから、以下のコマンドを実行して、notes アプリをデプロイします。

    kubectl create -f notes-app.yaml

  3. アプリを実行するには、アプリの REST API を呼び出すための外部 IP アドレスを知っておく必要があります。まず、以下のコマンドで出力される一覧から notes-app-deploy というポッドを見つけ、そのノードをメモしておきます。

    kubectl get pods -o wide
    notes-app-deploy ポッドとその関連ノード名を示す kubectl コマンドの出力

    次に、次のコマンドの出力からそのノード名を見つけ、外部 IP の値をメモします。

    kubectl get nodes -o wide
    ノードの外部 IP 値を示す kubectl コマンドの出力

    この例では、notes-app はノード gke-java-tracing-gke-default-pool-ccbd5526-dd3d で動作しており、その外部 IP は 35.196.6.199 であることが示されています。

  4. 別のターミナルを開いて、アプリを行使するために API リクエストを送信します。ノートアプリケーションは、同じコンテナで実行されているメモリ内 H2 データベースにデータを保存する REST API です。これにいくつかのコマンドを送信します。

curl '<EXTERNAL_IP>:30080/notes'
[]
curl -X POST '<EXTERNAL_IP>:30080/notes?desc=hello'
{"id":1,"description":"hello"}
curl '<EXTERNAL_IP>:30080/notes?id=1'
{"id":1,"description":"hello"}
curl '<EXTERNAL_IP>:30080/notes'
[{"id":1,"description":"hello"}]
  1. アプリケーションの実行を確認したら、それを停止して、トレースを有効にします。
    kubectl delete -f notes-app.yaml

トレースを有効にする

Java アプリケーションが動作するようになったので、トレースを有効にするための構成を行います。

  1. Java tracing パッケージをプロジェクトに追加します。Agent は GKE クラスターで動作するため、Dockerfile が適切に構成されていることを確認し、何もインストールする必要はありません。notes/dockerfile.notes.maven ファイルを開き、dd-java-agent をダウンロードする行のコメントを解除します。

    RUN curl -Lo dd-java-agent.jar https://dtdg.co/latest-java-tracer
    
  2. 同じ notes/dockerfile.notes.maven ファイル内で、トレースなしで実行するための ENTRYPOINT 行をコメントアウトしてください。次に、トレースを有効にしてアプリケーションを実行する ENTRYPOINT 行のコメントを解除します。

    ENTRYPOINT ["java" , "-javaagent:../dd-java-agent.jar", "-Ddd.trace.sample.rate=1", "-jar" , "target/notes-0.0.1-SNAPSHOT.jar"]
    

    これにより、アプリケーションは自動的に Datadog のサービスにインスツルメンテーションされます。

    : これらのサンプルコマンドのフラグ、特にサンプルレートは、このチュートリアル以外の環境では、必ずしも適切ではありません。実際の環境で何を使うべきかについては、トレース構成を読んでください。
  3. 異なるバージョンやデプロイ環境間でトレースされたサービスを識別する統合サービスタグにより、Datadog 内で相関が取れるようになり、検索やフィルターに利用できるようになります。統合サービスタグ付けに使用する環境変数は、DD_SERVICEDD_ENVDD_VERSION の 3 つです。Kubernetes でデプロイされるアプリケーションでは、これらの環境変数をデプロイメント YAML ファイル内、特にデプロイメントオブジェクト、ポッド仕様、ポッドコンテナテンプレートに追加することができます。

    このチュートリアルでは、kubernetes/notes-app.yaml ファイルに、デプロイメントオブジェクト、ポッド仕様、ポッドコンテナテンプレートなど、ノートアプリケーションのためのこれらの環境変数がすでに定義されています。

    ...
    spec:
      replicas: 1
      selector: 
        matchLabels:
          name: notes-app-pod
          app: java-tutorial-app
      template:
        metadata:
          name: notes-app-pod
          labels:
            name: notes-app-pod
            app: java-tutorial-app
            tags.datadoghq.com/env: "dev"
            tags.datadoghq.com/service: "notes"
            tags.datadoghq.com/version: "0.0.1"
       ...
    

アプリケーションイメージの再構築とアップロード

docker ディレクトリで前と同じ手順で、トレースを有効にしてイメージを再構築します。

gcloud auth configure-docker
DOCKER_DEFAULT_PLATFORM=linux/amd64 docker-compose -f service-docker-compose-k8s.yaml build notes
docker tag docker-notes:latest gcr.io/<PROJECT_ID>/notes-tutorial:notes
docker push gcr.io/<PROJECT_ID>/notes-tutorial:notes

トレースを有効にしたアプリケーションはコンテナ化され、GKE クラスターがプルできるようになります。

Helm を使用した Agent のインストールと実行

次に、インスツルメンテーションされたアプリケーションからトレースデータを収集するために、GKE に Agent をデプロイします。

  1. kubernetes/datadog-values.yaml を開くと、GKE 上の Agent と APM に最低限必要な構成が表示されます。このコンフィギュレーションファイルは、次に実行するコマンドで使用されます。

  2. /kubernetes ディレクトリから、API キーとクラスター名を入れて、以下のコマンドを実行します。

    helm upgrade -f datadog-values.yaml --install --debug latest --set datadog.apiKey=<DD_API_KEY> --set datadog.clusterName=<CLUSTER_NAME> --set datadog.site=datadoghq.com datadog/datadog

    API キーを公開しない、より安全なデプロイについては、シークレットの使用に関するこのガイドをお読みください。また、us1 以外の Datadog サイトを使用している場合は、datadoghq.com を自分のサイトに置き換えてください。

自動トレースを見るためにアプリを起動する

[前回と同じ手順](#configure the-application-locally-and-deploy)で、notes アプリを kubectl create -f notes-app.yaml でデプロイし、実行するノードの外部 IP アドレスを確認します。

アプリを動かすために、いくつかの curl コマンドを実行します。

curl '<EXTERNAL_IP>:30080/notes'
[]
curl -X POST '<EXTERNAL_IP>:30080/notes?desc=hello'
{"id":1,"description":"hello"}
curl '<EXTERNAL_IP>:30080/notes?id=1'
{"id":1,"description":"hello"}
curl '<EXTERNAL_IP>:30080/notes'
[{"id":1,"description":"hello"}]

しばらく待って、Datadog の APM > Traces にアクセスすると、API 呼び出しに対応するトレースの一覧が表示されます。

APM トレースエクスプローラーのサンプルアプリのトレース

h2 はこのチュートリアルのために埋め込まれたメモリ内データベースで、notes は Spring Boot アプリケーションです。トレースリストには、すべてのスパン、いつ開始したか、どのリソースがスパンで追跡されたか、どれくらいの時間がかかったか、が表示されます。

もし、数分待ってもトレースが表示されない場合は、Traces Search フィールドのフィルターをクリアしてください (使用していない ENV などの環境変数にフィルターをかけている場合があります)。

トレースの検証

Traces ページで、POST /notes トレースをクリックすると、各スパンにかかった時間や、あるスパンが完了する前に他のスパンが発生したことを示すフレームグラフが表示されます。グラフの上部にあるバーは、前の画面で選択したスパンです (この場合、ノートアプリケーションへの最初のエントリポイントです)。

バーの幅は、それが完了するまでにかかった時間を示します。低い深さのバーは、高い深さのバーの寿命の間に完了するスパンを表します。

POST トレースのフレームグラフは次のようになります。

POST トレースのフレームグラフ。

GET /notes トレースは次のようになります。

GET トレースのフレームグラフ。

トレーシングのコンフィギュレーション

Java トレーシングライブラリは、Java のビルトイン Agent とモニタリングのサポートを利用します。Dockerfile のフラグ -javaagent:../dd-java-agent.jar は、JVM が Java Agent として実行できるように、Java トレーシングライブラリをどこで見つけるかを指示します。Java Agent については、https://www.baeldung.com/java-instrumentation で詳しく説明されています。

dd.trace.sample.rate フラグは、このアプリケーションのサンプルレートを設定します。Dockerfile の ENTRYPOINT コマンドでは、この値を 1 に設定しています。これは、notes サービスに対する全てのリクエストの 100% が、分析と表示のために Datadog のバックエンドに送信されることを意味します。低容量のテストアプリケーションの場合、これは問題ありません。実稼働時や大量のデータを扱う環境では、このようなことはしないでください。代わりに、リクエストの一部をサンプリングします。例えば、-Ddd.trace.sample.rate=0.1 とすると、リクエストの 10% 分のトレースが Datadog に送信されます。トレース構成設定サンプリング機構について詳しくお読みください。

このコマンドのサンプリングレートフラグは -jar フラグの前に表示されていることに注意してください。これは、このフラグがアプリケーションではなく、Java Virtual Machine のパラメーターだからです。アプリケーションに Java Agent を追加するときは、このフラグを正しい場所に指定するようにしてください。

Java アプリケーションに手動インスツルメンテーションを追加する

自動インスツルメンテーションは便利ですが、より細かいスパンが欲しい場合もあります。Datadog の Java DD Trace API では、アノテーションやコードを使用してコード内のスパンを指定することができます。

次のステップでは、ビルドスクリプトを修正して Java トレーシングライブラリをダウンロードし、コードにいくつかのアノテーションを追加して、いくつかのサンプルメソッドにトレースする手順を説明します。

  1. 現在のアプリケーションデプロイを削除します。

    kubectl delete -f notes-app.yaml

  2. /notes/src/main/java/com/datadog/example/notes/NotesHelper.java を開きます。このサンプルには、コードにカスタムトレースを設定するさまざまな方法を示す、コメントアウトされたコードがすでに含まれています。

  3. 手動トレーシングをサポートするためのライブラリをインポートしている行のコメントを解除します。

    import datadog.trace.api.Trace;
    import datadog.trace.api.DDTags;
    import io.opentracing.Scope;
    import io.opentracing.Span;
    import io.opentracing.Tracer;
    import io.opentracing.tag.Tags;
    import io.opentracing.util.GlobalTracer;
    import java.io.PrintWriter;
    import java.io.StringWriter
    
  4. 2 つのパブリックプロセスを手動でトレースしている行のコメントを解除します。これらは、@Trace アノテーションを使用して、operationNameresourceName などのアスペクトをトレースで指定することを示しています。

    @Trace(operationName = "traceMethod1", resourceName = "NotesHelper.doLongRunningProcess")
    // ...
    @Trace(operationName = "traceMethod2", resourceName = "NotesHelper.anotherProcess")
    
  5. また、アプリケーション内の特定のコードブロックに対して、別のスパンを作成することもできます。スパン内には、サービスやリソース名のタグ、エラー処理タグを追加します。これらのタグは、Datadog の視覚化でスパンとメトリクスを表示するフレームグラフになります。プライベートメソッドを手動でトレースする行のコメントを解除します。

            Tracer tracer = GlobalTracer.get();
            // Tags can be set when creating the span
            Span span = tracer.buildSpan("manualSpan1")
                .withTag(DDTags.SERVICE_NAME, "NotesHelper")
                .withTag(DDTags.RESOURCE_NAME, "privateMethod1")
                .start();
            try (Scope scope = tracer.activateSpan(span)) {
                // Tags can also be set after creation 
                span.setTag("postCreationTag", 1);
                Thread.sleep(30);
                Log.info("Hello from the custom privateMethod1");
    

    また、エラー時にタグを設定する行も:

         } catch (Exception e) {
             // Set error on span
             span.setTag(Tags.ERROR, true);
             span.setTag(DDTags.ERROR_MSG, e.getMessage());
             span.setTag(DDTags.ERROR_TYPE, e.getClass().getName());
    
             final StringWriter errorString = new StringWriter();
             e.printStackTrace(new PrintWriter(errorString));
             span.setTag(DDTags.ERROR_STACK, errorString.toString());
             Log.info(errorString.toString());
         } finally {
             span.finish();
         }
    
  6. notes/pom.xml を開き、手動トレースの依存関係を構成する行のコメントを解除して、Maven ビルドを更新します。dd-trace-api ライブラリは @Trace アノテーションに使用され、opentracing-utilopentracing-api は手動でスパンを作成するために使用されます。

  7. アプリケーションを再構築し、前回と同じ手順に従って GCR にアップロードし、docker ディレクトリで以下のコマンドを実行します。

    gcloud auth configure-docker
    DOCKER_DEFAULT_PLATFORM=linux/amd64 docker-compose -f service-docker-compose-k8s.yaml build notes
    docker tag docker-notes:latest  gcr.io/<PROJECT_NAME>/notes-tutorial:notes
    docker push gcr.io/<PROJECT_NAME>/notes-tutorial:notes
  8. [前回と同じ手順](#configure the-application-locally-and-deploy)で、notes アプリを kubectl create -f notes-app.yaml でデプロイし、実行するノードの外部 IP アドレスを確認します。

  9. いくつかの HTTP リクエスト、特にいくつかの GET リクエストを再送します。

  10. トレースエクスプローラーで、新しい GET リクエストの 1 つをクリックすると、次のようなフレームグラフが表示されます。

    カスタムインスツルメンテーションを用いた GET トレースのフレームグラフ。

    getAll 関数にカスタムトレースが追加され、スタックトレースがより詳細になったことに注意してください。

    手動でスパンを作成した privateMethod は、他のコールとは別のブロックとして表示され、別の色でハイライトされています。@Trace アノテーションを使用した他のメソッドは、GET リクエスト (notes アプリケーション) と同じサービス、同じ色で表示されます。カスタムインスツルメンテーションは、ハイライトして監視する必要があるコードの重要な部分がある場合に有効です。

詳しくは、カスタムインストルメンテーションをご覧ください。

分散型トレーシングを見るために 2 つ目のアプリケーションを追加する

単一のアプリケーションをトレースすることは素晴らしいスタートですが、トレースの本当の価値は、リクエストがサービスを通じてどのように流れるかを見ることです。これは、_分散型トレーシング_と呼ばれています。

サンプルプロジェクトには calendar という 2 番目のアプリケーションが含まれており、呼び出されるたびにランダムな日付を返します。Notes アプリケーションの POST エンドポイントには、add_date という名前の 2 つ目のクエリパラメーターがあります。このパラメータが y に設定されると、Notes はカレンダーアプリケーションを呼び出して、ノートに追加する日付を取得します。

  1. ノートアプリと同様に、Dockerfile の起動コマンドに dd-java-agent を追加して、トレース用の calendar アプリの構成を確認します。calendar/dockerfile.calendar.maven を開き、すでに dd-java-agent がダウンロードされていることを確認します。

    RUN curl -Lo dd-java-agent.jar https://dtdg.co/latest-java-tracer
    
  2. 同じ calendar/dockerfile.calendar.maven ファイル内で、トレースなしで実行するための ENTRYPOINT 行をコメントアウトしてください。次に、トレースを有効にしてアプリケーションを実行する ENTRYPOINT 行のコメントを解除します。

    ENTRYPOINT ["java" , "-javaagent:../dd-java-agent.jar", "-Ddd.trace.sample.rate=1", "-jar" , "target/calendar-0.0.1-SNAPSHOT.jar"]
    
    : 繰り返しになりますが、フラグ、特にサンプルレートは、このチュートリアル以外の環境では、必ずしも適切ではありません。実際の環境で何を使うべきかについては、トレース構成を読んでください。
  3. 両方のアプリケーションをビルドし、GCR に公開します。docker ディレクトリから、以下を実行します。

    gcloud auth configure-docker
    DOCKER_DEFAULT_PLATFORM=linux/amd64 docker-compose -f service-docker-compose-k8s.yaml build calendar
    docker tag docker-calendar:latest  gcr.io/<PROJECT_NAME>/calendar-tutorial:calendar
    docker push gcr.io/<PROJECT_NAME>/calendar-tutorial:calendar

  4. kubernetes/calendar-app.yaml を開き、image エントリを GCR イメージの URL で更新します。これは前のステップで calendar アプリ をプッシュした場所です。

        spec:
          containers:
            - name: calendar-app
              image: gcr.io/<PROJECT_ID>/calendar-tutorial:calendar
              imagePullPolicy: Always

  5. kubernetes ディレクトリから、カスタムインスツルメンテーションを持つようになった notescalendar の両アプリをクラスター上にデプロイします。

    kubectl create -f notes-app.yaml
    kubectl create -f calendar-app.yaml

  6. 先ほどの方法で、notes アプリの外部 IP を探します。

  7. add_date パラメーターを指定して、POST リクエストを送信します。

curl -X POST '<EXTERNAL_IP>:30080/notes?desc=hello_again&add_date=y'
{"id":1,"description":"hello_again with date 2022-11-06"}
  1. トレースエクスプローラーで、この最新のトレースをクリックすると、2 つのサービス間の分散型トレーシングが表示されます。

    分散型トレーシングのフレームグラフ。

    notes アプリケーションでは何も変更していないことに注意してください。Datadog は notes から calendar への HTTP コールに使用される okHttp ライブラリと、notescalendar の HTTP リクエストをリッスンするために使用する Jetty ライブラリの両方を自動的にインスツルメントします。これにより、トレース情報を 1 つのアプリケーションから他のアプリケーションに渡すことができ、分散型トレースをキャプチャすることができます。

  2. 確認が終わったら、すべてのリソースをクリーンアップし、デプロイを削除してください。

    kubectl delete -f notes-app.yaml
    kubectl delete -f calendar-app.yaml

    クラスターの削除については、GKE のドキュメントを参照してください。

トラブルシューティング

もし、期待通りのトレースが受信できない場合は、Java トレーサーのでデバッグモードを設定してください。詳しくはデバッグモードの有効化を読んでください。

その他の参考資料