Kubernetes / Monitoring / 探知未来 / 运维笔记

Jaeger OpenTracing 分布式链路追踪

Einic Yeo · 10月16日 · 2021年 · · · ·

微服务架构 作为云原生核心技术之一,提倡将单一应用程序划分成一组小的服务(微服务),服务之间互相协调、互相配合,为用户提供最终价值。

但数量庞大的微服务实例治理起来给我们带来了很多问题,通常的做法都是引入相应组件完成,如 API 网关 ( apisix, kong, traefik ) 负责认证鉴权、负载均衡、限流和静态响应处理;服务注册与发现中心 ( Consul, Etcd, ZooKeeper ) 负责管理维护微服务实例,记录服务实例元数据;可观察性方面包括 M版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!etrics 监控 ( Prometheus ) 负责性能指标统计告警,Logging 日志 ( Loki, ELK ) 负责日志的收集查看,Tracing 链路追踪 ( OpenTracing, Jaeger ) 负责追踪具体的请求和绘制调用的拓扑关系。对于这种需要自行引入各种组件完成微服务治理的称为 侵入式架构 ,与之相对应的另外一种做法就是未来微服务架构 —— 服务网格 ( Service Mesh ) 。

正文

本文主要介绍可观察性的链路追踪模块,我将按以下几个大纲逐步演进:

  • OpenTracing 介绍
  • Jaeger 介绍
  • Jaeger 部署
  • Jaeger 使用

OpenTracing 介绍

起源

实现分布式追踪的方式一般是在程序代码中进行埋点,采集调用的相关信息后发送到后端的一个追踪服务器进行分析处理。在这种实现方式中,应用代码需要依赖于追踪服务器的 API,导致业务逻辑和追踪的逻辑耦合。为了解决该问题,CNCF (云原生计算基金会)下的 OpenTracing 项目定义了一套分布式追踪的标准,以统一各种分布式追踪系统的实现。OpenTracing 中包含了一套分布式追踪的标准规范,各种语言的 API,以及实现了该标准的编程框架和函数库。参考[1]

OpenTracing 提供了平台无关、厂商无关的 API,因此开发者只需要对接 OpenTracing API,无需关心后端采用的到底是什么分布式追踪系统,Jager、Skywalking、LightStep 等都可以无缝切换。

数据模型

OpenTracing 定义了以下数据模型:

  • Trace (调用链):一个 Trace 代表一个事务或者流程在(分布式)系统中的执行过程。例如来自客户端的一个请求从接收到处理完成的过程就是一个 Trace。
  • Span(跨度):Span 是分布式追踪的最小跟踪单位,一个 Trace 由多段 Span 组版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!成。可以被理解为一次方法调用, 一个程序块的调用, 或者一次 RPC/数据库访问。只要是一个具有完整时间周期的程序访问,都可以被认为是一个 Span。
  • SpanContext(跨度上下文):分布式追踪的上下文信息,包括 Trace id,Span id 以及其它需要传递到下游服务的内容。一个 OpenTracing 的实现需要将 SpanContext 通过某种序列化协议 (Wire Protocol) 在进程边界上进行传递,以将不同进程中的 Span 关联到同一个 Trace 上。对于 HTTP 请求来说,SpanContext 一般是采用 HTTP header 进行传递的。

总结:多个 Span 共同组成一个有向无环图(DAG)形成了 Trace ,SpanContext 则用于将一个 Span 的上下文传递到其下游的 Span 中,以将这些 Span 关联起来。

例如:下面的示例 Trace 就是由 8 个 Span 组成的:参考[2]

以树的结构展示 Trace 调用链:单个Trace中,span间的因果关系


[SpanA]←←←(therootspan)
|
+------+------+
||
[Span B][Span C]←←←(Span C 是 Span A 的孩子节点, ChildOf)
||
[SpanD]+---+-------+
||
[SpanE][SpanF]>>>[SpanG]>>>[SpanH]
↑
↑
↑
(SpanG在SpanF后被调用,FollowsFrom)


基于时间轴的时序图展示 Trace 调用链:

单个Trace中,span间的时间关系


––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–>time

[SpanA]
[SpanB]
[SpanD]
[SpanC]
[SpanE][SpanF][SpanG][SpanH]

OpenTracing API for Go

以官方博客例子为例[3]

安装

gogetgithub.com/opentracing/opentracing-go

创建main.go,实现一个 Web 服务,并在请求流程中使用 OpenTracing API 进行埋点处理。

packagemain

import(
"fmt"
"log"
"math/rand"
"net/http"
"time"

"github.com/opentracing/opentracing-go"
)

funcmain(){
port:=8080
addr:=fmt.Sprintf(":%d",port)
mux:=http.NewServeMux()
mux.HandleFunc("/",indexHandler)
mux.HandleFunc("/home",homeHandler)
mux.HandleFunc("/async",serviceHandler)
mux.HandleFunc("/service",serviceHandler)
mux.HandleFunc("/db",dbHandler)
fmt.Printf("http://localhost:%d\n",port)
log.Fatal(http.ListenAndServe(addr,mux))
}

//主页Html
funcindexHandler(whttp.ResponseWriter,r*http.Request){
w.Write([]byte(`<ahref="/home">点击开始发起请求</a>`))
}

funchomeHandler(whttp.ResponseWriter,r*http.Request){
w.Write([]byte("开始请求...\n"))

//在入口处设置一个根节点span
span :=opentracing.StartSpan("请求/home")
deferspan.Finish()

//发起异步请求
asyncReq,_:=http.NewRequest("GET","http://localhost:8080/async",nil)
//传递span的上下文信息
//将关于本地追踪调用的span context,设置到httpheader上,并传递出去
err:=span.Tracer().Inject(span.Context(),
opentracing.TextMap,
opentracing.HTTPHeadersCarrier(asyncReq.Header))
iferr!=nil{
log.Fatalf("[asyncReq]无法添加span context到httpheader:%v",err)
}
gofunc(){
if_,err:=http.DefaultClient.Do(asyncReq);err!=nil{
//请求失败,为span设置tags和logs
span.SetTag("error",true)
span.LogKV(fmt.Sprintf("请求/asyncerror:%v",err))
}
}()

time.Sleep(time.Duration(rand.Intn(200))*time.Millisecond)

//发起同步请求
syncReq,_:=http.NewRequest("GET","http://localhost:8080/service",nil)
err=span.Tracer().Inject(span.Context(),
opentracing.TextMap,
opentracing.HTTPHeadersCarrier(syncReq.Header))
iferr!=nil{
log.Fatalf("[syncReq]无法添加span context到httpheader:%v",err)
}
if_,err=http.DefaultClient.Do(syncReq);err!=nil{
span.SetTag("error",true)
span.LogKV(fmt.Sprintf("请求/serviceerror:%v",err))
}
w.Write([]byte("请求结束!"))
}

//模拟业务请求
funcserviceHandler(whttp.ResponseWriter,r*http.Request){
//通过httpheader,提取span元数据信息
varspopentracing.Span
opName:=r.URL.Path
wireContext,err:=opentracing.GlobalTracer().Extract(
opentracing.TextMap,
opentracing.HTTPHeadersCarrier(r.Header))
iferr!=nil{
//获取失败,则直接新建一个根节点span
sp=opentracing.StartSpan(opName)
}else{
sp=opentracing.StartSpan(opName,opentracing.ChildOf(wireContext))
}
defersp.Finish()

dbReq,_:=http.NewRequest("GET","http://localhost:8080/db",nil)
err=sp.Tracer().Inject(sp.Context(),
opentracing.TextMap,
opentracing.HTTPHeadersCarrier(dbReq.Header))
iferr!=nil{
log.Fatalf("[dbReq]无法添加span context到httpheader:%v",err)
}
if_,err=http.DefaultClient.Do(dbReq);err!=nil{
sp.SetTag("error",true)
sp.LogKV("请求/daerror",err)
}

time.Sleep(time.Duration(rand.Intn(200))*time.Millisecond)
}

//模拟DB调用
funcdbHandler(whttp.ResponseWriter,r*http.Request){
//通过httpheader,提取span元数据信息
varspopentracing.Span
opName:=r.URL.Path
wireContext,err:=opentracing.GlobalTracer().Extract(
opentracing.TextMap,
opentracing.HTTPHeadersCarrier(r.Header))
iferr!=nil{
//获取失败,则直接新建一个根节点span
sp=opentracing.StartSpan(opName)
}else{
sp=opentracing.StartSpan(opName,opentracing.ChildOf(wireContext))
}
defersp.Finish()

time.Sleep(time.Duration(rand.Intn(200))*time.Millisecond)
}

最后,只需要在应用程序启动时连接到任意实现了 OpenTracing 标准的链路追踪系统即可。详见下文的 Jaeger 使用。

Jaeger 介绍

Jaeger 受 Dapper 和 OpenZipkin 的启发,是 Uber Technologies 开源的分布式跟踪系统,遵循 OpenTracing 标准,功能包括:

  • 分布式上下文传播
  • 监控分布式事务
  • 执行根原因分析
  • 服务依赖分析
  • 优化性能和延迟时间

架构

Jaeger 既可以部署为一体式二进制文件 (ALL IN ONE),其中所有 Jaeger 后端组件都运行在单个进程中,也可以部署为可扩展的分布式系统 (高可用架构)

主要有以下几个组件:

  • Jaeger Client : OpenTracin版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!g API 的具体语言实现。它们版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!可以用来为各种现有开源框架提供分布式追踪工具。
  • Jaeger Agent : Jaeger 代理是一个网络守护进程,它会监听通过 UDP 发送的 span,并发送到收集程序。这个代理应被放置在要管理的应用程序的同一主机上。这通常是通过如 Kubernetes 等容器环境中的 sidecar 来实现的。
  • Jaeger Collector : 与代理类似,该收集器可以接收 span,并将其放入内部队列以便进行处理。这允许收集器立即返回到客户端/代理,而不需要等待 span 进入存储。
  • Storage : 收集器需要一个持久的存储后端。Jaeger 带有一个可插入的机制用于 span 存储。
  • Query : Query 是一个从存储中检索 trace 的服务。
  • Ingester : 可选组件。Jaeger 可以使用 Apache Kafka 作为收版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!集器和实际后备存储之间的缓冲。Ingester 是一个从 Kafka 读取数据并写入另一个存储后端的服务。
  • Jaeger Console : Jaeger 提供了一个用户界面,可让您可视觉地查看所分发的追踪数据。在搜索页面中,您可以查找 trace,并查看组成一个独立 trace 的 span 详情。

Jaeger 部署

Jaeger 部署方案主要围绕以下几个方面:

  • ALL IN ONE 还是分布式
  • 后端存储的选择(Elasticsearch、Cassandra 甚至 memory)
  • 是否引入 Kafka 作为中间缓冲器
  • Jaeger Agent 代理安装方式:sidecar 还是 DaemonSet
  • 安装工具的选择:Operator 还是 Helm chart

仁者见仁智者见智,结合自身业务场景选择适合自己的即可。

本文为了简化操作,就以 Operator + Jaeger Agent sidecar + memory + ALL IN ONE 为例。

  1. 在 Kubernetes 上安装 Jaeger Operator
#创建observability命名空间
kubectlcreatenamespaceobservability
#创建crd资源
kubectlcreate-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml
#声明用户权限
kubectlcreate-nobservability-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
kubectlcreate-nobservability-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
kubectlcreate-nobservability-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml
#部署JaegerOperator
kubectlcreate-nobservability-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
  1. 获得集群范围的权限,可选
kubectlcreate-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role.yaml
kubectlcreate-fhttps://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role_binding.yaml
  1. 查看 Jaeger Operator 是否部署成功
$kubectlgetdeploymentjaeger-operator-nobservability
NAMEREADYUP-TO-DATEAVAILABLEAGE
jaeger-operator1/11110s
  1. 使用 Jaeger Operator 部署 Jaeger ,创建 Jaeger 定制资源 参考[4]
apiVersion:jaegertracing.io/v1
kind:Jaeger
metadata:
name:my-jaeger
spec:
strategy:allInOne#部署策略
allInOne:
image:jaegertracing/all-in-one:latest
options:
log-level:debug#日志等级
storage:
type:memory#可选Cassandra、Elasticsearch
options:
memory:
max-traces:100000
ingress:
enabled:false
agent:
strategy:sidecar#代理部署策略可选DaemonSet
query:
serviceType:NodePort#用户界面使用NodePort

$kubectlapply-fmy-jaeger.yaml-nobservability
jaeger.jaegertracing.io/my-jaegercreated

$kubectlgetjaeger-nobservability
NAMESTATUSVERSIONSTRATEGYSTORAGEAGE
my-jaegerallinonememory10s

$kubectlgetsvc-nobservability
NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
jaeger-operator-metricsClusterIP10.103.46.73<none>8383/TCP,8686/TCP3m33s
my-jaeger-agentClusterIPNone<none>5775/UDP,5778/TCP,6831/UDP,6832/UDP15s
my-jaeger-collectorClusterIP10.111.136.244<none>9411/TCP,14250/TCP,14267/TCP,14268/TCP15s
my-jaeger-collector-headlessClusterIPNone<none>9411/TCP,14250/TCP,14267/TCP,14268/TCP15s
my-jaeger-queryNodePort10.105.255.201<none>16686:32710/TCP,16685:32493/TCP15s

访问 jaeger 用户界面 http://集群域名:32710

恭喜成功看到土拨鼠。

Jaeger 使用

继续回到上文的 OpenTracing API for Go 示例,现在就可以将我们的应用程序连接到 Jaeger 了。

安装 Jaeger Client Go

goget-ugithub.com/uber/jaeger-client-go

main.go 添加init 初始化函数

funcinit(){
cfg:=jaegercfg.Configuration{
Sampler:&jaegercfg.SamplerConfig{
Type:jaeger.SamplerTypeConst,
Param:1,
},
Reporter:&jaegercfg.ReporterConfig{
LogSpans:true,
},
}
_,err:=cfg.InitGlobalTracer(
"jaeger-example",//服务名
jaegercfg.Logger(jaegerlog.StdLogger),
jaegercfg.Metrics(metrics.NullFactory),
)
iferr!=nil{
panic(err)
}
}

将应用部署到 k8s 集群

$kubectlapply-fhttps://raw.githubusercontent.com/togettoyou/jaeger-example/master/jaeger-example.yaml-nobservability
deployment.apps/jaeger-examplecreated
service/jaeger-example-servicecreated

$kubectlgetsvcjaeger-example-service-nobservability
NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGE
jaeger-example-serviceNodePort10.106.2.139<none>8080:32668/TCP11s

提示:要使 jaeger 能够自动为我们的应用注入边车代理,只需要在部署的 Deployment 资源中添加"sidecar.jaegertracing.io/inject": "true" 的注释

访问 http://集群域名:32668

访问 jaeger 用户界面

查看刚才的调用链:

总结

本文主要介绍了 OpenTracing 以及 jaeger 之间的关系和使用方法,OpenTracing 是一个链路追踪的规范,我们可以使用 OpenTracing API 完成代码的监控埋点,最后可以自由选择连接遵循 OpenTracing 标准的链路追踪系统,比如 jaeger 。

本文所有代码均托管在 github.com/togettoyou/jaeger-example[5]

参考资料

[1] istio-handbook/practice/opentracing:https://www.servicemesher.com/istio-handbook/practice/opentracing.html

[2] opentracing-specification-zh:https://github.com/opentracing-contrib/opentracing-specification-zh/blob/master/specification.md

[3] opentracing-io/quick-start:https://wu-sheng.gitbooks.io/opentracing-io/content/pages/quick-start.html

[4] jaeger-operator:https://github.com/jaegertracing/jaeger-operator/tree/master/examples

[5] github.com/togettoyou/jaeger-example:https://github.com/togettoyou/jaeger-example

0 条回应