自从微服务大行其道,容器化和k8s编排一统天下之后,“可观测性” 便被提出来。这个概念是指,对于应用或者容器的运行状况的掌控程度,其中分为了三个模块:Metrics
、Tracing
、Logging
。Metrics 指应用采集的指标;Tracing 指应用的追踪;Logging 指应用的日志。
日志自不用多说,这是最原始的调试和数据采集能力。Metrics 比较火的方案就是 Prometheus + Grafana,思路就是通过应用内埋入SDK,选择 Pull 或者 Push 的方式将数据收集到 prometheus 中,然后通过 Grafana 实现可视化,当然这不是本文的重点就此略过。
Tracing 也并不是可观测性提出后才诞生的概念,在微服务化的进程中就已经有Google的Dapper落地实践,并慢慢形成 OpenTracing 规范,这一规范又被多家第三方框架所支持,如 Jaeger、Zipkin 等。OpenTelemetry 就是结合了 OpenTracing + OpenCensus 规范,约定并提供完成的可观测性套件,只是目前(2021-12-15)稳定下来的只有 Tracing 这一部分而已。对 OpenTelemetry 发展历史感兴趣的可以自行了解。
效果预览 #
链路总览,包含了前端页面的生命周期 + 整个了链路采集到的Span聚合。
前端页面指标采集概览,包含了该页面生命周期内的动作和日志等。
服务端链路细节,包含了服务端链路采集的标签和日志(事件)等信息。
propagation兼容jaeger效果,保证jaeger侧链路完整,使用一致的 traceId检索。因为服务侧 sentry 是渐进更新的,因此没有接入的应用并不会展示在sentry侧, 等到完全更新后就会完整。
背景 #
目前运行中的链路追踪组件是采用 opentracing + jaeger 实现,这套方案唯二的不足就是:
- opentracing 已经被 opentelemetry 兼容,且 opentelemetry 在可观测性上更全面,更灵活。
- 浏览器侧支持不完善(可以参见 https://github.com/jaegertracing/jaeger-client-javascript )【恰恰我司有这方面的需求 🤪】。
前端采用 sentry 来采集前端页面数据(APP + WEB 都支持很好),因此才有了这么一个 前后端链路打通的需求。
经过调研,我发现 sentry 也并不完美,详情参见 附录/sentry作为链路采集组件的优缺点
最开始的需求目标是后端相关 Tracing SDK 全部使用sentry替换,但是结合上述对于sentry的调研,发现直接接入sentry并不是一个好的选择:
- 相关的 Tracing 概念没有被我个人和社区接受,社区内主流的还是 OpenTracing 和 OpenTeletemetry 规范,而且这两个规范都是相互兼容的(个人认为:OpenTelemetry 大有一统可观测性的趋势,他们立项也是朝着这个方向前进)。
- 官方go-SDK活跃度低乎想象,参见 https://github.com/getsentry/sentry-go/issues/387 截止本文时(since 2021-10-22)仍然没有任何回复,进一步阻止我直接使用sentry。
- 调研 OpenTelemetry 在研究它的架构设计时,发现其设计中包含了一个 Collector(详细介绍参见 附录/OpenTelemetry中的概念) 。从示意图我们也可以看出,它的作用近似于 Jaeger Collector / Agent,但是相比于 Jaeger 它更开放,支持多种 Vendor (Jaeger / Zipkin 等等),更加灵活。
- 如果替换了 sentry 加入是不是以后还可能更换新的采集组件,服务这么多再来换一遍还是费时费力
方案描述 #
那么基于以上的考虑,设计了如下的改造方案(也可以理解为新的实施方案,毕竟出了兼容也不会考虑以前怎么用的🤪):
- 在应用侧 Tracing SDK 全部替换为 otel SDK(后面经过考虑,还是自己做了一个防腐的 Tracing API封装)。
- 为了保证渐进更新服务端链路采集的同时,在Jaeger Dashborad中的链路不断,在 propagator 实现 otel 到 jaeger 的转换。
- 自己实现一个 Sentry Trace Exporter, 将客户端上报的链路,再投递到 sentry。
- Collector 配置 exporter 时,同时上报到 sentry 和 jaeger
相应的实践,我已经放了一份在 github 上,请查看:https://github.com/yeqown/opentelemetry-quake
期间遇到的问题 #
-
怎么调研,产出方案?
刚开始接到需求,我第一时间在质疑需求的必要性,现在的链路采集好好的为啥非要换成另一种?也没有好的想法,毕竟对于 sentry 和 opentelemetry 认知都不够。通过查看官方文档,尤其是 opentelemetry 的设计,才找到了这么一个现阶段很满意的解决方案。
调研 = 多看文档和别人的最佳实践。只有你了解够多,你才能站得更高,更容易考虑问题。
方案 = 你需要的是什么,就用现成的工具来组合,如果实在没有那么是不是可以自己实现? -
怎么定制自己的 Trace Exporter?
主要还是参考 https://github.com/open-telemetry/opentelemetry-collector-contrib 仓库中的实现。Collector 设计时已经考虑了用户自定义,所以按照官方的约定开发即可,至于 sentryexporter 在官方仓库已经存在了,所以我只是定制化了一下。(为了加深理解,我其实又从头撸了一遍 🤓)
这里值得注意的是,如果你的collector需要使用特定的插件,那么需要使用官方的 github.com/open-telemetry/opentelemetry-collector/cmd/builder 来自己定制编译 collector, 这一点也可以在我提供的实践代码中找到。
-
Collector 和 Agent 的差异?
可以简单理解为:中心化和分布式部署的区别,在实现上并没有区别。至于你应该使用哪种部署方式,当然视你的集群规模而定,如果只有不到100个服务实例,个人觉得仅仅collector足以,直到不足以承载。反之,如果你集群中节点众多,服务实例也众多,那么最好一开始就上agent。
-
k8s 中如何以 agent 方式部署 collector?
官方也已经提供了样例代码,可以参考 github.com/open-telemetry/opentelemetry-collector/examples/k8s/otel-config.yaml。大致思路为:通过DaemonSet 在每个节点上运行一个 Collector 实例。这里需要注意的是,如果想要配置
NODE_IP:PORT
的方式让该节点上的服务直连 agent,那么需要在otel-config.yaml
中添加如下配置:apiVersion: apps/v1 # .... template: metadata: labels: app: opentelemetry component: otel-agent spec: hostNetwork: true # 增加这一行 dnsPolicy: ClusterFirstWithHostNet # 增加这一行 containers: # ...
当然不止这么一种方式,比如还可以使用
hostPort
, 视你的k8s集群配置而定。
附录 #
sentry作为链路采集组件的优缺点 #
仅代表个人看法,可能对于 sentry 理解不到位,请自行按照自己的理解来看。
缺点:
sentry 的主要场景并不是在于链路采集,而是在于前端页面采集(页面加载/路径/日志),包括页面异常数据;主要根据在于它独有(或许)的链路采集概念。其中常见的如下:
概念 | 定义 | OpenTracing中的映射 | 存在的问题 |
---|---|---|---|
Trace | 可以通过 TraceId 标识的一条链路 | Trace | 前端 Trace/Page 与 Trace/Req 出入 |
Span | 可以通过 SpanId 标识的一个单元 | Span | - |
Trasnaction | Trace 在进程内 Span 的集合 | - | 独有的概念 |
Scope | Trace 的上下文 (包含用户,Request等) | 只能用 SpanContext 【并不准确】 | 相比 SpanContext 包含的信息过于太多了 |
Hub | 应用侧 Tracer | Tracer | - |
个人觉得,正是 sentry 自己的定位并不着重于后端服务的链路采集,才会如此设计。
优点:
- 已经支持前后端链路打通,集成展示前端页面上发生的一切行为。
- 单独对异常进行采集和展示。
OpenTelemetry中的概念 #
链路采集基本的概念就不多介绍了,了解过 OpenTracing 之后,会发现大同小异
这里只是罗列了 Tracing 相关的概念:
概念 | 定义 | 功能 |
---|---|---|
TracerProvider | Tracer 注册中心 | 可以用于区分不同 Lib |
Propagator | 负责解析和注入 trace header | 跨进程传递 |
otel SDK | OpenTelemetry 客户端 SDK | 语言相关的API,负责链路的采集和上报 |
otel Collector | 链路采集后端组件 | 收集客户端上报的链路信息 |
otel Agent | 与Collector相同,只是以Agent模式部署 | k8s 中采用 DaemonSet 部署方式,增加部署容错能力,减少上报时延 |
Exporter (Span) | SDK 处理 span 的组件 | 用于客户端自定义span数据加工处理(如客户端直接上报链路到Vendor) |
Exporter (Trace) | Collector 中处理Trace的组件 | 与 Span Exporter 类似,但工作在 Collector 中,处理被 Collector 收集归纳的链路数据 |
Reciever | Collector 中处理Trace的组件 | 与 Span Exporter 类似,但工作在 Collector 中,处理被 Collector 收集归纳的链路数据 |
前文提到的 OpenTelemetry 的开放能力,从 Collector 和 Exporter 也能看得出来。
总结 #
对于 sentry 并没有过多的介绍,因为从后端的角度看过去,通过改造方案设计,sentry 就只是其中的一种 vendor 而已,并不会对我们的链路采集产生影响。就算以后前端想要试验另一种方案,那我们也只需要支持多一种 vendor 即可。强烈建议了解 OpenTelemetry 的相关概念,以后一定用得上。
水平有限,如有错误,欢迎勘误指正🙏。