作者 | 刘文平
策划 | 凌敏
本文整理自腾讯 IEG 高级研发工程师刘文平在《蓝鲸 x DeepFlow 可观测性 Meetup》中的分享实录,详细阐述了蓝鲸可观测性平台如何有效地 融合了 OpenTelemetry 的标准化数据接入能力及 DeepFlow 的无插桩、全面覆盖的数据收集能力, 进而解决游戏业务在观测数据采集、数据孤岛、以及云原生基础设施观测等领域所面临的难题。并展望了通过 DeepFlow,构建适合腾讯游戏的专属观测场景。
大家好,我是刘文平,很高兴能有机会参与本次有关 eBPF 的 meetup。我大概是在 2017 年加入腾讯,目前是专注于 APM、日志等相关领域,希望通过本次分享能够给大家带来在分布式链路追踪上不一样的观测方式。
本次分享主要会从两个重点部分来展开,首先,我会介绍一下当前 蓝鲸观测性平台上的一些实践,基于 OTel(OpenTelemetry)的一些方式。然后,我会给大家分享一个不一样的观测方式变化,也是我们基于最近一两年火起来的 eBPF 技术和 OTel 技术结合 后,所带来的改变。
蓝鲸观测平台整体架构
首先,我先介绍一下蓝鲸以及蓝鲸观测平台。上图是蓝鲸整体架构图,包括底层的基础设施以及上层 SaaS 应用。在中间,我们构建了很多平台应用,包含了 aPaaS(开发者中心、前后台开发框架等)和 iPaaS(持续集成、CMDB、作业平台、容器管理、计算平台、AI 等原子平台)等模块,可以去 帮助企业技术人员快速地构建他们自己的基础运营 Paas 平台。
蓝鲸观测平台是在最上层,也就是在 CO 这个持续运营场景下所孵化出来的一个 SaaS 服务。
上图是蓝鲸观测平台整体功能分层架构图。左边是监控平台部分,会专注于采集和上层应用场景。右边是计算平台和 AI 部分,专注于计算和数据分析。用户侧采集的数据只要是按监控平台协议要求接入的,可以直接使用监控平台所有的能力,但是对于数据有更复杂的统计需求或者是智能监控的场景,会按功能开关自动流入到计算平台和 AIOps 平台来实现,对于用户来说成本是相当低的。对于计算平台和 AIOps 平台来说,也是非常好的数据源,因为这些数据都是标准的有质量保障的数据。
我们花费三年多的时间搭建了上图的这个“房子”。我们以整个蓝鲸体系为基石,以五种数据类型为支柱,开箱即用的观测场景作为"屋顶"。
其中,Traces 数据也是拼图的重要一环,目前仍在不断打磨和探索中,往下,我会以 Traces 数据为重点,来展示我们的实践成果。
基于 OTel 的实践与挑战
OTel 可能已为熟悉可观测性平台的朋友所熟知。在 OTel 诞生之前,已有诸多分布式链路追踪相关的产品,如 Jaeger、Zipkin 等,蓝鲸观测平台也有自己的实现方案。然而,由于各种实现方案层出不穷,这对使用者来说意味着每换一个平台就需要重新检测代码、配置新的 SDK,甚至还需要关心新平台的数据协议,导致接入成本很高。
OTel 的出现便是为解决这个问题,它统一了 Trace、Metric、Log 这三种数据协议,用户只需要加载一套 SDK,更改上报地址即可体验不同平台的功能。同时,数据标准化后不仅降低了用户维护 SDK 的负担,也提高了数据的可移植性。
2021 年,蓝鲸观测平台也拥抱开源,我们将自己的实现方案统一转换成了 OTel 协议格式。下面,我将展示基于 OTel 的一些特性,帮助用户快速定位问题。
首先,当用户有了一个具体问题后,这个时候往往用户已经有了一个明确的 traceid,或者说他已经有了明确的一个时间段,需要去定位具体是哪个服务出错了,是哪一个接口出错了,具体的这个出错内容是什么?那么可以带着 traceid 去到我们平台的检索页面,把这个 traceid 带入进去,直接快速的找到本次 trace 请求所经过的所有链路服务。
其次,如果用户没有具体的问题,我们也会帮助他们挖掘更深层次的未知问题。
为此我们提供了业务的全景视角,用户通过这个拓扑图,然后往下钻,通过观察这个拓扑图上的具体一个服务,然后下钻到里面可以看到每一个服务它的一些黄金指标,像请求数、错误数或者说请求耗时以及底下的这些像这种 Top 视角,包括耗时 Top 或者错误 Top。
也可以切换接口的统计视角,这种视角去看到每一个接口的这种整体的一个情况。或者以错误统计的这种视角去查看错误次数最高是什么?然后我们也可以在右边的这个视图里面,直接展开看到它的这个错误的一些更详细的错误信息,堆栈信息。
此外,我们还提供了其他视角,如主机视角、日志视角和实例视角,用户可以在这些关联视角中观测到更多的对象,以帮助他们更快地发现架构中更深层次的问题。
在前两种情况中,用户会主动查看平台,通过平台发现问题。然而,即使用户没有查看平台,我们也会从 Trace 数据中提取出一些基础的黄金指标,并提供给用户配置告警,同时也提供了用户自定义的维度,以满足用户个性化的需求。将用户关心的关键指标通过告警的形式及时送达给用户。
上图就是数据的联动。这里介绍关键的三个数据 Metric、Log、Trace 之间的关联关系,从任意一个数据视角都可以关联跳转到其他两个数据的视角,帮忙用户快速的发现定位问题。
以上就是蓝鲸观测平台上基于 OTel 所进行的实践。
诚然我们做了这么多,但是在推广的过程中,还是会发现部分用户无法买账,不接受。主要的挑战有以下两个方面。
第一,接入成本高。
虽然 OTel 的出现已经将用户的接入成本降低了很多,SDK 中也提供了很多自动插码的逻辑,对于一些常见的库都有自动 hook 的逻辑,但也仅限于 Python 或者 Java 这种可注入的语言。但对于像 C、C++ 这种语言,接入成本就比较高了,几乎所有请求都需要自己手动插码。而在腾讯,游戏基本上都是基于 C、C++ 开发的。
另外对于存量的游戏业务,或者代理的游戏业务,接受度就更低了,基本上不可能要求游戏研发方给运营加上这个插码逻辑。
第二,数据的完整性。
基于 eBPF 的可观测性实践
那么怎么去解决这些问题呢?前面讲了这么多,现在终于到今天的重点了:eBPF。
eBPF(Extended Berkeley Packet Filter)是最近一两年兴起的技术,我们一直在关注这个领域。简单地说,eBPF 是对内核能力的扩展,它可以将用户的代码加载到内核中运行,这样我们就可以 hook 所有系统函数调用,捕获所有的系统请求和网络请求,然后分析用户程序的行为和网络数据的行为。
目前,eBPF 的应用场景主要集中在网络、安全、和可观测性这三个领域。 许多大公司已经在使用这项技术,这是从 ebpf.io 官网上截取的一张图片,上面列出了所有使用 eBPF 技术的优秀公司及产品。从这些公司中,我们可以大致了解到这些大牛公司的技术发展方向。我挑选出了所有应用了可观测性领域的公司,基本上有以下几家:
Cillium 是基于 eBPF 的一层封装,提供了基础的数据抓取能力。它旗下两个产品,Hubble 和 Tetragon,分别专注于网络的可观测性以及安全可观测性。SysmonForLinux 是由微软开发的一个 Linux 命令行工具,可以监控和记录系统活动,包括进程生命周期、网络连接、文件系统写入等等。Pixie 是一款专注于应用可观测的产品,已经被 New Relic 收购,主要面向 K8s,但提供的工具不多,更多地提供一种脚本化操作能力。
最后就是 DeepFlow,这是一个高度自动化的可观测性平台。在可观测性领域,DeepFlow 创新地实现了 AutoTracing、AutoMetrics、AutoTagging、SmartEncoding 等领先的技术,目前来看, 应该是该领域最为前沿的产品。
上图是 DeepFlow 在 GitHub 上的一个架构图,比较简单。由 Agent 和 Server 两个组件组成,Agent 负责采集,Server 负责管理 Agent、标签注入、数据的写入和查询。
让我们先来感受一下,eBPF 和 OTel 数据结合后的一个效果。
我们以一个自有的应用服务来进行演示,当前页面上的展示了 Trace 数据查询接口。
该接口是通过页面请求 Web 服务,再由 Web 服务请求 API 服务。左侧图表展示了在未使用 eBPF 时的调用链视图,而右侧则揭示了整合后的效果。可以清晰地看到,从浏览器发起请求,到 Web 请求 API 服务的全过程,网络和系统的调用关系及耗时都被完整呈现。同时,我们还成功识别出了如 Nginx 等未插码的接入层服务。
整个过程无需用户参与,也不必修改任何代码,就能完全追踪这些数据。
技术挑战
在实现这个功能的过程中,我们遇到了四个挑战:
挑战一:数据如何打通?
首先是 DeepFlow 数据跟我们观测平台的数据怎么去打通的一个问题。
下面我将逐一解析如何突破这些挑战。
首先,我们要 打通数据, 有两个选择:或是观测平台将 OTel 数据提供给 DeepFlow,或是 DeepFlow 将数据导出到我们的接收端。
尽管 DeepFlow 已具备前者的能力,但为了与观测平台后续数据的联动,并减少组件的维护,我们选择了后者。我们提出了此需求,希望 DeepFlow 能将数据导入我们的平台,DeepFlow 社区非常给力,很快就在 Server 上支持了数据导出的方式,且导出的数据仍为 OTel 格式。这样,我们接收端只需做少许微调,便可以复用已有的 OTel 接入流程,直接入库和查询。
这样我们就打通了数据,那么,下一步应如何关联数据呢?
挑战二:eBPF+OTel 数据如何关联?
首先来看向下关联。对于一个 HTTP 请求,如果符合 W3C 标准,其实在 header 中存在一个名为 traceparent 的项,traceparent 本身包含了 traceid 和 spanid 的信息。当 DeepFlow Agent 读到 HTTP 的 header 中的这份数据后,会将此信息与下游所有的系统 Span 以及网络 Span 关联起来。也就是说,下游的系统 Span 和网络 Span 都会被标记上 traceid 和 spanid。因此,向下关联可以直接通过 traceid 和 spanid 实现。
解决了向下关联的问题,我们再来看如何 向上关联。向上关联的过程并没有像之前提到的 traceid 和 spanid 这种直接关联的方式,那怎么办呢?
幸好我们有一个线索可以利用,就是在同一进程下的同一线程这个维度,相邻的系统调用会有一个叫做 syscalltraceid 的字段,用以记录相邻的系统调用。 有了这个信息后,我们可以根据 traceid 和 spanid 先找到下游的 Span 信息,然后 通过下游系统 Span 的 syscalltraceid 关联到上游的系统 Span。
此外,系统 Span 和网络 Span 之间会被标记上相同的 TCP 序列号。因此,当我们找到上游的系统 Span 后,可以通过 TCP 序列号找到上游的网络 Span。通过这样不断地向上查找,我们就可以完整地补全上游的 Span 数据。
挑战三:数据如何展示?
然后我来探讨数据的展示问题,尤其是排序展示的挑战,有三种方式。
第一种方式,我们可以根据开始时间排序,较早的时间排在前面,较晚的时间排在后面。然而,当两个系统主机存在时钟差异时,开始时间可能会混乱。因此,使用开始时间排序可能会引起混乱。
第二种方式:是根据 耗时进行排序。 由于从上到下的调用,上层的耗时肯定比下层的耗时要长,我们可以将耗时较大的排在前面,耗时较小的排在后面,这样也可以得到良好的顺序。然而,也可能遇到一些极端情况,比如网络间的调用,其耗时差异本身就非常小,比如容器网络和主机网络之间的差异就非常小。如果我们的精度只到毫秒,那么这部分网络的耗时就会相同,在这种情况下,也可能出现混乱。
第三种方式:是 按系统调用的顺序进行排序,并针对具体问题进行具体分析。例如,在客户端,我们可以将应用 Span 放在第一位,然后是系统 Span,网络 Span 则按照容器网络、虚拟机网络、主机网络的顺序排列。当流程走到服务器端时,我们可以将这个顺序反过来。这样就可以很好地在页面上展示 Span。
解决了上述问题后,还有根据用户的需求对原有的交互进行一些改进。因为系统 Span 和网络 Span 都加入了,会导致一次调用的 Span 数量至少增加了一倍,而一般开发者可能更关注业务逻辑的 Span 调用,只有在遇到基础组件问题时,他们才会关注系统的调用耗时以及网络的调用耗时。因此,我们采用了开关的方式,默认不开启,只有当用户打开了这个开关时,我们才会在页面上展示系统 Span 和网络 Span。 这样,无论是从运维视角还是开发视角,我们都可以在同一个页面进行查看。
挑战四:数据如何隔离?
我们再看一下数据怎么去隔离,实际上是与 DeepFlow 团队一起探讨的一个方案。在容器环境下,POD 会有一些通用的标签,代表应用的归属信息。如果我们能获取这部分数据,就可以在我们的接收端进行良好的路由。在与 DeepFlow 一起探讨了这个需求之后,DeepFlow 很快就支持了这一部分数据的抓取,并将这些信息导出到我们接收端的数据中。从右边的图中可以看到,在 Span 详情中有 K8s 的所有标签信息。这样,我们就可以在接收端进行的应用路由,从而实现数据隔离的能力。
在突破前面的这些挑战之后,我们实现了最初看到的那个效果图(基于 eBPF 和 OTel 的产品视图)。我们放大看一下 Web 到 API 的部分,可以看到基于 OTel 上报的 Span 只有两个,Web 端有一个主调的 Span,API 端有一个被调的 Span。
通过 eBPF,我们零成本地关联出了 6 个系统 Span 和 10 个网络 Span,同时也发现了我们的接入层 Nginx 网关以及未插码的服务,找出了这些组件服务的盲区,同时也很好地覆盖了系统和网络的整体调用关系和耗时。对于容器层面,也直接展示出来,很好地缓解 K8s 及容器网络所带来的复杂性。
基于 eBPF 的案例分析
接下来,我将分享一个基于 eBPF 结合的产品案例,这是很久以前遇到的一个问题,用户反馈说有时候日志查询的耗时非常长。这个案例的架构与我之前所讲述的架构非常相似,用户通过浏览器查询日志,首先到达 Web 服务,然后 Web 服务会调用 API 服务,API 服务会请求 ES,然后将数据返回。
从图中我们可以看到,Web 部分的 span 耗时大约 55 秒,基本上已经达到了不可接受的状态。最初,我们怀疑是不是 ES 出了问题,可能当时 ES 的负载较大,导致查询请求排队,从而造成了请求耗时过长。
然而,通过抓包查看 API 层面的耗时,我们发现这一部分的耗时可能不到 3 秒,这就排除了存储问题。那么,这些消耗的时间都去哪了呢?在 web 和 api 之间又经过了哪些基础组件呢?当遇到非业务组件问题时,问题会变得棘手。
在当时,我们的排查方式相当常规,首先找人,寻找 Web 和 API 之间到底经过了哪些基础组件,然后从请求的域名开始,逐步找到所有的服务,并检查这些服务的指标。如果某个服务没有指标,我们通常会采用 tcpdump 抓包的方式,然后使用 Wireshark 或者 Charles 这类工具进行分析,确定请求耗时长的原因在哪里。
这个问题的发生的原因,非常隐蔽,其中一个服务在接收到 API 返回的数据后,会尝试猜测其编码。这是 Python 的 requests 库的一个特性,当遇到长文本时,这个猜测过程可能会非常耗时。由于此处是日志查询,所以文本长度恰好会很长。
最后倒是解决方案非常简单了,即让 API 服务在返回数据时附带 charset 编码信息。这样,requests 库就不会去猜测编码了,从而解决了这个问题。
但这里,我们需要注意的并非问题本身,而是整个排查过程的耗时。当问题出现在基础组件时,排查时间通常会非常长,过程非常痛苦。 如前述案例,由于涉及到的基础组件较多,整体排查耗时可能会达到天级别。如果涉及到跨部门或跨团队,这个耗时可能会更长,一周甚至更久都有可能。
如果我们有了 eBPF 技术,当类似的问题再次出现时,我们可以更加轻松地进行排查。例如,如左图所示,客户端请求耗时 1.32 秒,但 API 端只需要 400 毫秒。那么,这段时间的消耗在哪里呢?
如果我们使用了 eBPF 技术,只需要在页面上勾选相关选项,我们就能快速查看从 Web 到 API 之间所有经过的服务以及中间系统网络的耗时。从图上可以看到,请求耗时最大的部分主要集中在某几个区域。通常,我们只需要花费大约 1 秒钟,就可以明确责任方。
这种方式极大地减少了问题排查的时间,降低了人力成本。 我们不再需要逐一询问各个服务的责任人,也无需对所有可能的服务进行 tcpdump 抓包分析,大大提高了效率。
总结与展望
在 eBPF 技术的加持下,我们未来主要工作是, 有两个方向:第一个方向:打造游戏观测场景,全面覆盖自研业务以及代理业务。
首先,对于自研业务,我们希望通过 eBPF 技术打通游戏自身的自研组件数据,无侵入式地覆盖所有游戏业务,提供全面的观测场景。
其次,对于代理业务,也就是我们托管并运行的第三方开发的游戏,由于我们很难要求第三方通过插入代码的方式来获取观测数据,因此 eBPF 的零代码插入能力就显得尤为重要。通过 eBPF,我们可以快速、低成本地获取所需的观测数据。
通过上述两个方面的覆盖,我们可以在很大程度上完善游戏业务的整体观测场景,进一步提升我们的服务质量和效率。
第二个方向:将 eBPF 数据与更多的数据进行关联。
例如,我们希望将 eBPF 数据与我们的蓝鲸配置平台以及蓝鲸容器管理平台的数据进行整合,从而为业务构建一个更全面的应用全景视图。这样,我们就能够不仅仅从应用层面,还能从逻辑架构层面,迅速地进行故障生成和根因定位。有了这个全景视图,我们也能反向推动所有对象的数据关联关系规范化。
目前,蓝鲸社区官方已经推出了免费线上体验环境,大家可以以最低的成本和最快的方式来了解和使用蓝鲸的所有产品。此外,我们的 eBPF 能力也将在近期推出。