Luga Lee
作者Luga Lee2023-04-13 09:52
系统架构师, None

基于 Jaeger 进行微服务链路追踪

字数 11077阅读 876评论 0赞 3

基于解决不同行业、业务应用的可扩展性、可用性等一系列问题,由此而生的微服务架构得到了各大厂商的、组织以及个人的青睐,随之而来便广泛应用于各种行业场景应用中。然而,随着时间的推移,越来越多的问题慢慢地呈现在大众的视野中。

其中,最为核心的问题莫过于微服务分布式性质导致的运行问题,以及带来的 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

在 本文中,有关端口释义如下所示:

组件端口协议描述
Agent6831UDP应用程序向代理发送跟踪的端口,接受 Jaeger.thrift而不是 Compact thrift协议
Agent6832UDP通过二进制thrift协议接受 Jaeger.thrift ,需要某些不支持压缩的客户端库
Agent5775UDP接收兼容zipkin的协议数据
Agent5778HTTP大数据流量下不建议使用
............

组件端口协议描述
Collector14250TCPAgent 发送 Proto 格式数据
Collector14267TCPAgent 发送 Jaeger.thrift格式数据
Collector14268HTTP从客户端接受 Jaeger.thrift
Collector14269HTTP健康检查

组件端口协议描述
Query16686HTTPHTTP 查询服务于 Jaeger UI
Query16687HTTP健康检查
............

自 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 jaeger
jaeger.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 中可以包含多个容器, 这通常可以用来实现嵌入一些基础设施服务, 在 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   
           <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 ,在实际的业务场景中对于 识别、定位 及分析 我们应用网络拓扑结构中服务间的链路调用的瓶颈 其作用是不言而喻的,具有十分重要的参考意义。基于其所具备的“问题识别”、“信息追溯”等特征,使得我们在梳理服务之间的复杂依赖以及疑难问题分析面前能够迎刃而解。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

3

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广