OpenTelemetry + Jaeger でトレーシングを試してみた

2020.05.27

OpenTelemetryJaegerTechnology Radar Vol.22でTRIALとして取り上げられており、今後に向けて触れておきたい要素です。

今回はOpenTelemetryの中でも OpenTelemetry Auto-Instrumentation for Javaを使って、すでにリリースされているprismatixのような既存サービスへ簡単に導入ができる方法を試してみます。

またOpenTelemetry Auto-Instrumentation for Javaで対応しているフレームワークとライブラリは supported-java-libraries-and-frameworks を見てください。

注) 執筆時点でOpenTelemetry Auto-Instrumentation for JavaはBeta版のため、検証以外の利用にはご注意ください

構成

検証していく構成はSpring Boootを用いて次のように2つのマイクロサービスが連携するように作成します。

構成図

マイクロサービスの作成

マイクロサービスはSpring Bootを使用して構築します。ひな形としてSpring Initializr を使用して作成します。

demo1 の作成

外部からリクエストを受けて、demo2へリクエストを投げる処理を記載します。

  • settings.gradle

    アプリケーションの名前が重複するとJARを見た際に区別がつかなくなるため、わかりやすい名前にします。

    rootProject.name = 'demo1'
  • build.gradle

    レスポンスデータを作成する際に ImmutableMap を使いたいため以下を追加します。

    dependencies {
        // ... 省略 ...
        compile group: 'com.google.guava', name: 'guava', version: '29.0-jre'
        // ... 省略 ...
    }
    
  • application.properties

    アプリケーションを複数起動するため、ポートや名前は重複しないように設定します。

    // Springの起動PORT
    server.port=8080
    
    // アプリケーション名
    spring.application.name = demo1
    
    // demo2のリクエスト送信URL
    backendBaseUrl=http://localhost:8081/api
    
  • APIエンドポイント作成

    リクエストを受けて demo2 へリクエストを送り、その結果と自身のアプリケーション名、現在時刻を返す処理を記載します。

    //packageとimportの記載は省略
    
    @EnableAutoConfiguration
    @RestController
    public class ApiController {
    
        @Autowired
        RestTemplate restTemplate;
    
        @Value("${backendBaseUrl}")
        private String backendBaseUrl;
    
        @Value("${spring.application.name}")
        private String applicationName;
    
        @RequestMapping("/link")
        public Map<String, String> printDate(@RequestHeader Map<String, String> headers) throws JsonProcessingException {
    
            String result = restTemplate.getForObject(backendBaseUrl, String.class);
            Map<String, String> articles = ImmutableMap.of(
                    "appName", this.applicationName,
                    "dateTime", new Date().toString(),
                    "backendResult", result
            );
            return articles;
        }
    
        @Bean
        RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    

demo2 の作成

demo1と基本的には同じのため差分のAPIエンドポイントのみ記載します。

  • APIエンドポイント作成

    demo1 からリクエストを受けて、自身のアプリケーション名と現在時刻を返す処理を記載します。

    //packageとimportの記載は省略
    
    @EnableAutoConfiguration
    @RestController
    public class ApiController {
        private static final Marker MARKER = MarkerFactory.getMarker("AUDIT");
        private static final Logger logger = LoggerFactory.getLogger(ApiController.class);
    
        @Value("${spring.application.name}")
        private String applicationName;
    
        @RequestMapping("/api")
        public Map<String, String> printDate(@RequestHeader Map<String, String> headers) throws JsonProcessingException {
    
            logger.info(MARKER, ImmutableMap.of("RequestHeader", headers).toString());
            Map<String, String> articles
                    = ImmutableMap.of("appName", this.applicationName, "dateTime", new Date().toString());
            return articles;
        }
    }
    

Spring Boot をビルド

demo1 と demo2 をそれぞれ gradlew build でビルドします。ビルドされた JARは build/libs/ 内に出力されます。

環境構築

1. OpenTelemetry Auto-Instrumentation for Javaをダウンロード

OpenTelemetry Auto-Instrumentation for Javaのリリースページ より以下の2つをダウンロードします

  • opentelemetry-auto-0.3.0.jar
  • opentelemetry-auto-exporters-jaeger-0.3.0.jar

ダウンロードしたファイルは、わかりやすいところに配置します。今回は以下のような配置としました。

├── demo1 (Spring Bootで今回作成したアプリケーション)
├── demo2 (Spring Bootで今回作成したアプリケーション)
├── opentelemetry-auto-0.3.0.jar
├── opentelemetry-auto-exporters-jaeger-0.3.0.jar
└── opentelemetry-auto-exporters-logging-0.3.0.jar  ([おまけ]の項で使います)

注) 執筆時点で最新版のBeta v0.3.0を使用します。

2. Jaegerの起動

DockerでJaegerを起動します1。起動後にブラウザから http://localhost:16686 でトレーシング結果を確認できます。

docker run --rm -it --name jaeger \
  -p 16686:16686 \
  -p 14250:14250 \
  jaegertracing/all-in-one:1.18

3. アプリケーションの起動

アプリケーションの demo1 と demo2 をそれぞれ以下のコマンドで起動します2

  • demo1 の起動
    java -javaagent:opentelemetry-auto-0.3.0.jar \
         -Dota.exporter.jar=opentelemetry-auto-exporters-jaeger-0.3.0.jar \
         -Dota.exporter.jaeger.endpoint=localhost:14250 \
         -Dota.exporter.jaeger.service.name=demo1 \
         -jar demo1/build/libs/demo1-0.0.1-SNAPSHOT.jar
    
  • demo2 の起動
    java -javaagent:opentelemetry-auto-0.3.0.jar \
         -Dota.exporter.jar=opentelemetry-auto-exporters-jaeger-0.3.0.jar \
         -Dota.exporter.jaeger.endpoint=localhost:14250 \
         -Dota.exporter.jaeger.service.name=demo2 \
         -jar demo2/build/libs/demo2-0.0.1-SNAPSHOT.jar
    

Jaeger でトレーシング

demo1 と demo2 の両方が起動できたことを確認し、 http://localhost:8080/link でアプリケーションにリクエストを送信します。

そしてブラウザで Jaeger を表示させると、このようにトレースされた結果が確認できます。

また個別のリクエストをクリックして開くことで、demo1 -> demo2にリクエストが送られている様子がわかり、各処理時間も確認することもできます。

このように既存のアプリケーションのJARに改修を入れることなく、トレースできるため使いやすいのではないでしょうか。(簡単に設定できるため記事の半分以上が、アプリケーションを作成する内容になってしまいました。)

[おまけ] コンソールにログ出力する

OpenTelemetry Auto-Instrumentation for Javaのリリースページ より以下を追加でダウンロードします。

  • opentelemetry-auto-exporters-logging-0.3.0.jar

実行

Jaegerに送信していたときと同様にJARの実行時パラメーターに指定しますが、 -Dota.exporter.jar を先ほどダウンロードしたJARに変えるだけです。

# demo1
java -javaagent:opentelemetry-auto-0.3.0.jar \
         -Dota.exporter.jar=opentelemetry-auto-exporters-logging-0.3.0.jar \
         -jar demo1/build/libs/demo1-0.0.1-SNAPSHOT.jar
# demo2
java -javaagent:opentelemetry-auto-0.3.0.jar \
         -Dota.exporter.jar=opentelemetry-auto-exporters-logging-0.3.0.jar \
         -jar demo2/build/libs/demo2-0.0.1-SNAPSHOT.jar

ログ

Jaegerの際と同様に demo1 のエンドポイントにリクエストを送ると、それぞれ以下のようなログが出力されます。このログはスパンの名前とその属性を標準出力しているのみであり、テストやデバッグ用だそうです 3 。またログのフォーマットに関する記載が見つけられなかったため、詳細がわかりませんがログ出力だけでトレーシングは現時点で難しそうです。

注) 実際のログは1リクエストが1行にまとめて出力されますが、読みづらいため適宜改行を入れています。

  • demo1
    Logging Exporter: HTTP GET 1f32fe36ced2c95f 
        net.peer.name="localhost" 
        http.url="http://localhost:8081/api" 
        http.status_code=200 
        net.peer.port=8081 
        http.method="GET" 
        Logging Exporter: ApiController.printDate edceaceb2608f708 
            Logging Exporter: /link ed9a1a1116d4fef5 
                http.status_code=200 
                net.peer.port=50087 
                servlet.path="/link" 
                servlet.context="" 
                http.url="http://localhost:8080/link" 
                net.peer.ip="0:0:0:0:0:0:0:1" 
                http.method="GET" span.origin.type="org.apache.catalina.core.ApplicationFilterChain"
    
  • demo2
    Logging Exporter: ApiController.printDate adcecb77d9cf7351 
        Logging Exporter: /api 21c6c3ce6f5fd64d 
            http.status_code=200
            net.peer.port=50089 
            servlet.path="/api" 
            servlet.context="" 
            http.url="http://localhost:8081/api" 
            net.peer.ip="127.0.0.1" 
            http.method="GET" span.origin.type="org.apache.catalina.core.ApplicationFilterChain"
    

参考


  1. https://www.jaegertracing.io/docs/1.18/getting-started/ 
  2. https://github.com/open-telemetry/opentelemetry-auto-instr-java#getting-started 
  3. https://github.com/open-telemetry/opentelemetry-auto-instr-java#logging-exporter