Zipkin導入最小ステップ


まえがき

既存のSpring BootプロジェクトへのZipkin導入をできるだけ少ないステップでまとめてみました。
サービス間のやり取りの結果についても書きたかったですが、別に何も意識せずとも勝手にやってくれるので、今回の記事では言及しません。
HTTP周りのやり取りはspring-mvc, RestTemplateを使っていれば勝手にやってくれます。

Step1 依存の追加

Maven

Spring IO Platform を使っている場合

pom.xml
<properties>
  <spring-cloud.version>Edgware.RELEASE</spring-cloud.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
  </dependency>
  <!-- Kafka経由で送る場合はこれも必要 -->
  <dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
  </dependency>
</dependencies>

Spring Boot のみの場合

公式リファレンスを参照してください。

Gradle

公式リファレンスを参照してください。

Step2 設定値の追加

Localマシンに全てデフォルトポートで構築済みであれば不要です。

HTTP送信

application.yaml
spring:
  zipkin:
    base-url: http://localhost:9411/ # default

Kafka送信

application.yaml
spring:
  kafka:
    bootstrap-servers: localhost:9092 # default

Step3 動作確認環境構築

Mac前提です。

Kafka & Zookeepr

$ brew install kafka
$ brew services start zookeeper
$ brew services start kafka

Zipkin

$ wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'

HTTP経由のみ

$ java -jar zipkin.jar

Kafka対応

$ KAFKA_ZOOKEEPER=localhost:2181 java -jar zipkin.jar

Step4 動作確認

@RequestMapping("/hello")
String hello() {
  return "hello world"
}

同作確認なので全てのリクエストをZipkinに送ります。

application.yaml
spring:
  sleuth:
    sampler:
      percentage: 1.0 # 昔の名残でプロパティ名がパーセンテージになっていますが、割合指定らしいです。

http://localhost:9411/にアクセスする。

ここまでの手順は依存ライブラリを追加しただけです。
送信先がデフォルトでない場合は、送信先のを指定をします。

Step5 Spanの追加

これだけではリクエスト受信からレスポンス返却までの時間しかわかりません。
もう少しSpanを増やしてみます。

@Autowired
DemoService demoService;

@RequestMapping("/hello")
public String helloWorld() throws Exception {
  Thread.sleep(300);
  return demoService.greeting("world");
}


@Service
static class DemoService {

  @NewSpan("greeting")
  String greeting(String target) throws Exception {
    Thread.sleep(200);
    return "Hello " + target;
  }
}

タグの追加

@Service
static class DemoService {

  @NewSpan("greeting")
  String greeting(@SpanTag("target") String target) throws Exception {
    Thread.sleep(200);
    return "Hello " + target;
  }
}

@SpanTag("target") をパラメータに追加しただけです。

色々な型のタグ

Zipkinはタグの値を文字列でしか受け取れないので、JSONにできるならそうして送ると良いです。

@NewSpan("greeting-name")
public String greeting(@SpanTag(value = "name", resolver = AsJson.class) Name name) throws Exception {
  Thread.sleep(200);
  return "Hello " + name + ".";
}
AsJson.java
@Component
public class AsJson implements TagValueResolver {

  ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
      .indentOutput(true)
      .build();

  @Override
  public String resolve(Object parameter) {
    if (parameter == null) {
      return null;
    }
    if (String.class.isAssignableFrom(parameter.getClass())) {
      return (String) parameter;
    }

    try {
      return objectMapper.writeValueAsString(parameter);
    } catch (JsonProcessingException e) {
      return null;
    }
  }
}

@SpanTag(key = "name") だと手元の環境だと認識されなかったので、value =で送りました。
@Aliasの処理ができていないのか、後でコードを追ってみます。

JSONタグを確認してみます。

大丈夫そうです。

トラブルシューティング

ZipkinサーバーがV1でJSONフォーマットが違うんだけど。

JSONフォーマットはバージョン指定ができます。

application.yaml
spring:
  zipkin:
    encoder: json_v1

Zipkinサーバーでサービス名のラベルが空文字列で表示される

V1のZipkinはラベル表示に使用するサービス名を規定のタグでしか見ないようです。
カスタムタグのみのSpanではサービス名を見てくれないらしいです。
https://github.com/openzipkin/zipkin/issues/962

そこで適当にコンポーネント名を設定しておきます。

@Bean
@ConditionalOnProperty(name = "spring.zipkin.encoder", havingValue = "json_v1")
SpanAdjuster spanAdjuster() {
  return span -> {
    span.tag(Span.SPAN_LOCAL_COMPONENT_TAG_NAME, "my-component");
    return span;
  };
}

これで大丈夫です。

非同期処理で途切れる

公式リファレンスを参照してください。

@Asyncではデフォルトで途切れない設定になっていると思います。

ExecutorServiceを使っている場合は、

ExecutorService traceableExecutorService = new TraceableExecutorService(beanFactory, executorService);

として、使えば大丈夫です。

処理の途中でタグを追加したい

アノテーションで指定できないタイミングでタグを追加したい時があります。

@Autowired
Tracer tracer

public void something() {
  tracer.addTag("$name", "$value");
}

これでOKです。現在のSpanにタグ付けされます。