追踪面向的是请求,可以通过获取请求执行的相关数据,轻松分析出请求中的异常点,针对云原生架构下的追踪,大体可以分为针对主机的动态追踪(DynamicTracing),以及针对微服务的应用行为追踪。
动态追踪是一种高级的内核调试技术,通过探针机制,采集内核态或者用户态程序的运行信息,而不需要修改内核和应用程序的代码。这种机制性能损耗小,不会对系统运行构成任何危险。因此,它能够以非常低的成本,在短时间内获得丰富的运行信息,进而可以快速地分析、排查、发现系统运行中的问题。
要实现动态追踪,通常需要在Linux中使用相应的探测手段,甚至涉及编写并编译成内核模块,这可能会在生产系统中导致灾难性后果。经过多年的发展,尽管它们的执行已经变得更加安全了,但是编写和测试仍然很麻烦。
eBPF似乎为上述问题找到了解决的福音,eBPF通过一种软件定义的方式,提供并支持了丰富的内核探针类型,提供了强大的动态追踪能力。开发者通过编写eBPF程序,可实现相应的追踪脚本,而eBPF利用自身的实现机制,保障了在内核执行动态追踪的效率以及安全性。
eBPF正是设计和实现了一种对内核进行软件定义(SoftwareDefine Kernel)的方式。控制平面是用户空间的各种eBPF程序,实现eBPF程序在内核的加载点以及执行逻辑;数据平面则是内核各种操作的执行单元,这些加载点可以是一个系统调用,甚至是一段确定的实现代码;控制平面和数据平面通过bpf()系统调用进行通信,将用户空间的控制平面逻辑,加载到内核空间数据平面的准确位置。
eBPF程序的类型分为两个方面:追踪(Tracing)和网络(Networking)。
- 追踪:eBPF可以通过各种类型的追踪点访问与特定程序相关的内存区域,从正在运行的进程中提取信息并执行跟踪。这样开发者就可以获取关于系统的行为及其所运行的硬件的直接信息,甚至还可以直接访问为每个特定进程分配的资源,包括文件描述符、CPU、内存等的使用情况。
- 网络:对内核网络的操作。eBPF程序允许开发者监控并且操作计算机系统中的网络流量,这也是BPF原始设计时的核心功能点。eBPF允许对来自网络接口的数据包进行过滤,甚至可以完全拒绝这些数据包。不同类型的eBPF程序可以加载到内核网络中不同的处理阶段。
在网络数据包的处理上,eBPF通常会与Linux内核的另外一个重要功能XDP(Express Data Path)一起实现。XDP是一个安全的、可编程的、高性能的、内核集成的包处理器,它位于Linux网络数据路径中,当网卡驱动程序收到包时就会执行eBPF程序,XDP程序会在尽可能早的时间点对收到的包进行删除、修改或转发到网络堆栈等操作。XDP程序是通过bpf()系统调用控制的,使用eBPF程序实现相应的控制逻辑。
BPFTrace是eBPF的高级追踪语言。它允许开发者用简洁的领域特定语言(DSL)编写eBPF程序,并将它们保存为脚本,开发者可以执行这些脚本,而不必在内核中手动编译和加载它们。它的灵感来自其他一些著名的追踪工具,比如awk和DTrace等,一些用户甚至认为BPFTrace将会是DTrace的一个很好的替代品。
无论是DTrace、SystemTap,还是BPFTrace,其实现动态追踪都是通过探针的机制,依赖于在追踪点实现的探针,进而获取相应的追踪数据。探针是用于捕获事件数据的检测点,BPFTrace在实现内核行为追踪时使用的探针主要包括动态探针(Kprobe/Kretprobe)和静态探针(Tracepoint)两种,这些探针延续了以往常见的动态追踪工具所使用的探针设计。
- 动态探针:Kprobe/Kretprobe
eBPF支持的内核探针功能,允许开发者在几乎所有的内核指令中以最小的开销设置动态的标记或中断。当内核运行到某个标记的时候,就会执行附加到这个探测点上的代码,然后恢复正常的流程。对内核行为的追踪探测,可以获取内核中发生任何事件的信息,比如系统中打开的文件、正在执行的二进制文件、系统中发生的TCP连接等。
内核动态探针可以分为两种:Kprobe和Kretprobe。二者的区别在于,根据探针执行周期的不同阶段,来确定插入eBPF程序的位置。Kprobe类型的探针用于跟踪内核函数调用,是一种功能强大的探针类型,让我们可以追踪成千上万的内核函数。由于它们是用来跟踪底层内核的,开发者需要熟悉内核源代码,理解这些探针的参数、返回值的意义。
- 静态探针:Tracepoint
Tracepoint是在内核代码中所做的一种静态标记,是开发者在内核源代码中散落的一些hook,开发者可以依托这些hook实现相应的追踪代码插入。
开发者在/sys/kernel/debug/tracing/events/目录下,可以查看当前版本的内核支持的所有Tracepoint,在每一个具体Tracepoint目录下,都会有一系列对其进行配置说明的文件,比如可以通过enable中的值设置该Tracepoint探针的开关等。
与Kprobe相比,它们的主要区别在于,Tracepoint是内核开发人员已经在内核代码中提前埋好的,这也是为什么称它们为静态探针的原因。而Kprobe更多的是跟踪内核函数的进入和返回,因此将其称为动态的探针。但是内核函数会随着内核的发展而出现或者消失,因此Kprobe对内核版本有着相对较强的依赖性。
分布式追踪是实现应用链路追踪的一种重要技术手段,同时也是实现云原生可观测性的重要组成部分,其主要用于应用程序性能管理(APM,ApplicationPerformance Management)和故障定位等。常见的分布式追踪工具包括Dapper、Zipkin、Jaeger、SkyWalking、Canopy、鹰眼、Hydra、Pinpoint等,其中常用的开源分布式追踪工具为Zipkin、Jaeger、SkyWalking和Pinpoint。这些分布式追踪工具大致可分为以下三类。
1)基于SDK的分布式追踪工具。以Jaeger为例,Jaeger提供了大量可供追踪使用的API,通过侵入微服务业务的软件系统,在系统源代码中添加追踪模块以实现分布式追踪。此类工具可以最大限度地抓取业务系统中的有效数据,提供了足够多的可参考指标;但其通用性较差,需要针对每个服务进行重新实现,部署成本较高,工作量较大。
2)基于探针的分布式追踪工具。以SkyWalking Java探针为例,在使用SkyWalking Java探针时,需将探针文件打包到容器镜像中,并在镜像启动程序中添加-javaagent agent.jar命令以实现探针的启动,并完成SkyWalking在微服务业务上的部署。SkyWalking的Java探针实现原理为字节码注入,将需要注入的类文件转换成byte数组,通过设置好的拦截器注入到正在运行的程序中。这种探针通过控制JVM中类加载器的行为,侵入运行时环境以实现分布式追踪。此类工具无须修改业务系统的源代码,相对SDK有更好的通用性,但其可获取的有效数据相对SDK类工具较少。
3)基于代理实现。Sidecar作为服务代理,为其所管理的容器实现服务发现、流量管理、负载均衡和路由等功能。在流量管理过程中,Sidecar可以抓取进出容器的网络请求与响应数据,这些数据可以记录该服务所完成的一次单个操作,可与追踪中的跨度信息对应,因此可将Sidecar视为一种基于数据收集的分布式追踪工具。Sidecar无须修改业务系统代码,也不会引入额外的系统的开销。但由于Sidecar所抓取的跨度不包含追踪链路上下文,要将Sidecar所抓取的跨度数据串联成追踪链路是很困难的。
若要对一个微服务业务系统进行分布式追踪,会产生两个基本问题。第一,业务系统运行时可能会产生很多脏数据或发生数据丢失,需要在这种环境下准确地生成追踪数据。第二,面对成百上千的服务所生成的追踪数据,需要设计合适的收集与存储方案。追踪链路是以跨度为根节点的树形数据结构,在微服务中,从客户端发起一次API调用,往往后面会产生多次服务间的API调用,因此追踪链路代表一次完整操作,其中包含了很多子操作。
虽然分布式追踪技术在应用方面已经取得了一些进展,但其仍然存在着一定的局限性。当前的分布式追踪工具或者需要侵入微服务软件系统的源代码,或者需要侵入业务系统的镜像与运行环境,或者在生成的跨度信息与追踪链路的准确性与完整性上仍有缺失。
标签:原生,eBPF,探针,开发者,内核,链路,分布式,追踪 From: https://blog.51cto.com/key3feng/6171822