☝点击上方蓝字,关注我们!
本文字数:3001 字
预计阅读时间:15分钟
导 读
分布式追踪系列文章下篇来啦!!
上周我们推送了分布式追踪系列文章的上篇——《分布式追踪系统概述及主流开源系统对比》,该篇文章介绍了分布式追踪系统的原理、“可观察性” 的三大支柱、OpenTracing标准,同时对当前主流的开源分布式追踪系统进行简单对比;
本周文章《利用Jaeger打造云原生架构下分布式追踪系统》是分布式追踪系列文章的实践部分,主要以Jaeger为例,介绍如何使用Jaeger 搭建云原生架构下的分布式追踪系统。
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/jaeger1011# 以NodePort 方式暴露 Query UI12kubectl expose service jaeger-query --port 16686 --type NodePort --name jaeger-query-node-port1314# 找到暴露的端口号15kubectl get service jaeger-query-node-port1617# 访问 http://127.0.0.1:${port}
当前Query 中可以看到是空的,我们运行 官方的 HotROD 微服务示例,生成一些数据:
1kubectl create -f deployment/kubernetes/example2kubectl 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-agent11 template:12 metadata:13 labels:14 app: jaeger-agent15 spec:16 containers:17 - name: jaeger-agent18 image: jaegertracing/jaeger-agent:1.12.019 env:20 - name: REPORTER_GRPC_HOST_PORT21 value: "jaeger-collector:14250"22 resources: {}23 hostNetwork: true24 dnsPolicy: ClusterFirstWithHostNet25 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: myapp13 spec:14 containers:15 - name: myapp16 image: example/myapp:version17 env:18 - name: JAEGER_AGENT_HOST19 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: myapp12 template:13 metadata:14 labels:15 app: myapp16 spec:17 containers:18 - name: myapp19 image: example/myapp:version20 - name: jaeger-agent21 image: jaegertracing/jaeger-agent:1.12.022 env:23 - name: REPORTER_GRPC_HOST_PORT24 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-dependencies34# 也可以创建 CronJob, 每天定点生成新的依赖图5kubectl create -f deployment/kubernetes/spark-dependencies/jaeger-spark-dependencies-cronjob.yaml
04
应用示例
下面以Python Django项目为例在服务中集成 Jaeger。
安装必要的依赖:
1pip install jaeger-client2pip 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()222324# this call also sets opentracing.tracer25jaeger_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]1011# OpenTracing settings1213OPENTRACING_SET_GLOBAL_TRACER = True1415# if not included, defaults to True.16# has to come before OPENTRACING_TRACING setting because python...17OPENTRACING_TRACE_ALL = True1819# defaults to []20# only valid if OPENTRACING_TRACE_ALL == True21OPENTRACING_TRACED_ATTRIBUTES = ['path', 'method']2223from example.service.jaeger import jaeger_tracer2425OPENTRACING_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-dependencies2kubectl delete -f deployment/kubernetes/example3kubectl delete -f deployment/kubernetes/jaeger4kubectl delete -f deployment/kubernetes/elasticsearch5kubectl delete service jaeger-example-hotrod-node-port6kubectl delete service jaeger-query-node-port
本文引用与参考如下:
[1].Jaeger Supported libraries
[2].OpenTracing API Contributions
[3].Jaeger Features
[4].Jaeger Roadmap
[5].Jaeger Sampling
[6].Jaeger Architecture
[7].Deployment strategies for the Jaeger Agent
[8].Kubernetes DNS 高阶指南
[9].Kubernetes Communicating with Daemon Pods
[10].Kubernetes Docs - Expose Pod Information to Containers Through Files
[11].Jaeger Kubernetes - Deploying the agent as sidecar
[12].Jaeger Operator for Kubernetes - Auto injection of Jaeger Agent sidecars
[13].Take OpenTracing for a HotROD ride
[14].opentracing-contrib/python-django
[15].Monitoring Jaeger
狐友技术团队其他精彩文章
分布式追踪系统概述及主流开源系统对比
安卓系统权限,你真的了解吗?
Swift之Codable实战技巧
AspectJ在Android中的应用
加入搜狐技术作者天团
千元稿费等你来!
戳这里!☛