利用Jaeger打造云原生架构下分布式追踪系统

标签: | 发表时间:2020-03-13 19:25 | 作者:
出处:https://mp.weixin.qq.com

01

为什么选择Jaeger

优点

  • Jaeger由Uber开源并被云原生基金会(CNCF)纳入孵化项目,背后有大厂和强大的组织支持,项目目前开发活跃;

  • 原生支持 OpenTracing 标准(可以认为是OpenTracing协议的参考实现),支持多种主流语言,可以复用大量的 OpenTracing 组件;

  • 丰富的采样率设置支持;

  • 高扩展,易伸缩,没有单点故障,可以随着业务方便扩容;

  • 多种存储后端支持;

  • 提供现代的 Web UI,可以支持大量数据的呈现;

  • 支持云原生的部署方式,非常容易部署在 Kubernetes 集群中;

  • 可观察性,所有组件均默认可暴露 Prometheus metrics,日志默认使用结构化的日志到标准输出。


缺点

  • 接入过程有一定的侵入性;

  • 相比与上篇介绍的 Apache SkyWalking 、CAT,Jaeger更专注于链路追踪(Tracing),日志和指标功能支持比较有限;

  • 本身缺少监控和报警机制,需要结合第三方工具来实现,比如配合Grafana 和 Prometheus实现。文章后面会给出简单的示例。


02

实现原理

1.Jaeger架构图解

图片来源: Jaeger Architecture


2.Jaeger组件

  • 客户端库实现了OpenTarcing API。可以手动也可以通过已经集成OpenTracing 的框架工具实现应用的分布式追踪,像Flask、Dropwizard、gRPC等都已经有现成的集成工具库;

  • 每当接受到新的请求,就会创建 span 并关联上下文信息(trace id、span id 和 baggage)。只有 id 和 baggage 会随请求向下传递,而所有组成 span 的其他信息,如操作名称、日志等,不会在同一个trace 的span间传递。通过采样得到的 span 会在后台异步发送到 Jaeger Agents 上;

  • 需要注意的是虽然所有的traces都会创建,但是只有少部分会被采样,采样到的trace会被标记并用于后续的处理和存储。默认情况下,Jaeger client 的采样率是 0.1%,也就是千分之一,并且可以从 Agent上取回采样率设置;

  • Agent 是一个网络守护进程,监听通过 UDP 发送过来的 spans,并将其批量发送给 Collector。按设计 Agent 要作为基础设施被部署到所有主机节点。Agent 将 Collector 和客户端之间的路由与发现机制抽象了出来。后面会详细介绍Agent的部署模式;

  • Collector 从 Agents 接收 traces,并通过一个pipeline对其进行处理。目前的pipeline会检验traces、建立索引、执行转换并最终进行存储。Jaeger的存储系统是一个可插入的组件,当前支持 Cassandra、Elasticsearch 和 Kafka(测试环境可以支持纯内存存储);

  • Query 从存储中检索 traces 并通过 一个漂亮的 UI 界面进行展现,目前支持搜索、过滤、traces 对比、查看依赖调用关系图等功能。


3.关于采样率

分布式追踪系统本身也会造成一定的性能低损耗,如果完整记录每次请求,对于生产环境可能会有极大的性能损耗,一般需要进行采样设置。

当前支持四种采样率设置:

  • 固定采样(sampler.type=const)sampler.param=1 全采样, sampler.param=0 不采样;

  • 按百分比采样(sampler.type=probabilistic)sampler.param=0.1 则随机采十分之一的样本;

  • 采样速度限制(sampler.type=ratelimiting)sampler.param=2.0 每秒采样两个traces;

  • 动态获取采样率 (sampler.type=remote) 这个是默认配置,可以通过配置从 Agent 中获取采样率的动态设置。

自适应采样(Adaptive Sampling)也已经在开发计划中。


03

部署实践

1.在Kubernetes集群上部署Jaeger

Jaeger是为云原生环境下的分布式追踪而打造,Kubernetes 又是当前云原生服务编排事实上的标准,下面以示例的方式介绍如何在 Kubernetes集群上部署 Jaeger:

   1# 克隆示例到本地   
2git clone https://github.com/maguowei/distributed-tracing-system.git
3cd distributed-tracing-system
4
5# 这里我们选择Elasticsearch作为存储, 简单创建测试用的 Elasticsearch 服务
6kubectl create -f deployment/kubernetes/elasticsearch
7
8# 部署Jaeger全家桶(Agent, Collector, Query)
9kubectl create -f deployment/kubernetes/jaeger
10
11# 以NodePort 方式暴露 Query UI
12kubectl expose service jaeger-query --port 16686 --type NodePort --name jaeger-query-node-port
13
14# 找到暴露的端口号
15kubectl get service jaeger-query-node-port
16
17# 访问 http://127.0.0.1:${port}



当前Query 中可以看到是空的,我们运行 官方的 HotROD 微服务示例,生成一些数据:

  1kubectl create -f deployment/kubernetes/example   
2kubectl expose service jaeger-example-hotrod --port 8080 --type NodePort --name jaeger-example-hotrod-node-port


打开HotROD页面, 任意点击页面上的按钮,生成一些调用数据:

刷新Jaeger Query UI 页面,然后我们就可以看到生成的调用信息:

点开具体的一条Trace 可以看到详细的调用过程:

还可以看到图形化的调用关系链:


2.选择 DaemonSet 还是 Sidecar

Agent 官方目前有两种部署方案,一种是 DaemonSet 方式,一种是 Sidecar 方式。

按照官方的说法,Jaeger 中的 Agent 组件是作为 tracer 和 Collector 之间的 buffer, 所以 Agent 应该离 tracer 越近越好,通常应该是 tracer 的 localhost, 基于这样的假定,tracer 能够直接通过UDP发送span 到 Agent,达到最好的性能和可靠性之间的平衡。

这样的假定在裸机服务器上部署非常棒,但在当前流行的云环境和容器中,对于 Kubernetes 来说究竟什么是本地(localhost)呢?是服务运行所在的节点(node)还是 pod 本身呢?

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)Agent 以 DaemonSet 模式部署

DaemonSet 方式部署会有一个问题,如何保证应用能够和自己所在节点的Agent通讯?

为解决通讯问题,Agent需要使用主机网络(hostNetwork), 应用中需要借用 Kubernetes Downward API 获取节点IP信息。

DaemonSet 模式部署 Agent:

   1apiVersion: apps/v1   
2kind: DaemonSet
3metadata:
4  name: jaeger-agent
5  labels:
6    app: jaeger-agent
7spec:
8  selector:
9    matchLabels:
10      app: jaeger-agent
11  template:
12    metadata:
13      labels:
14        app: jaeger-agent
15    spec:
16      containers:
17        - name: jaeger-agent
18          image: jaegertracing/jaeger-agent:1.12.0
19          env:
20            - name: REPORTER_GRPC_HOST_PORT
21              value: "jaeger-collector:14250"
22          resources: {}
23      hostNetwork: true
24      dnsPolicy: ClusterFirstWithHostNet
25      restartPolicy: Always


通过 Kubernetes Downward API 将节点的IP信息(status.hostIP) 以环境变量的形式注入到应用容器中:

   1apiVersion: apps/v1   
2kind: Deployment
3metadata:
4  name: myapp
5spec:
6  selector:
7    matchLabels:
8      app: myapp
9  template:
10    metadata:
11      labels:
12        app: myapp
13    spec:
14      containers:
15      - name: myapp
16        image: example/myapp:version
17        env:
18        - name: JAEGER_AGENT_HOST
19          valueFrom:
20            fieldRef:
21              fieldPath: status.hostIP


2)Agent以Sidecar模式部署

下面是以Sidecar模式运行的应用示例,官方也提供了自动注入Sidecar的机制,详细使用可以参考[12]:

   1apiVersion: apps/v1   
2kind: Deployment
3metadata:
4  name: myapp
5  labels:
6    app: myapp
7spec:
8  replicas: 1
9  selector:
10    matchLabels:
11      app: myapp
12  template:
13    metadata:
14      labels:
15        app: myapp
16    spec:
17      containers:
18        - name: myapp
19          image: example/myapp:version
20        - name: jaeger-agent
21          image: jaegertracing/jaeger-agent:1.12.0
22          env:
23            - name: REPORTER_GRPC_HOST_PORT
24              value: "jaeger-collector:14250"


这样Jaeger Agent将会监听 localhost:5775/localhost:6831/localhost:6832/localhost:5778 这些本地端口,通常你不需要再在client配置中指定连接的主机名或者端口信息,应为这都是默认值。


3.生成依赖调用关系图

Jaeger Query UI服务中的 dependencies 选项默认点开为空,需要运行 spark-dependencies 来生成依赖关系图。

spark-dependencies 是一个Spark job 可以通过聚合和分析存储中的 trace 数据,生成服务间的依赖关系图,并将依赖链接信息持久化到存储中。之后 Jaeger Query Dependencies 页面就可以显示服务之间的依赖关系。

  1# 可以手动只执行一次   
2kubectl run -it --rm jaeger-spark-dependencies --env=STORAGE=elasticsearch --env ES_NODES=http://jaeger-elasticsearch:9200 --env ES_NODES_WAN_ONLY=true --restart=Never --image=jaegertracing/spark-dependencies
3
4# 也可以创建 CronJob, 每天定点生成新的依赖图
5kubectl create -f deployment/kubernetes/spark-dependencies/jaeger-spark-dependencies-cronjob.yaml


04

应用示例

下面以Python Django项目为例在服务中集成 Jaeger。

安装必要的依赖:

  1pip install jaeger-client   
2pip install django_opentracing


Jaeger tracer 配置和初始化:

   1from jaeger_client import Config   
2from django.conf import settings
3
4
5def init_jaeger_tracer(service_name='your-app-name'):
6    config = Config(
7        config={
8            'sampler': {
9                'type': 'const',
10                'param': 1,
11            },
12            'local_agent': {
13                'reporting_host': settings.JAEGER_REPORTING_HOST,
14                'reporting_port': settings.JAEGER_REPORTING_PORT,
15            },
16            'logging': True,
17        },
18        service_name='django-example',
19        validate=True,
20    )
21    return config.initialize_tracer()
22
23
24# this call also sets opentracing.tracer
25jaeger_tracer = init_jaeger_tracer(service_name='example')


Django_opentracing配置, 在Django settings文件中增加以下配置:

   1import django_opentracing   
2
3...
4
5# 添加 django_opentracing.OpenTracingMiddleware
6MIDDLEWARE = [
7    'django_opentracing.OpenTracingMiddleware',
8    ... # other middleware classes
9]
10
11# OpenTracing settings
12
13OPENTRACING_SET_GLOBAL_TRACER = True
14
15# if not included, defaults to True.
16# has to come before OPENTRACING_TRACING setting because python...
17OPENTRACING_TRACE_ALL = True
18
19# defaults to []
20# only valid if OPENTRACING_TRACE_ALL == True
21OPENTRACING_TRACED_ATTRIBUTES = ['path', 'method']
22
23from example.service.jaeger import jaeger_tracer
24
25OPENTRACING_TRACER = django_opentracing.DjangoTracing(jaeger_tracer)


这样Django接收的每个请求都会生成一条单独的Trace,当前请求的path和method会以Span Tag的形式记录下来。

手动创建Span和记录调用信息等更详尽的使用方法,请参考官方使用文档。


05

监控和报警

当前Jaeger缺少自带的报警机制,但是由于存储可以使用Elasticsearch,配合Grafana就可以实现简单的报警监控。

Jaeger本身暴露了Prometheus 格式的metrics 信息, 配合 Grafana可以方便的监控 Jaeger本身的运行状态。


06

资源清理

演示完毕,最后做一下资源的清理和释放:

  1kubectl delete -f deployment/kubernetes/spark-dependencies   
2kubectl delete -f deployment/kubernetes/example
3kubectl delete -f deployment/kubernetes/jaeger
4kubectl delete -f deployment/kubernetes/elasticsearch
5kubectl delete service jaeger-example-hotrod-node-port
6kubectl delete service jaeger-query-node-port

相关 [利用 jaeger 架构] 推荐:

利用Jaeger打造云原生架构下分布式追踪系统

- -
Jaeger由Uber开源并被云原生基金会(CNCF)纳入孵化项目,背后有大厂和强大的组织支持,项目目前开发活跃;. 原生支持 OpenTracing 标准(可以认为是OpenTracing协议的参考实现),支持多种主流语言,可以复用大量的 OpenTracing 组件;. 高扩展,易伸缩,没有单点故障,可以随着业务方便扩容;.

SpringCloud项目接入Jaeger(下) - 掘金

- -
spring-cloud-sleuth这个组件时,会面临两个问题. 首先是日志中无法显示traceId和spanId这些链路信息,其次是不能在用. spring-cloud-sleuth所提供的方式进行链路传值. spring-cloud-sleuth是将traceId等链路信息保存在. slf4j的MDC(Mapped Diagnostic Contexts)中,然后通过%X{traceId}这种方式将traceId提取出来,比如打印到控制台的默认格式是:.

为Jaeger安装环境搭建监控基础设施

- - InfoQ - 促进软件开发领域知识与创新的传播
本文最初发表于 RedHat开发者博客,经原作者Juraci Paixão Kröhling和RedHat授权由InfoQ中文站翻译分享. 在生产环境中部署 Jaeger时,持续观察Jaeger实例,保证它的行为符合预期是非常重要的. 毕竟,Jaeger停机将会意味着跟踪数据的丢失,这样的话,我们很难理解生产环境的应用中究竟出现了什么问题.

架构

- - IT瘾-dev
网关:Nginx、Kong、Zuul. 缓存:Redis、MemCached、OsCache、EhCache. 搜索:ElasticSearch、Solr. 熔断:Hystrix、resilience4j. 负载均衡:DNS、F5、LVS、Nginx、OpenResty、HAproxy. 注册中心:Eureka、Zookeeper、Redis、Etcd、Consul.

信息架构

- Michael - Tony-懒得设计
写几篇关于信息架构的文章,系统地输出我理解的信息架构. 发了一篇关于招信息架构实习生的博客,收到不少简历. 但谈起信息架构,多数不了解,稍微了解的扯了很多很偏的东西. 随手搜索了一下,我发现了原因:. 1 《web信息架构》这本书太概念,太学术. 2 有人绑架了“信息架构”这个词,拿出去唬人,内容都是皮毛或者是根本和信息架构不沾边的东西.

CSS架构

- - 博客 - 伯乐在线
英文原文: CSS Architecture,编译: CSDN-张红月. Philip Walton 在AppFolio担任前端工程师,他在Santa Barbara on Rails的聚会上提出了CSS架构和一些最佳实践,并且在工作中一直沿用. 擅长CSS的Web开发人员不仅可以从视觉上复制实物原型,还可以用代码进行完美的呈现.

Linux的架构

- - 博客园_首页
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明. 我们以下图为基础,说明Linux的架构(architecture). (该图参考《 Advanced Programming in Unix Environment》). 最内层是我们的硬件,最外层是我们常用的各种应用,比如说使用firefox浏览器,打开evolution查看邮件,运行一个计算流体模型等等.

LMAX架构(转)

- - 企业架构 - ITeye博客
LMAX是一种新型零售金融交易平台,它能够以很低的延迟(latency)产生大量交易(吞吐量). 这个系统是建立在JVM平台上,核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单. 业务逻辑处理器完全是运行在内存中(in-memory),使用事件源驱动方式(event sourcing).

软件架构

- - 研发管理 - ITeye博客
    对于外包业务类型的项目,软件架构设计的目的与产品类型的项目有所不同,在这里主要讨论外包类型项目的软件架构设计目的.     1、为大规模开发提供基础和规范,并提供可重用的资产,软件系统的大规模开发,必须要有一定的基础和遵循一定的规范,这既是软件工程本身的要求,也是客户的要求. 架构设计的过程中可以将一些公共部分抽象提取出来,形成公共类和工具类,以达到重用的目的.