LoginSignup
1
0

Spring Boot x Micrometer x Zipkinでトレーシング

Last updated at Posted at 2023-12-10

やりたい事

  • Spring Boot、Micrometer、RestTemplate を使用する
  • 複数アプリケーション間でトレースIDを伝播する
  • トレース情報をZipkinに送信する

参考にした記事

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#actuator.metrics.micrometer-observation
https://kazuhira-r.hatenablog.com/entry/2023/11/03/234914

構成

api-service から customer-service にリクエストを投げる単純なシステムになります。
distributed-tracing.drawio.png

Spring Boot アプリケーションの作成

api-service の実装

構成図の api-service に該当するアプリケーションを作成します。

build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
	id("org.springframework.boot") version "3.2.0"
	id("io.spring.dependency-management") version "1.1.4"
	kotlin("jvm") version "1.9.20"
	kotlin("plugin.spring") version "1.9.20"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
	sourceCompatibility = JavaVersion.VERSION_17
}

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter")
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.springframework.boot:spring-boot-starter-actuator")
	//	Micrometer Observation APIをOpenTelemetryにブリッジするライブラリ
	implementation("io.micrometer:micrometer-tracing-bridge-otel:1.2.0")
	// トレース情報を Zipkin に送信するライブラリ
	implementation("io.opentelemetry:opentelemetry-exporter-zipkin:1.32.0")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
	kotlinOptions {
		freeCompilerArgs += "-Xjsr305=strict"
		jvmTarget = "17"
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

ネットワーク上でトレースを自動的に伝播させるには、auto-configured された RestTemplateBuilderを使用する必要があります。

RestTemplateAutoConfiguration.kt
import org.springframework.boot.autoconfigure.AutoConfigureAfter
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestTemplate

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration::class)
class RestTemplateAutoConfiguration(
    private val restTemplateBuilder: RestTemplateBuilder
) {

    @Bean
    fun restTemplate(): RestTemplate {
        return restTemplateBuilder.build()
    }
}
CustomerService.kt
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import org.springframework.web.client.getForEntity
import org.springframework.web.util.UriComponentsBuilder

@Service
class CustomerService(
    private val restTemplate: RestTemplate
) {
    fun get(): CustomerRes {
        val uri = UriComponentsBuilder.fromUriString("http://localhost:8081")
            .path("customer")
            .toUriString()

        val response: ResponseEntity<CustomerRes> = restTemplate.getForEntity(
            uri,
            CustomerRes::class
        )

        return response.body!!
    }
}

data class CustomerRes(
    val value: String
)
CustomerController.kt
import org.apache.commons.logging.LogFactory
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class CustomerController(
    private val customerService: CustomerService
) {
    private val logger = LogFactory.getLog(CustomerController::class.java)

    @RequestMapping("/customer")
    fun get(): HelloRes {
        logger.info("get() has been called")
        val result = customerService.get()
        return HelloRes(
            result.value
        )
    }

    class HelloRes(
        val value: String
    )
}
application.yml
spring:
  application:
    name: api-service customer-service # Zipkin に送信されるサービス名。同一のサービスと認識されてしまう為、一意である必要がある

management:
  tracing:
    sampling:
      probability: 1.0 # トレース情報の送信割合。1.0 で全てのトレース情報が送信される

logging:
  level:
    root: INFO

customer-service の実装

構成図の customer-service に該当するアプリケーションを作成します。
build.gradle.kts は api-service と同じです。

CustomerController.kt
import org.apache.commons.logging.LogFactory
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class CustomerController {
    private val logger = LogFactory.getLog(CustomerController::class.java)

    @GetMapping("/customer")
    fun get(): CustomerRes {
        logger.info("get() has been called")
        return CustomerRes("AAA株式会社")
    }

    class CustomerRes(
        val value: String
    )
}
application.yml
spring:
  application:
    name: api-service customer-service # Zipkin に送信されるサービス名。同一のサービスと認識されてしまう為、一意である必要がある

management:
  tracing:
    sampling:
      probability: 1.0 # トレース情報の送信割合。1.0 で全てのトレース情報が送信される

logging:
  level:
    root: INFO

Zipkin の起動

docker run -d -p 9411:9411 openzipkin/zipkin

Zipkin でトレース情報を見る

環境の構築が終わったので、実際に Zipkin でトレース情報を見てみます。
アプリケーションを何回か動かします。
http://localhost:8080/customer

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

以下のようなトレース情報が表示されます。
Zipkin_list.png

Zipkin_detail.png

Zipkin_dependencies.png

コンソールログにはそれぞれ以下のlogが出力されます。
fd1b6573c72d8270f7b993bff6cccb4bの部分がTrace IDになり、73daf3d1a44fa0cebab80bc50c856162は、Span IDになります。
こちらに記載している設定においては、Trace IDは一連のリクエストに対して同一の値が出力され、Span IDは各アプリケーション内で一連の処理に対して同一の値が出力されます。

api-service

2023-12-16T22:04:05.057+09:00  INFO 32049 --- [api-service customer-service] [nio-8080-exec-3] [fd1b6573c72d8270f7b993bff6cccb4b-73daf3d1a44fa0ce] c.e.s.controller.CustomerController      : get() has been called
2023-12-16T22:27:51.215+09:00  INFO 32049 --- [api-service customer-service] [nio-8080-exec-8] [b968352258fd27290e96238293776cb4-c5738f85f6f22a95] c.e.s.controller.CustomerController      : get() has been called

customer-service

2023-12-16T22:04:05.090+09:00  INFO 27445 --- [customer-service] [nio-8081-exec-7] [fd1b6573c72d8270f7b993bff6cccb4b-bab80bc50c856162] c.e.s.controller.CustomerController      : get() has been called
2023-12-16T22:27:51.225+09:00  INFO 27445 --- [customer-service] [nio-8081-exec-8] [b968352258fd27290e96238293776cb4-b57d4c22ce9b8af4] c.e.s.controller.CustomerController      : get() has been called
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0