基于 Docker 简单搭建 Jaeger 的示例,此例子运行在虚拟机中,虚拟机的 IP 为 192.168.99.100
Jaeger 搭建
创建数据库 Cassandra
使用的 docker-compose 文件如下:1
2
3
4
5
6
7
8
9version: "3"
services:
cassandra:
container_name: jaeger_db_cassandra
image: cassandra:3
volumes:
- /var/lib/cassandra
ports:
- 9042:9042
使用 Jaeger 提供的镜像为 Cassandra 初始化数据库
1 | docker run --link jaeger_db_cassandra:cassandra --net cassandra_default --rm -ti jaegertracing/jaeger-cassandra-schema |
因为 Cassandra 用了 Docker-Compose 启动,所以初始化时要用 --net
参数连接到 Cassandra 所在的网络
启动 Jaeger 的 Query 、 Collector 和 Agent 服务
使用的 docker-compose 文件如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33version: "3"
services:
query:
container_name: jaeger_query
image: jaegertracing/jaeger-query:1.11.0
environment:
- SPAN_STORAGE_TYPE=cassandra
- CASSANDRA_KEYSPACE=jaeger_v1_dc1
- CASSANDRA_SERVERS=192.168.99.100
ports:
- 16686:16686/tcp
collector:
container_name: jaeger_collector
image: jaegertracing/jaeger-collector:1.11.0
environment:
- SPAN_STORAGE_TYPE=cassandra
- CASSANDRA_KEYSPACE=jaeger_v1_dc1
- CASSANDRA_SERVERS=192.168.99.100
ports:
- 9411:9411/tcp
- 14267:14267/tcp
- 14268:14268/tcp
agent:
container_name: jaeger_agent
image: jaegertracing/jaeger-agent:1.11.0
environment:
- COLLECTOR_HOST_PORT=192.168.99.100:14267
ports:
- 5775:5775/udp
- 6831-6832:6831-6832/udp
- 5778:5778/tcp
depends_on:
- collector
每个组件可配置的参数可运行对应的镜像 + -h
参数查看,如:1
docker run --rm -ti jaegertracing/jaeger-collector -h
用 Docker-Compose 运行 Jaeger 时,参数除了配置成环境变量,也可以配置成启动参数的形式,对应的环境变量的名称就是启动的参数去掉 --
,字母大写,’.
‘ 转换成 ‘_
‘,如:1
2
3
4
5
6
7
8version: "3"
services:
query:
container_name: jaeger_query
image: jaegertracing/jaeger-query:1.11.0
ports:
- 16686:16686/tcp
command: "--cassandra.keyspace jaeger_v1_dc1 --cassandra.servers 192.168.99.100"
示例代码
首先构造一个项目,作为前端,用户访问前端的地址时,前端会调用一次微服务,并返回页面给用户
用 IDEA 新建项目,选择 Spring Initializr
,后面的依赖中选择 Web
就行
build.gradle
中引入 Jaeger Client
和发起 Http 调用的 okhttp
,如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36plugins {
id 'org.springframework.boot' version '2.1.4.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'frontend'
}
repositories {
mavenCentral()
}
sourceCompatibility = '11'
ext {
set('springCloudVersion', 'Greenwich.SR1')
}
dependencies {
implementation 'io.jaegertracing:jaeger-client:0.34.0'
implementation 'com.squareup.okhttp3:okhttp:3.4.2'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
注册 Tracer
的 Bean ,为了方便这里直接放在框架启动类 Application
中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
public Tracer tracer() {
SamplerConfiguration samplerConfig = SamplerConfiguration.fromEnv()
.withType(ConstSampler.TYPE)
.withParam(1);
ReporterConfiguration reporterConfig = ReporterConfiguration.fromEnv()
.withLogSpans(true);
Configuration config = new Configuration("frontend-demo")
.withSampler(samplerConfig)
.withReporter(reporterConfig);
return config.getTracer();
}
}
控制器中调用别的服务时,将其包装为 Span
,为了能构造调用的上下级关系,把本次调用的 Span
的上下文注入(Inject)到 Tracer
中,如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class FrontendController {
private Tracer tracer;
"/") (
public String index(Model model) throws IOException, InterruptedException {
Scope span = tracer.buildSpan("frontend-index").startActive(true);
String response = callExternalService("eureka-hello-service");
span.close();
model.addAttribute("data", response);
return "index";
}
private String callExternalService(String name) throws IOException, InterruptedException {
String url = "http://192.168.99.100:21001/greeting";
Request.Builder requestBuilder = new Request.Builder().url(url);
tracer.inject(tracer.activeSpan().context(), Format.Builtin.HTTP_HEADERS, new RequestBuilderCarrier(requestBuilder));
Request request = requestBuilder.build();
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
}
其中,注入 Span
上下文时用到的 RequestBuilderCarrier
可自定义,如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import io.opentracing.propagation.TextMap;
import okhttp3.Request;
import java.util.Iterator;
import java.util.Map;
public class RequestBuilderCarrier implements TextMap {
private final Request.Builder requestBuilder;
public RequestBuilderCarrier(Request.Builder requestBuilder) {
this.requestBuilder = requestBuilder;
}
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException("carrier is writer-only");
}
public void put(String key, String value) {
requestBuilder.addHeader(key, value);
}
}
同上,新建一个 Spring Boot 项目,作为被调用的微服务,Jaeger 配置相同,控制器如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
private Tracer tracer;
"/greeting") (
public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name,
@RequestHeader HttpHeaders headers)
throws IOException, InterruptedException {
SpanContext spanContext = tracer.extract(Format.Builtin.HTTP_HEADERS,
new TextMapExtractAdapter(headers.toSingleValueMap()));
Span span = tracer.buildSpan("test").asChildOf(spanContext).start();
span.finish();
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
启动示例代码
首先,在两个项目中分别运行 gradle build
生成 Jar 文件,这里是 frontend.jar 和 ms-service.jar ,把它们跟docker-compose.yml文件放一起,docker-compose.yml 如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22version: "3"
services:
frontend:
container_name: frontend_1
image: adoptopenjdk/openjdk11:jdk-11.0.2.9
volumes:
- ./frontend.jar:/frontend.jar
command: java -jar /frontend.jar
ports:
- 21000:8080
environment:
- JAEGER_AGENT_HOST=192.168.99.100
msservice:
container_name: ms_service_1
image: adoptopenjdk/openjdk11:jdk-11.0.2.9
volumes:
- ./ms-service.jar:/ms-service.jar
command: java -jar /ms-service.jar
ports:
- 21001:8080
environment:
- JAEGER_AGENT_HOST=192.168.99.100
注意代码跟 docker-compose.yml 中的地址、端口的对应关系
用 Docker-Compose 启动项目,在浏览器中访问前端地址 192.168.99.100:21000,获取到返回的页面后,访问 Jaeger Query 的地址 192.168.99.100:16686,左侧栏选择前端服务,点击 Find Traces
,即可看到调用链信息