基于 Jaeger 進行微服務鏈路追蹤
基于解決不同行業、業務應用的可擴展性、可用性等一系列問題,由此而生的微服務架構得到了各大廠商的、組織以及個人的青睞,隨之而來便廣泛應用于各種行業場景應用中。然而,隨著時間的推移,越來越多的問題慢慢地呈現在大眾的視野中。
其中,最為核心的問題莫過于微服務分布式性質導致的運行問題,以及帶來的 2 個至關重要的挑戰:
1、Monitoring,監控,如何能夠全方位監控所有服務及其所涉及的相關指標。
2、Tracing,追蹤,如何能夠立體化追蹤所有請求并識別我們應用服務中鏈路調用的瓶頸?
在本文中,我們將介紹如何將 Jaeger 被分類的跟蹤集成到 Spring Boot MicroServices 中。在解析之前,我們先來了解下 Jaeger 鏈路追蹤工作流原理,具體如如下參考示意圖所示:

基于 Jaeger 組件架構原理,我們可以看到:在分布式系統中處理,當一個跟蹤完成后,通過 Jaeger-Agent 將數據推送到 Jaeger-Collector。此時,Jaeger-Collector 負責處理四面八方推送來的跟蹤信息,然后存儲到后端系統庫中,例如:可以存儲到 ES、數據庫等。然后,用戶可以借助 Jaeger-UI 圖形界面觀測到這些被分析出來的跟蹤信息。
關于數據采樣率,在實際的業務場景中,鏈路追蹤系統本身也會造成一定的性能低損耗,如果完整記錄每次請求,對于生產環境可能會帶來極大的性能損耗,因此,我們需要依據當前現狀進行采樣策略配置。截止目前,當前可支持5種采樣率設置,具體如下: 1、固定采樣(sampler.type=const)sampler.param=1 全采樣, sampler.param=0 不采樣 2、按百分比采樣(sampler.type=probabilistic)sampler.param=0.1 則隨機采十分之一的樣本 3、采樣速度限制(sampler.type=ratelimiting)sampler.param=2.0 每秒采樣兩個traces
4、動態獲取采樣率 (sampler.type=remote) 此策略為默認配置,可以通過配置從 Agent 中獲取采樣率的動態設置 5、自適應采樣(Adaptive Sampling)開發計劃中
在實際的業務場景中,為了能夠追溯某一請求運行軌跡,通常,在理想情況下,我們需要對整個鏈路拓撲進行全方位追蹤,以便能夠在業務出現異常時能夠快速響應、快速處理。因此,無論是基于 VM 的 Spring Cloud 微服務還是基于 Container ,其鏈路追蹤體系基本的模型參考示意圖如下所示:

在本文中,我們以 “Demo” 的形式對基于 Jaeger 的分布式鏈路追蹤系統工程進行簡要描述。基于上述的模型參考示意圖,我們開始進行相關組件的部署。
為了能夠盡可能詳盡地解析 Jaeger 組件的基礎原理,我們將先以最簡單、明了的方式進行 Jaeger 組件部署。基于 Jaeger 的“all in one” 來進行鏡像的構建、啟動。具體如下所示:
[administrator@JavaLangOutOfMemory ~ ]% docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:latest
在本文中,有關端口釋義如下所示:
|
組件 |
端口 |
協議 |
描述 |
|---|---|---|---|
|
Agent |
6831 |
UDP |
應用程序向代理發送跟蹤的端口,接受 Jaeger.thrift而不是 Compact thrift協議 |
|
Agent |
6832 |
UDP |
通過二進制thrift協議接受 Jaeger.thrift ,需要某些不支持壓縮的客戶端庫 |
|
Agent |
5775 |
UDP |
接收兼容zipkin的協議數據 |
|
Agent |
5778 |
HTTP |
大數據流量下不建議使用 |
|
... |
... |
... |
... |
|
組件 |
端口 |
協議 |
描述 |
|---|---|---|---|
|
Collector |
14250 |
TCP |
Agent 發送 Proto 格式數據 |
|
Collector |
14267 |
TCP |
Agent 發送 Jaeger.thrift格式數據 |
|
Collector |
14268 |
HTTP |
從客戶端接受 Jaeger.thrift |
|
Collector |
14269 |
HTTP |
健康檢查 |
|
組件 |
端口 |
協議 |
描述 |
|---|---|---|---|
|
Query |
16686 |
HTTP |
HTTP 查詢服務于 Jaeger UI |
|
Query |
16687 |
HTTP |
健康檢查 |
|
... |
... |
... |
... |
自 1.17 版本(https://www.jaegertracing.io/docs/1.23/operator/ #當前版本),開始,我們還可以基于 Operator 的方式進行部署,并且其支持如下幾種業務方式:
1、All-In-One Strategy
2、Production Strategy
3、Streaming Strategy
Jaeger Operator:Jaeger Operator for Kubernetes 簡化了在 Kubernetes 上的部署和運行 Jaeger。Jaeger Operator 是 Kubernetes Qperator 的實現。從技術層面上講,Qperator 是打包,部署和管理 Kubernetes 應用程序的一種方法。Jaeger Operator 版本跟蹤 Jaeger 組件(查詢,收集器,代理)的一種版本。具體部署步驟如下所示:
[administrator@JavaLangOutOfMemory ~ ]% kubectl create namespace jaeger
[administrator@JavaLangOutOfMemory ~ ]% kubectl create -n jaeger -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml
[administrator@JavaLangOutOfMemory ~ ]% kubectl create -n jaeger -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
[administrator@JavaLangOutOfMemory ~ ]% kubectl create -n jaeger -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
[administrator@JavaLangOutOfMemory ~ ]% kubectl create -n jaeger -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml
[administrator@JavaLangOutOfMemory ~ ]% kubectl create -n jaeger -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
此時,查看其狀態,具體命令如下所示:
[administrator@JavaLangOutOfMemory ~ ]% kubectl get all -n jaeger
然后,進行 Jaeger 實例的創建,創建 jaeger.yaml 文件,配置 ES 集群及資源限制,具體如下所示:Deployment 以及所涉及 demo-prod-collector 容器的 CPU 和內存使用大小。如下示例文件,定義了最大數量可以起 10 個 Pod。
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: demo-prod
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://10.172.10.1:9200
index-prefix:
collector:
maxReplicas: 10
resources:
limits:
cpu: 500m
memory: 512Mi
[administrator@JavaLangOutOfMemory ~ ]% kubectl apply -f jaeger.yaml -n jaegerjaeger.jaegertracing.io/demo-prod created
若實際的業務場景中,如果流量過大,我們可以借助接入 Kafka 集群以減輕 ES 存儲庫壓力,故此,修改后的 jaeger.yaml 文件如下所示:
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: demo-streaming
spec:
strategy: streaming
collector:
options:
kafka:
producer:
topic: jaeger-spans
brokers: demo-cluster-kafka-brokers.kafka:9092 #修改為kafka地址
ingester:
options:
kafka:
consumer:
topic: jaeger-spans
brokers: demo-cluster-kafka-brokers.kafka:9092 #修改為kafka地址
ingester:
deadlockInterval: 5s
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch:9200 #修改為ES地址

針對 Agent 的部署,Agent 官方目前有兩種部署方案,一種是基于 DaemonSet 方式,一種是基于 Sidecar 方式。依據官方所述,Jaeger 中的 Agent 組件是作為 Tracer 和 Collector 之間的 buffer, 所以 Agent 應該離 Tracer 越近越好,通常應該是 Tracer 的 Localhost, 基于這樣的假定,Tracer 能夠直接通過UDP發送 span 到 Agent,達到最好的性能和可靠性之間的平衡。
DaemonSet 的 Pod 運行在節點(Node)級別,此形式的 Pod 如同每個節點上的守護進程,Kubernetes 確保每個節點有且只有一個 Agent Pod 運行, 如果以 DaemonSet 方式部署,則意味著這個 Agent 會接受節點上所有應用 Pods 發送的數據,對于 Agent 來說所有的 Pods 都是同等對待的。這樣確實能夠節省一些內存,但是一個 Agent 可能要服務同一個節點上的數百個 Pods。
Sidecar 是在應用 Pod 中增加其他服務,在 Kubernetes 中服務是以 Pod 為基本單位的,但是一個 Pod 中可以包含多個容器, 這通??梢杂脕韺崿F嵌入一些基礎設施服務, 在 Sidecar 方式部署下,對于 Jaeger Agent 會作為 Pod 中的一個容器和 Tarcer 并存,由于運行在應用級別,不需要額外的權限,每一個應用都可以將數據發送到不同的 Collector 后端,這樣能保證更好的服務擴展性。
綜合對比分析,若我們基于私有云環境且信任 Kubernetes 集群上運行的應用,通常建議可以采用 DaemonSet 進行部署,畢竟,此種方式盡可能占用較少的內存資源。反之,若為公有云環境,或者希望獲得多租戶能力,Sidecar 可能更好一些,由于 Agent 服務當前沒有任何安全認證手段,這種方式不需要在 Pod 外暴露 Agent 服務,相比之下更加安全一些,盡管內存占用會稍多一些(每個 Agent 內存占用在20M以內)。
1、基于 DaemonSet 模式部署
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: jaeger-agent
labels:
app: jaeger-agent
spec:
selector:
matchLabels:
app: jaeger-agent
template:
metadata:
labels:
app: jaeger-agent
spec:
containers:
- name: jaeger-agent
image: jaegertracing/jaeger-agent:1.12.0
env:
- name: REPORTER_GRPC_HOST_PORT
value: "jaeger-collector:14250"
resources: {}
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
通過 Kubernetes Downward API 將節點的 IP 信息(status.hostIP) 以環境變量的形式注入到應用容器中。
2、基于 Sidecar 模式部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: example/myapp:version
- name: jaeger-agent
image: jaegertracing/jaeger-agent:1.12.0
env:
- name: REPORTER_GRPC_HOST_PORT
value: "jaeger-collector:14250"
至此,Jaeger 服務部署 OK。剩余組件的部署可參考官網。接下來,我們來看一下 Jaeger 接入 Traefik 組件的相關配置。默認情況下,Traefik 使用 Jaeger 來最為追蹤系統的后端實現.,具體配置如下所示:
[administrator@JavaLangOutOfMemory ~ ]% cat traefik.toml
[tracing]
[tracing.jaeger] # 開啟jaeger的追蹤支持
samplingServerURL = "http://localhost:5778/demo" # 指定jaeger-agent的http采樣地址
samplingType = "const" # 指定采樣類型[const(const|probabilistic|rateLimiting)]
samplingParam = 1.0 # 采樣參數的值[1.0(const:0|1,probabilistic:0-1,rateLimiting:每秒的span數)]
localAgentHostPort = "127.0.0.1:6831" # 本地agent主機和端口(會發送到jaeger-agent)
gen128Bit = true # 生成128位的traceId,兼容OpenCensus
propagation = "jaeger" # 設置數據傳輸的header類型[jaeger(jaeger|b3兼容OpenZipkin)]
traceContextHeaderName = "demo-trace-id" # 跟蹤上下文的header,用于傳輸跟蹤上下文的http頭名
[tracing.jaeger.collector] # 指定jaeger的collector服務
endpoint = "http://127.0.0.1:14268/api/traces?format=jaeger.thrift"
user = "demo-user" # 向collector提交時的http認證用戶[""]
password = "demo-password" # 向collector提交時的http認證密碼[""]
# cli 配置
--tracing.jaeger=true
--tracing.jaeger.samplingServerURL=http://localhost:5778/demo
--tracing.jaeger.samplingType=const
--tracing.jaeger.samplingParam=1.0
--tracing.jaeger.localAgentHostPort=127.0.0.1:6831
--tracing.jaeger.gen128Bit
--tracing.jaeger.propagation=jaeger
--tracing.jaeger.traceContextHeaderName=uber-trace-id
--tracing.jaeger.collector.endpoint=http://127.0.0.1:14268/api/traces?format=jaeger.thrift
--tracing.jaeger.collector.user=demo-user
--tracing.jaeger.collector.password=demo-password
針對 Spring Boot 微服務,主要涉及以下步驟,具體如下所示:
1、整合 Jaeger ,引入 Maven 依賴
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-web-starter</artifactId>
<version>3.3.1</version>
</dependency>
2、連接屬性配置
spring.application.name=demo
opentracing.jaeger.enabled=true
opentracing.jaeger.log-spans=false
# opentracing.jaeger.enable128-bit-traces=true
3、其他參數配置,諸如,使用 JaegerAutoConfiguration 進行自動配置,以及 Log back 日志配置文件等,以便能夠有效對服務請求鏈路進行全方位追蹤?;谌罩九渲梦募?,以及結合官網給出的參考,主要針對自定義參數 traceId、spanId、sampled,當然這些參數也可以在 new MDCScopeManager.Builder() 時指定。具體可參考如下配置所示:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout
pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg% traceId=%X{traceId} spanId=%X{spanId} sampled=%X{sampled}%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug" additivity="false">
<AppenderRef ref="console" />
</Root>
</Loggers>
</Configuration>
基于上述的配置,即可完成 Spring Boot 微服務的部署(Order Service、Payment Service、Account Service、Customer Service以及其他服務),相關服務的部署及源碼暫不在本文中描述,后續將呈現。至此,在整個網絡架構拓撲中,接入層 Traefik 和 服務層 Spring Boot 已完成 Jaeger 分布式鏈路追蹤系統的接入,具體生成的相關依賴圖如下所示:

此時,我們也可以看到各個服務之間的調用依賴以及接口請求的日志情況,具體如下所示:

針對服務層下游的組件(緩存層、基礎中間件層、數據存儲層等等)接入,將在后續的文章中進行各自單獨解析。
綜上所述,基于云原生生態領域的鏈路追蹤系統 Jaeger ,在實際的業務場景中對于識別、定位及分析我們應用網絡拓撲結構中服務間的鏈路調用的瓶頸其作用是不言而喻的,具有十分重要的參考意義。基于其所具備的“問題識別”、“信息追溯”等特征,使得我們在梳理服務之間的復雜依賴以及疑難問題分析面前能夠迎刃而解。

浙公網安備 33010602011771號