BPF和Go:Linux中自省的现代形式

2020-12-16 02:02:08

每个人都有自己喜欢的关于魔法的书。一个人是托尔金,另一个是普拉切特,第三个是像我一样的人,是麦克斯·弗莱。今天,我将向您介绍我最喜欢的IT魔术:BPF及其周围的现代基础架构。

BPF目前正处于普及的高峰。该技术正在突飞猛进,到达意想不到的地方,并变得越来越为普通用户所用。如今,几乎每个受欢迎的会议都围绕这个主题进行演讲,而早在八月,我就应邀在俄罗斯GopherCon上进行了演讲。

我在那里有很好的经验,所以我想与尽可能多的人分享。本文将为您提供为什么我们需要BPF之类的背景,帮助您了解何时以及如何使用它,以及它如何帮助您作为工程师并改善您正在从事的项目。我们还将查看与Go相关的细节。

我真正想要的是让您的眼睛在像初读《哈利波特》后的小孩子一样读完这篇文章后开始发光,让您自己尝试这个新的“玩具”。

好吧,一个34岁的大胡子男人眼神灼热,您会告诉他什么魔力?

我们现在生活在2020年。打开Twitter,您可以阅读愤怒的技术专家的推文,他们都说今天编写的软件质量太差,所有这些都需要扔掉,我们需要从头再来。有些人甚至扬言完全放弃该专业,因为他们无法忍受一切破裂,不便和缓慢的情况。

他们可能是对的:没有征询一千条评论就无法定义原因。但我绝对同意的一件事是,现代软件堆栈比以往任何时候都更加复杂:我们拥有BIOS,EFI,操作系统,驱动程序,模块,库,网络交互,数据库,缓存,协调器(例如K8), Docker容器,最后是我们自己的带有运行时和垃圾回收的软件。

真正的专业人士可能需要花费数天的时间回答有关在浏览器中输入google.com之后会发生什么的问题。

理解系统中发生的事情非常复杂,尤其是在当前情况出现问题并且您正在赔钱的情况下。这个问题导致了提供帮助您解决系统内部正在发生的事情的业务的出现。在大型公司中,夏洛克·福尔摩斯(Sherlock Holmes)型侦探的整个部门都知道在哪里敲击这把谚语的锤子,以及拧紧哪些谚语的螺栓以节省数百万美元。

我想问人们如何在最短的时间内调试出意外的问题。通常,想到的第一种方法是分析日志。但是问题在于,唯一可访问的日志是开发人员在其系统中放置的日志,并且不够灵活。

第二种最受欢迎​​的方法是研究指标。 Go中编写了三种最受欢迎​​的度量标准系统。度量标准非常有帮助,尽管它们确实允许您查看症状,但它们并不总是帮助您定义根本原因。

第三个是所谓的“可观察性”:您可以在其中询问有关系统行为的尽可能多的复杂问题,并获得这些问题的答案。由于问题可能非常复杂,因此答案可能需要最广泛的信息,在提出问题之前,我们不知道这些信息是什么。这意味着可观察性绝对需要灵活性。

如何提供一个机会来“实时”更改日志记录级别?用调试器在程序运行时连接到程序,并做些什么而又不会在程序运行时中断该怎么办?如何理解将哪些查询发送到系统,可视化慢速查询的源,通过pprof查看正在消耗的内存以及获取其随时间变化的图表呢?如何测量一个函数的延迟以及延迟对参数的依赖性?我将所有这些方法归类为可观察性术语。这是一组实用程序,方法,知识和经验,它们共同为我们提供了机会,如果不做我们想做的事,那么至少可以在系统运行时在系统中进行很多“实时”的工作。这在现代IT中相当于瑞士军刀。

但是我们怎样才能做到这一点呢?市场上已经有并且仍然有很多工具可用:简单,复杂,危险和缓慢的工具。但是今天的文章是关于BPF的。

Linux内核是事件驱动的系统。实际上,内核以及整个系统中发生的所有事情都可以视为一组事件。中断是一个事件;通过网络接收数据包是一个事件;将处理器的控制权转移到另一个进程是一个事件;运行功能是一个事件。

是的,因此BPF是Linux内核的子系统,它使您有机会编写一些小程序,这些程序将由内核运行以响应事件。这些程序既可以说明系统中正在发生的事情,也可以对其进行控制。

BPF的第一个版本于1994年问世。为tcpdump实用程序编写简单的规则以查看或“嗅探”网络数据包时,您可能会遇到它。您可以为tcpdump设置过滤器,因此您不必查看所有内容,只需查看您感兴趣的数据包即可。例如,“仅tcp协议和仅端口80”。对于每个通过的数据包,将运行一个函数来确定您是否需要保存所讨论的特定数据包。可能有很多数据包,因此我们的功能必须快速。实际上,我们的tcpdump过滤器已转换为BPF函数。这是一个例子。

原始的BPF代表了一个非常简单的虚拟机,带有多个寄存器。但是,尽管如此,BPF大大加快了网络数据包的过滤速度。在当时,这是向前迈出的重要一步。

2014年,非常著名的内核黑客Alexei Starovoitov扩展了BPF的功能。他增加了寄存器的数量和程序的允许大小,添加了JIT编译并创建了一个检查程序,检查程序是否安全。但是,最令人印象深刻的是,新的BPF程序不仅能够在处理数据包时运行,而且还能够响应其他内核事件,并且能够在内核和用户空间之间来回传递信息。

这些变化为使用BPF的新方法打开了机会。以前可以通过编写复杂而危险的内核模块来实现的某些功能,现在可以通过BPF相对简单地完成。为什么这么好?因为编写模块时发生任何错误通常会导致恐慌,而不是导致“蓬松”的Go风格的恐慌,而是导致内核恐慌,此后唯一要做的就是重新启动。

普通的Linux用户突然有了一种新的超级能力:能够“在引擎盖下”-以前只有硬核内核开发人员可以使用,或者任何人都不能使用。可以将此选项与无需费力就可以为iOS或Android编写程序的能力进行比较:在旧手机上,这是不可能的,或者要复杂得多。

来自Alexei Starovoitov的BPF的新版本称为eBPF(扩展名为e)。但是现在,它已取代了BPF的所有旧用法,并且变得如此流行,以至于为了简单起见,它被称为BPF。

好的,可以将BPF程序附加到哪些事件或触发器上,人们是如何开始使用其新获得的功能的?

第一组用于处理网络数据包和管理网络流量。这些是XDP,流量控制事件等。

创建简单但非常有效的防火墙。诸如Cloudflare和Facebook之类的公司使用BPF程序来过滤出大量的寄生流量并抵抗最大规模的DDoS攻击。由于处理是在数据包生命的最早阶段进行的,因此直接在内核中进行(BPF程序有时甚至直接推送到网卡进行处理),因此可以通过这种方式处理大量流量。这些事情以前是在专用网络硬件上完成的。

创建更智能,针对性强但性能非常出色的防火墙-可以检查通过的流量是否符合公司规则,漏洞模式等。例如,Facebook内部进行此项审核,而某些项目则在外部销售此类产品。

创建智能平衡器。最杰出的例子是Cilium项目,该项目最常在K8s集群中用作网状网络。 Cilium管理流量,平衡,重定向和分析流量。所有这些都是借助于内核运行的小型BPF程序来完成的,以响应与网络数据包或套接字有关的事件。

这是与网络问题相关并能够影响行为的第一组触发器。第二组与更一般的可观察性有关。该组中的大多数程序都无法影响任何东西,而只能“观察”。这使我更加感兴趣。

perf事件—与性能以及与perf Linux分析器有关的事件:硬件处理器计数器,中断处理,次要/主要内存异常的侦听等。例如,我们可以设置一个处理程序,该处理程序将在每次内核需要从swap读取内存页面时运行。例如,假设有一个实用程序显示当前正在使用swap的程序。

跟踪点—内核源代码中的静态(由开发人员定义)位置,您可以从中提取静态信息(通过将其附加到开发人员之前准备的信息)。在这种情况下,看起来静态是一件坏事,因为我说过日志的缺点之一是它们仅包含程序员最初放置的内容。从某种意义上说是正确的,但是跟踪点具有三个重要的优点:-在最有趣的位置,有很多跟踪点散布在内核中-当它们不在“ on”时,它们不使用资源-它们是API的一部分,它们是稳定的并且不会改变。这非常重要,因为我们将提到的其他触发器缺乏稳定的API。例如,想象一个显示程序的实用程序,由于某种原因,内核没有时间来执行程序。您坐着,想知道为什么它这么慢,而pprof却没有有趣的表现。

USDT —与跟踪点相同,但用于用户空间程序。也就是说,作为程序员,您可以将这些位置添加到程序中。许多大规模的知名程序和编程语言已经采用了这些痕迹:例如,MySQL或PHP和Python语言。通常,它们的默认设置是“ off”,要打开它们,您需要使用— enable-dtrace参数或类似参数重建解释器。是的,我们还可以选择在Go中注册这些类型的跟踪。您可能已经在参数名称中识别了DTrace一词。关键是,这类静态跟踪已由具有相同名称的系统普及,该名称源自Solaris OS。举个例子,想象一下能够知道何时创建新线程,何时启动GC或与特定语言或系统相关的其他事物。

Ftrace触发器使我们可以选择在几乎任何内核功能开始时运行BPF程序。完全动态。这意味着内核将在您选择执行的任何内核函数或所有内核函数开始执行之前调用BPF函数。您可以附加到所有内核功能,并在输出时获得所有调用的吸引人的可视化效果。

kprobes / uprobes提供的功能几乎与ftrace相同,但是在内核和用户空间中执行函数时,可以选择附加到任何位置。如果在函数中间,变量上有一个“ if”,而您需要为该变量建立一个直方图值,那不是问题。

kretprobes / uretprobes —此处的所有操作都类似于先前的触发器,但是可以在内核函数或用户空间中的函数返回时触发。这类触发器便于查看函数返回的内容以及测量执行时间。例如,您可以找出“叉”系统调用返回了哪个PID。

我再说一遍,关于这一切的最奇妙的事情是,响应这些触发器中的任何一个而被调用,我们的BPF程序可以很好地“环顾四周”:读取函数的参数,记录时间,读取变量,读取全局变量,进行堆栈跟踪,保存内容以备后用,将数据发送到用户空间进行处理和/或从用户空间获取数据或其他控制命令以进行过滤。奇妙!

我不了解您,但是对我来说,这个新的基础设施就像一个玩具,我很想长期使用。

好的,Marko,您已经说服我们看一下BPF。现在我们如何仔细看看?

让我们看看BPF计划的组成以及如何与之互动。

首先,我们有一个BPF程序,如果通过验证,它将被加载到内核中。在那里,JIT编译器会将其编译为机器代码,并在内核模式下运行,当所连接的触发器被激活时。

BPF程序可以选择与第二部分进行交互,即与用户空间程序进行交互。可以通过两种方式发生这种情况。我们可以写入循环缓冲区,而用户空间部分可以读取它。我们还可以对键-值映射(称为BPF映射)进行写入和读取,并且用户空间部分可以相应地执行相同的操作,并且可以相互传递信息。

使用BPF的最简单方法(在任何情况下都不应该从头开始)包括使用C语言编写BPF程序,然后使用Clang编译器将相关代码编译为虚拟机代码。然后,我们直接使用BPF系统调用加载此代码,并使用BPF系统调用与我们的BPF程序进行交互。

可用的第一个简化是使用libbpf库。它与内核的源代码一起提供,使您可以直接使用BPF系统调用。基本上,它为加载代码和使用BPF映射提供了方便的包装器,以将数据从内核发送到用户空间并返回。

显然,这对人们来说并不方便。幸运的是,以iovizor为品牌的BCC项目已经出现,这使我们的生活更加轻松。

基本上,它准备了整个构建环境,并允许我们编写单个BPF程序,其中С-part将被构建并自动加载到内核中,而用户空间部分可以使用Python进行制作,这非常简单明了。

但是,密件抄送似乎在很多方面都很复杂。由于某些原因,人们尤其不喜欢用С书写部分。

那些来自iovizor的人都提供了bpftrace工具,该工具使您可以使用类似于AWK(甚至单线)的简单脚本语言编写BPF脚本。

Brendan Gregg是生产力和可观察性领域的著名专家,他对BPF的可用工作方式进行了以下可视化处理:

纵轴表示可以轻松使用给定工具,而横轴表示其功能。您可以看到BCC是一个非常强大的工具,但它并非超级简单。 bpftrace更加简单,但同时功能也有所降低。

但是,让我们看一下我们已经可以利用的一些神奇力量的具体例子。

BCC和bpftrace都包含一个“工具”目录,其中包含大量有趣且有用的随时可用的脚本。它们也可以用作本地堆栈溢出,您可以从中复制自己的脚本的代码块。

╭─marko@ marko-home〜╰─$ sudo gethostlatency-bpfcc时间PID通讯LAMS主机16:27:32 21417 DNS Res〜ver#93 3.97 live.github.com 16:27:33 22055 cupsd 7.28 NPI86DDEE.local 16 :27:33 15580 DNS重新版本#87 0.40 github.githubassets.com 16:27:33 15777 DNS重新版本#89 0.54 github.githubassets.com 16:27:33 21417 DNS重新版本#93 0.35直播。 github.com 16:27:42 15580 DNS Res〜ver#87 5.61 ac.duckduckgo.com 16:27:42 15777 DNS Res〜ver#89 3.81 www.facebook.com 16:27:42 15777 DNS Res〜ver# 89 3.76 tech.badoo.com :-) 16:27:43 21417 DNS Res〜ver#93 3.89 static.xx.fbcdn.net 16:27:43 15580 DNS Res〜ver#87 3.76 scontent-frt3-2.xx .fbcdn.net 16:27:43 15777 DNS解析版本#89 3.50 scontent-frx5-1.xx.fbcdn.net 16:27:43 21417 DNS解析版本#93 4.98 scontent-frt3-1.xx.fbcdn .net 16:27:44 15580 DNS Res〜ver#87 5.53 edge-chat.facebook.com 16:27:44 15777 DNS Res〜ver#89 0.24 edge-chat.facebook.com 16:27:44 22099 cupsd 7.28 NPI86DDEE.local 16:27:45 15580 DNS Res〜ver#87 3.85 safebrowsing.googleapis.com ^ C%

实用程序实时显示DNS查询的完成时间,因此,例如,您可以捕获一些意外的异常值。

这是一个脚本,可以“监视”其他人在终端输入的内容:

╭─marko@ marko-home〜╰─$ sudo bashreadline-bpfcc时间PID命令16:51:42 24309 uname -a 16:52:03 24309 rm -rf src / badoo

这种脚本可用于捕获“邻居”或对公司服务器进行安全审核。

mark─marko@ marko-home〜/ tmp╰─$ sudo / usr / sbin / lib / uflow -l python 20590跟踪python进程20590中的方法调用... Ctrl-C退出。 CPU PID TID TIME(us)Method 5 20590 20590 0.173-> helloworld.py.hello 5 20590 20590 0.173-> helloworld.py.world 5 20590 20590 0.173<-helloworld.py.world 5 20590 20590 0.173<-helloworld.py.hello 5 20590 20590 1.174-> helloworld.py.hello 5 20590 20590 1.174-> helloworld.py.world 5 20590 20590 1.174<-helloworld.py.world 5 20590 20590 1.174<-helloworld.py.hello 5 20590 20590 2.175-> helloworld.py.hello 5 20590 20590 2.176-> helloworld.py.world 5 20590 20590 2.176<-helloworld.py.world 5 20590 20590 2.176<-helloworld.py.hello 6 20590 20590 3.176-> helloworld.py.hello 6 20590 20590 3.176-> helloworld.py.world 6 20590 20590 3.176<-helloworld.py.world 6 20590 20590 3.176<-helloworld.py.hello 6 20590 20590 4.177-> helloworld.py.hello 6 20590 20590 4.177-> helloworld.py.world 6 20590 20590 4.177<-helloworld.py.world 6 20590 20590 4.177<-helloworld.py.hello ^ C%

相同的Brendan Gregg制作了一张图像,其中将所有相关脚本与箭头指向的子系统结合在一起,每个实用程序都允许您观察这些子系统。如您所见,我们已经拥有大量随时可用的实用程序,几乎可以用于任何可能的情况。

当前,唯一能够编译为BPF机器理解的格式的编译器是Clang。另一种流行的编译器GСС仍然没有BPF后端。可以编译为BPF的唯一编程语言是C的非常有限的版本。

但是,BPF程序在用户空间中还具有第二部分。可以用Go编写。

如前所述,BCC允许您用Python编写此部分,这是该工具的主要语言。同时,在主存储库中,BCC还支持Lua和C ++,在副存储库中,它还支持Go。

该程序看起来与Python中的程序完全相同。首先,它有一个字符串,其中BPF程序位于C中,然后我们交流将给定程序附加到何处,并以某种方式与之交互,例如,额外

......