分布式追踪系列文章下篇来啦!!
上周我们推送了分布式追踪系列文章的上篇——《分布式追踪系统概述及主流开源系统对比》,该篇文章介绍了分布式追踪系统的原理、“可观察性” 的三大支柱、OpenTracing标准,同时对当前主流的开源分布式追踪系统进行简单对比;
本周文章《利用Jaeger打造云原生架构下分布式追踪系统》是分布式追踪系列文章的实践部分,主要以Jaeger为例,介绍如何使用Jaeger 搭建云原生架构下的分布式追踪系统。
-
Jaeger由Uber开源并被云原生基金会(CNCF)纳入孵化项目,背后有大厂和强大的组织支持,项目目前开发活跃;
-
原生支持 OpenTracing 标准(可以认为是OpenTracing协议的参考实现),支持多种主流语言,可以复用大量的 OpenTracing 组件;
-
丰富的采样率设置支持;
-
高扩展,易伸缩,没有单点故障,可以随着业务方便扩容;
-
多种存储后端支持;
-
提供现代的 Web UI,可以支持大量数据的呈现;
-
支持云原生的部署方式,非常容易部署在 Kubernetes 集群中;
-
可观察性,所有组件均默认可暴露 Prometheus metrics,日志默认使用结构化的日志到标准输出。
图片来源: Jaeger Architecture
-
客户端库实现了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 对比、查看依赖调用关系图等功能。
分布式追踪系统本身也会造成一定的性能低损耗,如果完整记录每次请求,对于生产环境可能会有极大的性能损耗,一般需要进行采样设置。
当前支持四种采样率设置:
-
固定采样(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)也已经在开发计划中。
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以内)。
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
下面是以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配置中指定连接的主机名或者端口信息,应为这都是默认值。
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
下面以Python Django项目为例在服务中集成 Jaeger。
安装必要的依赖:
1pip install jaeger-client2pip install django_opentracing
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和记录调用信息等更详尽的使用方法,请参考官方使用文档。
当前Jaeger缺少自带的报警机制,但是由于存储可以使用Elasticsearch,配合Grafana就可以实现简单的报警监控。
Jaeger本身暴露了Prometheus 格式的metrics 信息, 配合 Grafana可以方便的监控 Jaeger本身的运行状态。
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
………………………………