
嘉宾 | 刘晗 整理 | 郑远程
出品 | CSDN云原生
2022年5月10日,在CSDN云原生系列在线峰会第4期“Apache SkyWalking峰会”上,Tetrate工程师、Apache SkyWalking PMC成员刘晗分享了SkyWalking原生eBPF探针的应用实践。
戳👇观看刘晗分享视频
Tetrate刘晗透彻解析eBPF与可观测性的联系


从上往下看,最上面部分代表的是使用案例:
用eBPF进行一些网络上的优化
对安全进行监控、处理
进行可观测性领域建设
对于User Space:
eBPF技术有很多项目,比如bcc、cilium
当开发eBPF程序时,它有很多不同语言的框架,比如最常见的Golang、 C++、Rust
对于Kernel:
当eBPF程序嵌入到Linux内核过程中,同时可以对用户态程序进行监控,也可以对内核的一些运行过程进行监控处理

举例来说,现在有两个微服务进程,a服务调用b服务,在调动过程中会涉及一些网络通信问题,在进程内部无论如何处理,它最终都会通过syscall进行调用系统函数,比如调用sendmsg发送消息,内核接收到来自业务进程的请求就会进行相关的处理,最终到达一些硬件部分,不管是读取文件还是网络或者其他操作都会涉及到内核。
eBPF程序可以把它理解为是一个基于事件驱动的程序回调。

比如进行调用系统syscall时,可以嵌入eBPF程序,进行相关处理。
进行网络请求时,可以嵌入eBPF程序,感知到这个程序并进行网络请求。
进行自定义程序处理、网卡及其网卡涉及到的相关操作时,同样可以嵌入eBPF程序进行处理。
比如有一个进程,它准备执行一个系统函数,此时内核就会检测到这个函数是否有eBPF程序,如果有就会先执行eBPF程序。
下面是一个简单的eBPF程序,执行完这个函数之后,才会真正调度执行内核函数。

下图展示了eBPF框架bcc可以做哪些事情。

从中间往外看,中间相当于是Linux,上面有应用程序,中间有系统syscall interface,下面有内核方法,比如CPU执行会用到schedule或者内存执行时会用到的Visual Memory,再或者网络磁盘上可以进行调用的相关函数,包括最终的Device Driver驱动硬件。图中每个不同颜色的块代表不同模块。
在外层则代表对应的内核、应用程序或者硬件上,可以做出什么样的工具。最常见的是当业务程序产生内存溢出时,可以利用oomkill这个eBPF程序感知到内存溢出并输出结果到控制台中。
eBPF程序开发与运行

首先需要编写一套C代码,它由很多代码块所组成。
通过Clang编译器编译成bytecode。
通过go library 调用系统syscall 把程序加载到内核中,当加载到内核中时,会先验证eBPF程序合法性,比如是否有很多循环等,如果通过不了,就无法执行eBPF程序。从而保证eBPF程序在内核中运行时是安全且高效的。
当验证通过之后,通过JIT编译器,将bytecode编译成机器特定的指令集,以优化程序的执行速度。
内核程序如何与用户的程序进行程序交互呢?其实eBPF专门有一套Maps机制,可以把它理解为Map或者是Array。业务程序或者eBPF程序通过写入到eBPF Map中,双方就可以读取、修改对应的数据内容。

整个系统中遍布大量Event,都可以进行拦截,进行定制化的操作。
eBPF程序是动态加载的。动态加载理解为程序部署在服务器时,这个程序不用做任何代码改动,就可以动态加载、卸载eBPF程序。对应用程序没有任何侵入。
所有的eBPF程序均在内核中执行,具有很高的执行效率。
eBPF有很多应用场景,包括可观测性、安全和网络等领域,并且可以从内核态和用户态两个层面对系统进行监控,以全局视角进行操作。


从左往右看,SkyWalking可以收集很多方面的数据,比如日志、指标或者Tracing甚至Event,通过Kafka、gRPC或者HTTP接收到请求。
之后,通过Receiver集群接收到所有请求,同时再传递到真正的聚合集群,对所有数据进行聚合处理。随后进行存储,同时可以将数据导出,包括告警导出或其他指标导出。最终通过UI界面或者CLI的方式进行查询。

最常见的是指标,指标可以收集各个进程、服务的指标运行情况。其次基于网络分析,分析出多个服务进程、指标之间的关联关系,从而生成拓扑图。
性能剖析分析可以对内核态和用户态的进程进行dump线程操作,从而生成火焰图,协助开发人员进行更快速的性能问题分析。
对应用系统的网络进行优化,最常见场景是Service Mesh领域的Envoy,当业务程序与Envoy或者Envoy与业务程序进行交互流量时,内部会通过很多iptables规则来完成数据交互。通过eBPF程序,可以对请求进行短路优化,从而绕过iptables规则来实现性能提升。
在SkyWalking中,我们通过新建一个SkyWalking Rover子项目(一个eBPF探针),可以对当前机器、进程等信息进行采集,并且基于eBPF技术对程序进行Profiling操作。

SkyWalking Rover系统需要部署在每一个Linux机器上,通过一系列的配置监控本机中的进程,并且会把进程信息上报到SkyWalking后端系统中进行相关处理,这就是SkyWalking Rover的整体架构。

SkyWalking有几个比较关键的实体概念,Service、Service Instance、Endpoint。
但SkyWalking Rover引入后,其实缺少一个概念。当前的Service Instance其实是逻辑上的概念,无法代表一个真实的进程,所以SkyWalking需要在Service Instance下面引入新的Process实体。Process可以理解为是真正的Linux进程,因此进程不是逻辑上的概念,而是物理上的概念。
eBPF Profiling通过对内核态和用户态的执行状态分析,生成一张火焰图。

如上图所示,每一层就代表了栈中的每一个栈帧的名称。越往上走,说明该栈具有较高的深度。如果这个函数执行时间相对较长,那么该层的宽度越宽。基于以上两点,来帮助用户了解到哪些地方需要进行优化。
如果某进程的CPU耗时较多,此时可以利用On CPU Profiling周期对进程和内核采样线程数据,比如几毫秒进行一次采样,查看进程执行情况,帮助开发人员更好地分析出哪个代码行出现问题,哪个CPU消耗是比较多,从而帮助开发人员优化系统性能。

如上图所示,从上往下看,上面是On CPU执行,下面是Off CPU,分别代表进程是否在CPU上执行。
当程序在CPU上执行时,先执行A方法,并且完成了一次采样,此时方法栈就是A。当A方法中调用了方法B之后再次执行一次采样,此时这个栈就是BA。
在B方法中需要进行文件读取操作,便通过调用syscall系统函数请求内核进行文件读取。因为文件读取需要与硬件交互,此时,内核就会将读取任务下发至硬件来执行,并将当前程序让出CPU。这时候,程序就会从On CPU切换至Off CPU,因为我们是On CPU Profiling,所以此时没法办法采样到数据。当进程重新回归到On CPU执行时,则可以继续进行采样工作。这就是一个最典型的On CPU Profiling的实践原理。
当有了On CPU Profiling数据之后,需要把这份数据合成为可以理解的内容。
在内核中,数据内容依旧是以栈的形式存储的,但栈的每一个栈帧其实是个内存地址,此时就需要通过进程符号表来将地址和真正的符号进行映射。这个符号可以理解为是代码中的每一个代码行,通过将这两者结合,可以得知真正的栈的情况。

SkyWalking在9.0版本之前有一个Profiling功能,但这个Profiling可以理解为Trace Profiling。
那么Trace Profiling和eBPF Profiling有什么关联性,它们有什么区别?如下表所示。

在数据关联维度上可以理解Trace Profiling只能和单个Trace进行相互关联,而eBPF Profiling是整个Process全局的,和某个Trace没有任何关系。
目前Trace Profiling支持Java、Python这两个语言, eBPF主要支持C、C++、Golang语言。
Trace Profiling只能Dump单一线程数据,而eBPF Profiling可以Dump多个线程。
Trace Profiling只支持周期性的采样,比如9毫秒取样一次。而eBPF Profiling除了支持周期采样以外,同时支持基于事件进行采样。
在数据内容层面上,Trace Profiling只能支持用户进程,而eBPF Profiling可以将内核和用户进程的执行情况进行相互关联,帮助以更全局视角来分析这个程序的执行情况。
eBPF Profiling还支持Service或单一Process维度的数据聚合,比如进行Profiling之后,可以把该服务下面所有服务实例的进程进行数据聚合,更全面地了解整个服务Profiling执行状态的样子。


第一部分需要有相关的应用程序,应用程序需要确保拥有符号表。
第二部分需要部署一个Rover程序:配置进程寻找规则,并且启动Rover程序。
第三部分需要创建一个Profiling任务,最终可以查看到火焰图。

在上图中,左侧是创建了一个简单的Golang程序,这个程序会启动一个HTTP端口,当监听到HTTP请求后,会调用到sqrt函数,让程序一直保持在CPU执行;右侧是对该程序进行编译执行,发送请求之后就会让进程始终保持在CPU上执行。通过Top命令可以看到,该程序一直在CPU上执行,一直占用单个CPU的资源。

左侧这张图是在编译时,把符号表去掉了,这时候再通过objdump来查看到这个进程中的符号表时发现确实并没有任何的符号信息,此时是没法进行Profiling的,因为它无法将在栈中的内存地址转换成真正需要的信息,所以无法进行查看。
从右侧这张表可以看到,有很多符号信息,此时SkyWalking Rover可以将内存地址转换为真实的符号信息。

第一种方式是通过Command Line匹配方式,可以理解为通过ps命令来查询到进程的完整命令行,Rover则会根据配置的正则信息与命令行进行匹配。
第二种方式是Agent感知,该方式需要对Agent进行一定的改造工作。当改造后的Agent与程序共同启动后,Rover可以动态感知到该进程。目前我们已经在Go2Sky项目中完成了该Agent的改造工作。
第三种方式是最常见的——Kubernetes进程感知。SkyWalking Rover可以感知到所部属宿主机中所有Pod的启动和关闭,并且根据规则自动感知Pod中的进程信息。
首先在界面中,可以看到一个eBPF Profiling控件,当点开之后,会形成右侧这个控件。

左侧是一个任务列表,展示了它所创建的Profiling任务列表,右侧是该任务的执行情况。每一个任务均与服务实体产生关联。

Profiling任务需要多方面的信息:首先是标签,这个标签可以理解为一个Process标签,比如可以给这个进程打很多的标签;当有了标签之后,就可以跨实例地筛选出符合标签的所有进程,从而确认进程,发送Profiling任务。
目标类型则说明如何对该进程进行Profiling,目前支持On CPU Profiling,与Trace Profiling类似,均是以指定的间隔时间来完成数据采样。

左侧任务列表可以看到刚才所创建的任务,右侧蓝条代表着该Profiling任务执行过一次调度,调度的时间范围是9:51到9:56。

当点击分析按钮之后,就可以看到该进程的火焰图,hello server这个方法执行比较耗时,该方法其实是刚才在代码中所看到的sqrt的所在函数。


在指标层面,可以配置化地进行采集指标或者event。其次可以利用监控网络形成相关拓扑图。
在Profiling层面,目前已经有了On CPU Profiling,之后可以引入网络请求,内存分配或者磁盘写入的Profiling。比如磁盘写入Profiling可以理解为当某进程I/O占比较高的时候,可以进行网络Profiling,从而分析出是因为那一部分代码导致磁盘写入量比较大。
在网络方面,主要是针对于Service Mesh中的应用与Envoy交互,实现iptables短路优化。
聚焦云原生新技术、新实践,帮助开发者群体赢在开发范式转移的新时代。欢迎关注CSDN云原生微信公众号~