やりたい事
- 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 にリクエストを投げる単純なシステムになります。
Spring Boot アプリケーションの作成
api-service の実装
構成図の api-service に該当するアプリケーションを作成します。
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
を使用する必要があります。
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()
}
}
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
)
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
)
}
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 と同じです。
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
)
}
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/
コンソールログにはそれぞれ以下のlogが出力されます。
fd1b6573c72d8270f7b993bff6cccb4b
の部分がTrace IDになり、73daf3d1a44fa0ce
やbab80bc50c856162
は、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