本篇内容是根据2021年10月份#201 eBPF and Go音频录制内容的整理与翻译
eBPF(已有 7 年历史)是一个可以在 Linux 内核中运行代码的沙箱。它最初是一种构建防火墙的技术,随着时间的推移不断发展,包含一系列新功能。
本期大家讨论了 eBPF 的起源及其工作原理,并深入研究了一些实际用例。虽然 eBPF 程序本身不是用 Go(更像 C)编写的,但我们将了解如何从 Go 代码与 eBPF 程序进行通信。
过程中为符合中文惯用表达有适当删改, 版权归原作者所有.
Mat Ryer:大家好,欢迎来到 Go Time。我是 Mat Ryer,今天我们要聊聊 eBPF。eBPF 是一项技术,它允许你在沙盒中安全地运行程序,而不需要更改内核、代码或安装模块等。这通常是解决诸如网络、安全或可观察性问题的理想之地,因为内核控制一切,它能看到一切……所以可以说它非常完美。但是,由于它是一个如此核心的组件,意味着它实际上很难更改。设想一下你自己的代码,如果你有一个核心服务或其他系统的依赖项,你就会明白这种情况有多难更改;而当你无法更改某些东西的时候,你就无法在这里进行创新。
这通常就是更改内核的故事---
它基本上是不可行的,直到 eBPF 出现,似乎改变了规则。让我们更多了解它吧,因为我们现在就做一期关于它的节目。今天和我一起的有 Derek Parker。你好,Derek!
Derek Parker:你好。
Mat Ryer:Derek,你创建了 Delve,对吧?你直接就创建了 Delve…
Derek Parker:是的,没错。
Mat Ryer:是的。而且你现在是 Red Hat 的高级软件工程师,没错吧?
Derek Parker:是的,没错。
Mat Ryer:非常酷。欢迎来到 Go Time。我们还邀请到了 Grant Seltzer。Grant 是 Aqua Security 开源工程团队的一员。
Grant Seltzer Richman:是的。
Mat Ryer:他住在纽约市布鲁克林。那是个很酷的地方。感谢你加入我们,Grant。
Grant Seltzer Richman:谢谢你邀请我。
Mat Ryer:荣幸之至。荣幸是我们所有人的,一半是我的,一半是---
Johnny Boursiquot 也在这。你好,Johnny。
Johnny Boursiquot:你好,兄弟。我在这儿准备问一些关于 eBPF 的愚蠢问题。
Mat Ryer:哦,很好,很好,这样我就不用问了。
Johnny Boursiquot:我会让你看起来很棒。我会问所有的---
Mat Ryer:是的。[笑] 我其实刚才只是从 Wikipedia 上写了这个介绍,兄弟。接下来的---
今天我是冒牌货。我不介意这些我不太懂的主题,因为我真的可以深入探讨,并且总能学到很多,尤其当我们邀请到像今天这样的尊贵嘉宾时。
那么,谁愿意给我们讲讲 eBPF 的背景?它从何而来,究竟是什么?
Grant Seltzer Richman:正如你所说,eBPF 是一项技术,它允许你编写一些小片段的代码,然后把它们放入 Linux 内核中的特定位置,在某些钩子触发时运行。所以你可以把它看作是为一个网络服务注册一个 webhook;同样的,你也可以为你的实际系统做这件事。这些小片段---
你可以把它们看作是脚本;这些响应的事件可以是内核函数被调用,比如 Linux 内核源代码中的某些事件。你可以将它们附加到函数上,诸如此类的事情……你可以将这些 eBPF 程序附加到网络套接字上,让它们对进出数据包作出响应。你还可以将它们附加到用户空间函数上,所以如果你在运行一个编译好的 Go 程序,一些服务,即使是长时间运行的服务,你可以将它们附加到实际的---
其实你并不是附加到源代码上,而是附加到你编译后的二进制文件中的符号,这些符号对应于实际的函数。你可以让 eBPF 程序对此作出响应。 这是 Derek 肯定可以阐述的东西。
Johnny Boursiquot:等一下---
我说过我要问一些愚蠢的问题。让我用一个对我来说更简单的方式说你刚才说的内容……
Grant Seltzer Richman:当然。
Johnny Boursiquot:eBPF 就像---
内核就像 HTML,而 eBPF 就像你的 JavaScript……当有人点击 HTML 上的按钮时,你的 JavaScript 可以对该事件的发生作出反应。
Grant Seltzer Richman:完全正确。这是很多人---
我想这个类比最早是 Brendan Gregg 提出的,但这是人们喜欢解释 BPF 的经典方式。这个类比非常好。
Johnny Boursiquot:那它只是一个观察工具吗?你只能监听事件,还是可以更改事情?
Grant Seltzer Richman:不,你实际上可以更改事情,你可以作出响应。当然有一些限制,因为安全性肯定是一个问题……你不希望能够随意在运行的操作系统中放入任何东西,尤其是在生产环境中……但是的,你可以做很多事情。你可以采取行动,阻止某个进程的发生……在网络路由的情况下,你可以按照自己的意愿重新路由数据包……
Johnny Boursiquot:我的各种警觉感官都在作响,不过我们稍后再谈……[笑]
Derek Parker:是的,我见过纯 eBPF 的负载均衡实现,我觉得这非常酷,也非常有趣。而且关于 eBPF 程序本身的另一个有趣之处是---
你基本上用 C 语言编写它们,但这就像是简化版的 C。与典型的 C 编译器作斗争不同,你得和 BTF 验证器作斗争,它会抱怨“你不能在 BPF 程序中使用循环。”你需要非常小心地分配栈空间,因为有非常严格的要求……因为它必须是安全的,毕竟它运行在内核中;即使它是在沙盒中运行,程序仍然必须终止,所以你不能有循环等验证器无法验证程序是否会实际终止的情况。
因此,在编写程序时,为了绕过这些限制,你需要做一些有趣的事情。
Mat Ryer:这些事情是得到内核许可的吗?这是内核明确允许的,还是某种对内核进行的操作?
Grant Seltzer Richman:BPF 确实存在于内核中。它是 Linux 内核的一部分虚拟机,伴随所有系统发布。在加载实际程序以及程序能做什么方面,是有权限体系的。你必须有 root 访问权限,或者加载 BPF 程序的进程要有特定的能力。
Mat Ryer:对。所以你不能对任何内核都这么做。这是一项明确支持的技术。
Grant Seltzer Richman:没错。你不能在 macOS 上这样做。这是特定于 Linux 的。而且所有 Linux 发行版都支持它。还有一些我无法详细说明的事情,不过微软确实有在努力将 BPF 移植到 Windows 上。
Derek Parker:是的,我也听说过很多,但我没有在 Windows 上开发,所以我不清楚它的状态……不过我觉得这很酷,因为---
看到 Linux 中的一些创新传播到其他地方很棒。我真希望我们能在 macOS 上进行更多原生的容器化工作,而不是“哦,我们赶紧安装个 Linux 虚拟机,假装我们在 macOS 上做容器化工作,但实际上并没有。”如果微软真的在内核中采用这种技术,我觉得这很酷,而不是---
我不知道这是否真的是在内核中,还是他们在幕后通过“WSL赶快把你的 eBPF 程序传那边”来做。
Grant Seltzer Richman:我认为它目前是基于用户空间的,但我不想误解……不过我觉得你说的没错。我觉得如果防作弊软件能够通过 BPF 程序检测到发现的新作弊手段,并应用于 Windows 上的游戏,那会非常酷。
Derek Parker:就像是---
叫什么来着,Punk Buster 吧?这是一些大型游戏的防作弊系统……像 Punk Buster eBPF 版。
(译者注: 是一款游戏防作弊软件,通常被玩家简称为PB)
Grant Seltzer Richman:是的。没错。
Johnny Boursiquot:我试图将用户空间、内核空间等概念可视化……对于那些仍在努力理解的人来说---
通常当你和我用我们喜欢的编程语言写程序时,比如 Go,我们写的是用户空间程序。当它们需要在操作系统级别做某些事情时,它们会发出系统调用,比如“嘿,我想打开一个文件。”
对我们来说,开发者是使用标准库。Go 会说“嘿,我想打开这个文件,因为我想读取内容”或其他操作。所以这是一个系统调用,然后操作系统会处理所有的事情,接着返回结果给我们。但这一切都发生在用户空间,对吧?而我们现在讨论的是编写程序的能力,让它们在内核空间中运行。这就深入了一层,实际上能监听、响应,甚至可能更改内核正在做的事情。
Grant Seltzer Richman:完全正确。这样做有很多优势。让我们以系统调用为例,事件的流程是你的 Go 程序尝试写入一个文件;在底层,Go 的标准库使用的是 write 系统调用。在系统调用之前,你的 Go 程序会把所需的信息放到正确的寄存器中,然后执行系统调用指令,内核接管,执行系统调用,返回用户空间,告诉你“嘿,我们成功写入了文件”或其他信息。
在 eBPF 的世界中,你可以写一个 eBPF 程序,它在每次调用系统调用时触发。所以如果你有一个 eBPF 程序,它在每次 write 系统调用被调用时触发……所以在原始事件流程上增加了一步,Go 程序设置系统调用,执行它……就在执行之前,eBPF 程序运行;它可以检查传递给系统调用的所有参数,并做任何它想做的事情……一旦它完成了,系统调用执行,然后返回到用户空间,等等。而 BPF 这一边对实际触发它的应用程序是完全不可见的。
Johnny Boursiquot:有意思。
Mat Ryer:那么它是作为一种后台进程运行在主要任务的旁边,还是一种阻塞代码?当它看到某个调用时,它到底是如何运行的?
Derek Parker:如果探针附加在某个函数或类似的地方,它会阻塞。在那个时刻,触发探针的程序执行将暂停,以便 eBPF 程序可以执行任何需要的检查。这也是为什么在追踪的上下文中---
这是让我对 eBPF 感兴趣的地方---
涉及到系统调用的开销以及追踪的总体开销。
类似的事情也会发生---
我对它感兴趣的原因是我想让 Delve 的追踪后端更高效,减少开销,这样你也许可以在生产环境中使用它。我曾经发过一条推文,当我刚开始研究这个时,我展示了它增加的开销。我有一个程序,运行时间是一些奇怪的微秒数… 然后使用基于 eBPF 的追踪,它从大约 20 微秒增加到了 300-400 微秒左右… 这听起来像是一个显著的开销,但我们谈论的是微秒。
然后我使用 Delve 之前基于 ptrace 的追踪进行了计时,时间增加到了 2.3 秒。所以你从微秒增加到实际的秒,这种开销在生产环境中是不可接受的。这让我对它产生了兴趣,写出一些非常小型、目标明确的程序,它们可以作为某些事件的结果被调用,不会在内核和用户空间之间进行上下文切换,尽可能短的时间内暂停程序,获得高效、详细但又灵活的追踪数据。
另一个难点是如何实现灵活性。很多时候,当人们编写 eBPF 程序时,它们非常有针对性。你在编写函数时已经知道它会附加到哪个对应的函数上;可能是内核函数,或者其他函数……你通常已经知道,所以你大概知道要期待哪些参数。但在 Delve 的情况下,我有点滥用了它,因为我想将探针附加到完全随机的函数上,我不知道它有多少参数或返回值。我对它一无所知,但我想从中获取所有信息。我该怎么做呢?
这引发了很多问题,比如如何编写一个通用的 eBPF 程序,以及如何在不引入我试图消除的慢速问题的前提下,在内核空间和用户空间之间进行通信。
Johnny Boursiquot:你之前提到你使用了一种受限的 C 语言;显然出于性能原因,有些事情是不允许的。那么开发流程是什么样的?如果我要使用 eBPF,我必须用 C 吗?还是有包装器或 SDK?开发流程是什么样的?
Grant Seltzer Richman:eBPF 程序本身,从高层次来看---
实际上就是这样,你有两部分。你有 eBPF 程序本身,然后你有用户空间程序,它将 eBPF 程序加载到内核中,并监听反馈……本质上是与 eBPF 程序交互的代理。
在 BPF 这一侧,我的经验仅限于用 C 编写。我听说过有一个库可以让你用 Rust 编写实际的 BPF 程序,因为它的后端是 LLVM。LLVM 控制着 eBPF 字节码的规范,类似于 Windows 的 BPF,不过我对此一无所知,不想详细讨论……而且 Rust 是竞争语言,所以我们不能讨论它……
Mat Ryer:[笑]
Grant Seltzer Richman:是的,你用 C 编写 BPF 程序,所以大多数情况下你只是在将这些定义在 BPF 世界中的 helper 函数串联起来。它们是定义在头文件中的 BPF helper 函数,使用起来并不复杂。如果你是 Go 开发者,这不会花太长时间让你上手,尤其是看看例子……而且有很多入门指南。
在用户空间这一侧,你可以用多种语言写程序。你可以使用 C 标准库,它叫做 libbpf。还有一个项目叫做 BCC,虽然不推荐使用,但它可以让你使用 Python 或 Go 版本,甚至还有一个旧的、不再维护的 Lua 版本,当然也有 Rust 的版本。用 Go 也有很多不同的库可以使用。我偏好一个叫做 libbpfgo 的库,它是 libbpf 的包装器。还有一个 Go 原生实现,它是 Cilium 项目的一部分……但我维护 libbpfgo 并在我帮助维护的项目中使用它,所以我偏好这个。
Johnny Boursiquot:你有点偏心。
Grant Seltzer Richman:是的,我对此很坦诚。
Johnny Boursiquot:显然你提到这是个 Linux 专属的东西,除了正在进行的 Windows 版本……所以如果我在 Mac 上需要编写这些程序,我得用某种虚拟机来测试和运行它们。
Grant Seltzer Richman:是的,暂时如此。
Derek Parker:我记得以前有 DTrace,用于 Darwin 内核,类似的东西。
Grant Seltzer Richman:是的,macOS 有一个---
我不记得确切名字,但最近版本的 macOS 有一个类似的安全框架,不过它们之间没有互操作性。
Johnny Boursiquot:我们来谈谈使用场景吧。我对 eBPF 程序的最佳应用场景很感兴趣。我们谈到过可观察性,知道某些事件何时发生,Derek 提到这是一个非常有针对性的工具,对吧?所以你已经知道想要获取哪些系统调用的回调,除了你想获取所有信息的情况……我猜这种情况下写这些程序的方式会非常不同,而不是只是寻找某个文件是否被打开,或者类似的事情。
所以我很好奇使用场景……我听说过可观察性,似乎是个不错的用例……我还听说过网络故障排查、写负载均衡器的情况,我对这方面尤其感兴趣……eBPF 能帮你解决哪些问题?你在解决哪些问题?
Grant Seltzer Richman:当然。我最常用的场景是安全性。我帮助维护一个叫做 Tracee 的项目,它挂钩到数百个不同的事件,试图关联所有这些在内核中发生的事情,以确定是否有入侵或恶意软件……它允许你在上面应用策略,做一些很酷的事情,比如当程序或进程被执行时,尝试捕获实际运行的二进制文件以供以后检查……你可以做很多安全方面的事情。
可观察性---
你可以在生产中使用 BPF 来确定你的 web 服务的健康状况。你可以附加到网络套接字或某种网络机制上……BPF 支持多种机制,以确定丢包数量或数据包的路由位置;你可以通过这种方式获取大量信息。
Johnny Boursiquot:我们谈论的这种可观察性,和我们现在常用的术语“可观察性”有些不同。我们通常想到的可观察性是“我需要一个仪表盘,我需要 Honeycomb 或 DataDog,看我的服务是否运行,延迟是多少”等等。而我们现在谈论的是一种不同层级的可观察性,没错吧?
Grant Seltzer Richman: 是的,确实不同。在大多数情况下,甚至可以说所有情况下,你可以访问到原始内存。你可以看到数据包的全部内容,或者用户空间程序的全部内存内容。不过即便你不检查内存,你仍然可以让这些 BPF 程序触发并报告“嘿,发生了这个事”,就像你在 Go 程序中添加一行代码,然后重新编译并运行它一样……比如 println,而不是编辑源代码并重新编译,你可以添加一个 BPF 程序,附加到内存中的某个地方,看看某行代码何时执行。这就是 Delve 所做的。
Mat Ryer:那么我们需要自己编写所有这些东西吗?是否已经有一些现成的工具?有没有工具可以监控内存分配,比如收集到 Prometheus 中并显示在仪表盘上?有没有现成的工具在围绕这个技术发展?是否有一个生态系统?
Derek Parker:是的,我知道有一些工具---
从系统管理员的角度来看,Grant 之前提到的 Brendan Gregg,来自 Netflix,他是 DevOps 的专家……他有一整套基于 eBPF 的工具、脚本和单行代码,可以用来检查系统。我记得他有一篇很棒的博客,介绍如何在五分钟内调试生产问题,里面也提到了很多基于 eBPF 的脚本和工具。
但我觉得你提到的问题是这些工具的产品化,以及如何将它们集成到指标收集系统中……我知道在这个领域有很多努力正在进行。
Mat Ryer:是的,这很有趣。那么当你说在生产环境中运行这些东西时,是不是需要提前计划、启用、构建功能?还是可以直接附加到正在运行的进程上?因为它是在内核中;几乎在所有进程的底层。
Derek Parker:是的。我认为大多数准备工作在于确保你有一个可以加载这些程序的内核,我认为只要你运行的是现代内核,应该没有问题。但在用户空间程序方面,你不需要做任何协调。你只需要与内核协调,并让你正在运行的程序加载 eBPF 程序。但你不需要与用户空间程序进行协调。对于 Delve 或类似的工具来说,这就像是一个普通的调试会话,我们只是请求内核允许我们做一些事情,程序不需要参与决策。
Mat Ryer:是的,这很有趣。这对于调试或任何类型的检查都非常有用;你几乎不需要额外运行任何东西……我可以理解为什么会有一系列单行代码工具,因为这在工具箱中非常有用。非常有趣。我们会尝试找到它并把链接放在节目笔记中。这听起来非常有趣。至少我们可以看到一些真实的 eBPF 程序示例。
Grant Seltzer Richman:是的。我还想补充一下,关于之前的问题,关于生态系统……我会说,如果这项技术让你兴奋,或者说拥有这种可见性让你兴奋,但你可能会被吓到,甚至不想自己编写 eBPF 代码,实际上有一个正在发展和成熟的生态系统围绕着这个技术。许多产品正在开发中,以获得这种可见性。
另外,你完全不需要重新编译代码,这对 SRE 或安全人员来说非常好用。你可以运行服务并在不重启服务的情况下,编写 BPF 程序,检查不同的内存区域,这非常有价值。
Mat Ryer:那么稍微高层一点的呢?比如你是一个 web 开发者,想实现文件监控,自动重载的功能。你能不能写一个 eBPF 程序监控某个路径下的文件变化,然后采取行动,提醒你刷新?
Grant Seltzer Richman:是的,绝对可以。我觉得 Derek 说得很好,Brendan Gregg 有很多工具可以非常出色地完成非常特定的任务。我记得有一个工具叫 OpenSnoop,它可以告诉你每次文件被打开时的信息。或者一个更强大的工具……我再一次推荐我参与的项目 Tracee,你可以运行它并获得所有你想要的信息,还可以过滤不同的事件,而无需编写 eBPF 代码。
Mat Ryer:那就是 Tracee,对吧?
Grant Seltzer Richman:没错,它在 Aqua Security 的 GitHub 上。
Johnny Boursiquot:顺便提一下,你和 Liz Rice 一起工作,对吧?
Grant Seltzer Richman:我曾经和她一起工作。她在我加入后不久就离开了,很遗憾……
Johnny Boursiquot:[笑] 我本来想问你和一位摇滚明星(我用了一个敏感词)……一位我们社区中知名且受人尊敬的成员一起工作是什么感觉?[笑]
Mat Ryer:……她还玩摇滚。
Johnny Boursiquot:她还玩摇滚。[笑声]
Grant Seltzer Richman:当她在的时候感觉很棒。我在社区里仍然经常与她互动。她为 eBPF 社区做了很多工作,所以我仍然常与她合作。
Mat Ryer:你是说她在你加入后就离开了,还是之前?
Grant Seltzer Richman:在我加入后不久。我可能把她吓跑了……[笑声]
Mat Ryer:很可疑。这是个可能性,是吧?我就是这么想的。
Derek Parker:Johnny,你之前提到对我提到的基于 eBPF 的负载均衡感兴趣……
Johnny Boursiquot:是的。
Derek Parker:我知道 Liz Rice 做过一次关于如何实现负载均衡的非常好的演讲。如果你感兴趣,我强烈推荐你去找找她的演讲,真的非常棒。
Johnny Boursiquot:我会去找的,谢谢你。
Mat Ryer:很酷,我们也会找到并把它放在节目笔记中。
Grant Seltzer Richman:eBPF 周围有一个很棒的社区。虽然有很多东西需要学习,也有很多让人困惑的地方,但整个生态系统真的在不断发展,变得越来越容易接触……有很多人对它感到兴奋,并且乐于提供帮助。如果你访问 eBPF.io,那上面有一个 Slack 频道,你可以加入,非常有帮助。而且有很多相关的演讲……Derek 和我即将参加一个会议发表演讲。学习资料可以说是丰富多样。
Mat Ryer:是的。那么这个社区主要在哪里?是不是都集中在那个 Slack 频道里?如果有人想参与,eBPF 的社区还存在于哪些地方?
Derek Parker:我认为 eBPF 的技术,尤其是在云原生和 CNCF(云原生计算基金会)领域非常流行;云原生、Kubernetes 生态系统是社区主要的聚集地。此外,它也涉足了一些编程语言社区,针对那些想要实现相关功能的人们。但总体而言,大部分兴趣和社区活动都集中在云原生领域。
Johnny Boursiquot: 我在短暂了解 eBPF 的过程中,发现大多数示例似乎都围绕着 BCC(BPF Compiler Collection),主要用 Python 编写。我看过一些这样的例子,当时我心里想,“好吧,我们在这里写了一些 Python 代码,然后在某个地方插入了一大段 C 代码……” 我们可以看到代码中的钩子,但这让我回想起之前提到的开发体验。我很好奇,在 Go 中编写这些程序的体验是什么样的?在 Go 生态系统中,你通常用哪些库来与这些程序交互并编写它们?
Derek Parker:我先来回答这个问题……我也要为 libbpfgo 框架做个宣传,因为我在 Delve 中使用它来实现基于 eBPF 的追踪后端。对于编写和加载 eBPF 程序并将其应用到 Go 程序中,已经有非常不错的工具。不过在某些 eBPF 特性上与 Go 结合时会遇到一些挑战……回到正题,毕竟这是 Go Time 播客……例如,当你在 Go 程序中使用探针时可能会遇到一些棘手的情况,有两种探针。对于用户空间探针,有 uprobes 和 uretprobes。uprobes 可以附加到函数的入口点,而 uretprobes 则附加到函数的返回点。因此,你可以在函数入口和返回时分别进行钩取。
但这在 Go 中非常棘手,因为 uretprobes 的工作机制是修改一些数据,比如 goroutine 栈上的某些地址。如果你不熟悉,goroutine 的栈通常非常小,并会随着时间增长。而在栈增长过程中,Go 运行时需要遍历栈中的指针,移动它们并进行更新等操作。
所以,如果你不小心使用 uretprobes,可能会导致 Go 程序崩溃,因为当 Go 运行时试图复制栈时,会发现一个它无法识别的地址,然后程序就会崩溃。
在 Delve 中,我们不得不做一些非常巧妙的事情,用 ptrace 监控 Go 运行时何时准备复制栈,然后临时取消 uretprobe,让它完成操作后再重新附加探针。因此,涉及到 Go 运行时时需要多加小心。对于带有运行时的语言,或者像 Go 这种自检查的语言,使用 uretprobes 可能会带来一些奇怪的问题,这也是你在做这些低级别探查时需要注意的地方。
Mat Ryer:是的,我相信遇到这种崩溃会非常奇怪。如果你有一个小型的 eBPF 程序,它可能会发出一些有趣的信息,例如统计内存分配之类的,那你该如何提取这些信息呢?首先,这些信息会存储在哪里?eBPF 程序有自己的内存吗?Go 程序如何获取这些信息?
Grant Seltzer Richman:当然。也许我们在讨论 BPF 程序时遗漏了一个关键点,那就是 BPF 程序到底能做些什么。BPF 程序主要与各种形式的映射(map)交互;就像 Go 中的 map 一样,BPF 中也有不同类型的映射,你可以用它们来存储信息。这些 map 可以在用户空间和内核空间之间共享,或者在多个 BPF 程序之间共享。
例如,你可以有一个环形缓冲区(ring buffer),假设你有一个简单的 BPF 程序,它在每次某个函数被调用时触发,或者每次触发一个系统调用。在这个 BPF 程序中,你可以创建一个小消息,比如“系统调用被触发了”,然后将这个消息放入一个字符串中,使用环形缓冲区发送到用户空间。在用户空间中,你可以有一个 goroutine 来监听这些事件并将它们打印到屏幕上。这些缓冲区和映射使得在用户空间和 BPF 程序之间可以共享内存。
Mat Ryer:那么在 Go 端能否获得一个通道(channel)接口,可以通过 for range
来读取这些内容?
Grant Seltzer Richman:我会说是的,但这取决于你使用的库。实际上,底层的原语是不同的接口,但在使用 libbpfgo 的情况下,你确实可以得到一个通道。所以你可以像与其他 Go 程序一样与它交互。
Mat Ryer:这也包括发送数据吗?
Grant Seltzer Richman:发送数据有点不同,因为你是更新共享映射中的值。虽然有一个接口或 API 可以做到这一点,但环形缓冲区更适合从 BPF 向用户空间发送数据。
Mat Ryer:我明白了。所以这些 map 就像对象一样,它们是键值对。内核是否已经有了这种概念?或者这是 eBPF 模型化出来的?
Grant Seltzer Richman:不深入讨论不同特性的话---
因为有很多我也不太了解的东西---
eBPF 提供了对内核的可见性,这个概念并不新鲜,但它确实让事情变得更简单。
以前,可能你需要编写一个内核模块来实现的功能,那些模块没有太多安全保证,并且要求你重启,甚至有时候需要重新编译 Linux 内核,然后重启,整个过程耗时很长。而 BPF 让这一切变得更快、更安全,并且更容易接触。
Mat Ryer:是的,作为一个 Go 程序员,能够通过一个通道接收关于系统内部详细信息的实时数据,光是这种机制就足够让人兴奋了。因为谁知道你能构建出什么样的东西呢……我刚想到的一个用例是文件监控,但我相信,如果你能深入了解内核中的实际情况,可能会有很多其他的应用场景。
Grant Seltzer Richman:可能性是无穷的! [笑声]
Mat Ryer:是的,我对此感到非常兴奋。我们已经听说了很多关于 libbpfgo 的内容,我们一定会提供相关链接。我在查看仓库时,发现它确实提供了一个不错的 API。很有趣的是,即使使用这个库,我是否也可能会遇到我们之前提到的那些崩溃问题?如果我要编写这样的代码,是不是应该避免使用 goroutine?
Derek Parker:如果你要在 Go 中使用 eBPF,我唯一建议避免的是 uretprobes,除非你非常非常清楚自己在做什么……因为几乎 100% 的情况下,它会导致你的程序崩溃。让它正常工作的唯一方法是做一些我们在 Delve 中做的那种奇怪的事情,这是一种非常复杂的小技巧。
Mat Ryer:这个技巧能不能打包成一个库呢?这个小技巧能不能一次解决所有问题?
Derek Parker:这是有可能的……实际上,这个问题在 Delve 中的一个待处理的拉取请求里已经得到了解决。实现这个解决方案需要涉及很多东西,比如 Dwarf 知识(即二进制文件中的调试信息),使用 ptrace 并获得使用 ptrace 的权限,还要结合 eBPF……有很多组合在一起的东西,这些都不是典型的 Go 编程体验。所以这里确实有一些复杂的地方……但总的来说,除了 uretprobes,其他功能都可以安全地与 Go 一起使用。不过如果你使用 uretprobes,几乎肯定会导致问题。
Mat Ryer:它们通常会用来做什么?
Johnny Boursiquot:捕捉函数的返回值。
Derek Parker:对。
Mat Ryer:我明白了。所以如果你只需要读取数据,可以使用环形缓冲区之类的东西来实现。
Derek Parker:是的,从程序内部,uprobe 会触发并开始执行你的 eBPF 程序,而你的 eBPF 程序可以使用环形缓冲区或映射与用户空间通信。
Delve 同时使用了两者。它使用映射从用户空间向 eBPF 程序传递信息,然后使用环形缓冲区将 eBPF 程序生成的数据发送回 Delve。
Mat Ryer:我明白了。这真的很有趣。当我想到 Delve 时,我觉得它是一个非常底层的工具,因为我通常处理的是更大型的系统。但每次我深入研究时,我都会发现相似的层次结构,通常这些架构会更加复杂……我总是觉得这非常有趣。
Johnny Boursiquot:简单并不容易,伙计……
Mat Ryer:是的,绝对如此。
Mat Ryer:我想问一下,大家觉得 eBPF 的未来会怎样?我们是不是感觉这只是一个开始,未来会越来越令人兴奋?
Johnny Boursiquot:商业产品……这是下一个方向。商业产品。 [笑声]
Mat Ryer:这是下一步的发展吗?我们现在就可以开始创业了。我们四个。 [笑声] 就在 Go Time 上直播创业。不是 The Go Time,对吧?应该叫 Go Time。我刚才做了个像 The Facebook 那样的事。不过我觉得现在叫 The Go Time 反而更酷。
Johnny Boursiquot:The Go Time。
Mat Ryer:你懂我的意思吗?感觉我们已经绕了一圈。不过如果我们要创业,公司会是什么样子的?
Grant Seltzer Richman:我肯定会说这个生态系统正在成熟,或者说刚刚开始成熟,但还有很多用例尚未被发掘。libbpf 还没有达到 1.0 版本。我觉得现在有很多人正在进入这个社区,他们正在学习 BPF……内核这边也有很多讨论,关于 BPF 正在吞噬 Linux 的说法,甚至有人在讨论用 BPF 代码重写 Linux 内核的大部分内容,使其更加模块化。
比如调度器(scheduler),我们可以动态地将逻辑放入调度器中,改变我们调度进程的方式。当然,驱动程序也是另一个人们正在考虑的领域……但从一个更高的视角来看---
我不想太过“思想领袖化”,但……
Johnny Boursiquot:请继续。请继续。
Grant Seltzer Richman:我忘了是谁在演讲中提到过这个观点,BPF 代表了一种新的软件范式,它让你能够动态改变软件与操作系统的交互方式。很难说 BPF 的未来会走向何方,因为 1)还有无数新的想法可以将 BPF 程序附加到不同的地方;2)有很多新的人才正在进入社区,带来了很多好点子,新的贡献者层出不穷……总的来说,这些想法几乎没有受到什么限制。就像问“Go 的下一个大事件是什么”,或者“你可以用 Go 写什么”一样,答案几乎是“任何东西”。不过也许 BPF 更酷一些。
Mat Ryer:好吧,这就引出了我们的常规环节……是时候进入“不受欢迎的意见”环节了。
Mat Ryer:Grant Seltzer,你有什么不受欢迎的意见要分享吗?
Grant Seltzer Richman:有的。我还想提一下,我可是这个环节的冠军……
Mat Ryer:真的吗?
Grant Seltzer Richman:我在这个节目上发表过有史以来最不受欢迎的意见。
Mat Ryer:是吗?你是说 eBPF 比 Go 更酷的时候吗? [笑声] 你打破了自己的记录。
Grant Seltzer Richman:我当时好像说的是关于棒球的。
Johnny Boursiquot:你想换个不受欢迎的意见吗?
Grant Seltzer Richman:不,我仍然认为棒球是最好的运动。但这次我不会试图超越自己。我不会说任何政治性的东西……不过我想说,我一直以来的观点是,工程团队里每个小组都应该有一个安全工程师。
Mat Ryer:真的吗?
Grant Seltzer Richman:我认为很多软件开发者,甚至是负责架构整个系统的人,在做决定时,如果能够有一个安全工程师的意见,或者是一个对安全有更多培训的开发人员的意见,对于整个组织的安全性会有非常大的帮助,而不是仅仅依赖一个在旁边的安全团队,他们只能对现有的基础设施抛出一些产品。
Johnny Boursiquot:所以在你看来,这与在产品发布前进行的一次安全审查不同?你说的是在我们构建软件时,团队中就应该有一个安全人员。
Grant Seltzer Richman:是的。
Mat Ryer:这有点像测试。以前测试几乎是独立于软件开发的一个环节,后来我们成为了测试驱动开发者,意识到编写良好测试代码是我们的责任……我们是不是正在走向一种“安全驱动开发”(Security-Driven Development,SDD)的模式?
Johnny Boursiquot:SDD。哦,我还以为你在说别的…… [笑声]
Mat Ryer:不,-驱动开发。DD。
Johnny Boursiquot:明白了。
Mat Ryer: 这真的很难做到,不知道为什么。写下来。我想我做到了。我觉得我们可以在剪辑中修复它。[笑声] 如果大家都同意的话,我很乐意继续下一个话题。
Derek Parker: 那样最理想了。
Mat Ryer: 设计系统时,安全性确实是你需要考虑的关键问题之一。你说得对,有时候你只需做一些设计决策就可以让系统更加稳健。例如,如果系统是幂等的,你可以多次重试某个操作,采取“宁可安全也不冒险”的原则,因为系统的设计方式确保不会因为重试而出问题。同样的做法也适用于安全问题。通过做出某些设计选择,你自然会让系统更加安全。所以,这确实是个有趣的话题……我们一定要在 Twitter 上测试这个观点。虽然我们也可以在 Facebook 上测试,但我想我们都知道为什么不这样做……
Grant Seltzer Richman: 哈哈。
Mat Ryer: 因为我们永远也看不到测试结果……
Derek Parker: BGP,和 BPF 不同。
Johnny Boursiquot: [笑声]
Mat Ryer: 哦,是吗?这就是我们做这个节目的原因……[笑声] 哦,不……!
Johnny Boursiquot: 这个澄清很及时,的确如此。
Mat Ryer: 我犯了个错误。好吧,非常有趣……Johnny,你怎么看?你觉得团队里有安全专家是什么感觉?
Johnny Boursiquot: 我并不反对。很难反对这个观点。
Mat Ryer: 说实话,很难反对任何关于安全的观点。你不能成为那个在房间里说“我觉得我们应该少关注安全”的人。[笑声] Derek,今天你有什么不受欢迎的观点吗?
Derek Parker: 有的。上次我没有发表什么观点,我紧张了。但这次我有一个。虽然没有 Grant 的观点那么发人深省。
Mat Ryer: 也没有那么戏剧性,毕竟你这次没紧张得说不出话来。[笑声] 这个环节的戏剧性正是来源于大家的紧张感。
Derek Parker: 最近我在写 Go 代码和 eBPF 代码之间来回切换,这让我回到了很多 C 语言的编程中。所以我的不受欢迎的观点是---
蛇形命名法比驼峰命名法更好。[笑]
Johnny Boursiquot: 哇哦。
Mat Ryer: 那么,对于那些不熟悉这两种命名法的人来说,能解释一下它们的区别吗?
Derek Parker: 蛇形命名法是这样的:word_another_word_another_word
,而驼峰命名法是这样的:wordAnotherWord
……Go 语言通常使用驼峰命名法,而 C 或 Rust 通常使用蛇形命名法。我个人觉得蛇形命名法看起来更好,也更易读。我不知道,驼峰命名法总给人一种词语混在一起的感觉,而蛇形命名法看起来更像一个句子……我觉得它看起来更好。
Mat Ryer: 这个观点挺有意思的。那在 Twitter 上的标签(hashtag)呢?你会用蛇形命名法来写标签吗?
Derek Parker: 我通常都全用小写。我会把字符串转成小写后写标签。
Mat Ryer: 把字符串转成小写。
Derek Parker: 对。
Mat Ryer: 那你会用 eBPF 来处理这个问题吗?比如在发推之前改掉它?[笑声] 不过据说这其实是一个无障碍访问的问题,全用小写对屏幕阅读器来说不太友好。我之前在电脑上故意输入很多无意义的东西,让电脑读出来,我玩了好几个小时。
Derek Parker: 是的,是的。
Mat Ryer: 是的,基本上就是在模糊测试 say
命令。
Derek Parker: 确实,SSH 到别人的电脑上,然后用 say
命令输入一些随机的内容,乐趣无穷。[笑]
Mat Ryer: 哦,是的,确实有趣。
Derek Parker: 但通常我会保持单词简短,用一个词的标签,这样希望不会影响可读性。
Mat Ryer: 但蛇形标签会解决这个问题,不是吗?
Derek Parker: 没错。
Johnny Boursiquot: 虽然看起来有点可疑,但的确如此……
Derek Parker: [笑声]
Mat Ryer: 看起来是有点奇怪。我以前用 Ruby 编程时,标准输入(STDIN)也使用蛇形命名法,比如下划线分隔词汇……是啊,我也不知道。
Johnny Boursiquot: 俗话说,“入乡随俗。”
Grant Seltzer Richman: 有一年在 GopherCon 上有一个非常有趣的演讲,讲的是如何编写更易于访问的 Go 代码,或者说通用代码,其中的一部分就是让代码更容易被屏幕阅读器读取。当 Derek 提出他的观点时,我就在想这个问题。我觉得蛇形命名法可能比驼峰命名法更容易让屏幕阅读器读取。
Mat Ryer: 是的,可能是这样吧。不过 Derek,你的观点能走到多远?你会给你的孩子起蛇形命名的名字吗?
Johnny Boursiquot: 你还在考虑这个问题吗?[笑声]
Derek Parker: 是的,确实是这样。我最小的孩子叫 Davie_,你知道的……[笑]
Mat Ryer: 我真迫不及待想见到第一个给孩子起这种名字的工程师,比如名字中带下划线之类的。我会非常喜欢。
Derek Parker: [笑]
Mat Ryer: 这个观点不错。那么,Johnny,你接受这个观点了吗?
Johnny Boursiquot: 我做过一些 Ruby 编程,所以我对下划线的可读性非常熟悉,但既然这是 Go Time 播客,我们谈论的是 Go,我得说“不,我不喜欢这个观点。”
Derek Parker: [笑声]
Mat Ryer: 但 Derek,你在写 Go 代码时会用下划线吗?
Derek Parker: 不会……
Mat Ryer: 你真的会在 Go 代码里用下划线命名吗?
Derek Parker: 不会,不会。我还没那么怪。
Johnny Boursiquot: 他知道该怎么做。
Mat Ryer: 那如果你真的这么做了,代码会变成什么样子?会有多糟?
Derek Parker: 我觉得问题出在大写的蛇形命名法让我觉得非常别扭。它在 Go 里行不通,因为大写字母表示导出功能---
比如蛇形命名中的首字母大写……这就完全不对劲了。这不对劲。
Mat Ryer: 所有的东西在成为“标准做法”前看起来都不对。
Derek Parker: 对,没错。[笑]
Mat Ryer: 这就是一种趋势。其实没有什么问题。我现在就去试试看,看看是什么样子。嗯,我感觉不太好。我感觉有点恶心。太糟糕了。对……不过这个观点确实不错。我喜欢这种观点。Johnny,你最近有不受欢迎的观点吗?
Johnny Boursiquot: 我的不受欢迎的观点就是,我永远也想不出一个不受欢迎的观点。
Mat Ryer: 是啊,我知道。因为你太受欢迎了。
Johnny Boursiquot: 每个观点最终都会变得很受欢迎,所以我厌倦了不受欢迎的观点,因为它们总是变得很受欢迎。
Mat Ryer: 这太有深意了。你不受欢迎的观点似乎是“我们不应该继续这个环节。”[笑声] 有趣的是,当我们把这些观点发到 Twitter 上时---
我真的认为 Grant 是记录保持者,因为……大多数时候,人们会同意这些观点。通常,大家的论点都很有力。
Johnny Boursiquot: 现在我更关注的是有多少人持不同意见的比例。我试图弄清楚究竟有多少人倾向于某个观点。这比去说“哦,这个观点非常不受欢迎”更有趣,因为这种情况不常发生。
Mat Ryer: 是的,确实不常发生。这非常有趣。好的,我们快结束了。我想快速做一个“喊出”(shout-at)环节……这就像“喊出”(shout-out),但我第一次做的时候说错了……
Johnny Boursiquot: 是的,你是在喊着叫。
Mat Ryer: 所以现在变成了“喊出”(shout-at)……我们要“喊出”一个特定的线下活动。今天我要“喊出”的是 Meetup.com 上的 GDN 页面。网址是 meetup.com/pro/go。在那里你可以找到很多资源、本地线下活动、附近的 Gopher 社区,你可以去认识并交流……谁知道呢?你可能会在那里找到对 eBPF 感兴趣的人,然后你们可以一起讨论并加入进来。看看你能否构建一些很酷的东西。
Johnny Boursiquot: 对于那些不知道的人来说,GDN 代表 Go Developer Network(Go 开发者网络)。这是所有 Go 线下活动和事件背后的元组织,甚至包括 GoBridge 等等。它是幕后团队的幕后团队。
Mat Ryer: 是的,它有很多成员。截至目前有 117,000 名成员……你可以成为其中一员;如果你已经是其中一员,你可以让这个数字再增加一个。
Johnny Boursiquot: 是的,加入 GDN,我们会把你的会员证信息邮寄给你。开玩笑的,没有会员证。
Mat Ryer: 我刚才还很兴奋呢。我在想“我要加入这个组织。”
Johnny Boursiquot: [笑] 我们不是发放执照的组织。
Mat Ryer: 哎……这样还有什么意思?我本来想加入的。如果没有徽章……
Johnny Boursiquot: 你想要证书吗?
Mat Ryer: 一个小银色的 Gopher 徽章,你可以到处炫耀,还能享受半价优惠。
Johnny Boursiquot: 我可以私信你我的地址,你可以给我寄张支票……
Mat Ryer: 好吧。那你会给我做个 Gopher 警徽吗?
Johnny Boursiquot: 是的,我会给你画个涂鸦,然后寄给你。
Mat Ryer: 它上面会写“治安官 Mat”吗?[笑声]
Johnny Boursiquot: 你可以做治安官,Mat。你可以做治安官。
Mat Ryer: 我们应该这样做。我们应该在社区里设立等级制度。
Johnny Boursiquot: 等级制度?我们在哪里?[笑声]
Mat Ryer: 是的,是的,现在就像警察部队一样。
Derek Parker: Go 开发者部队,对吧。
Mat Ryer: 对。现在就像政府机构一样。“Mat Ryer,GDN。” 这听起来像一个新闻组织。Johnny,这是它的主页吗?你似乎对此很了解……
Johnny Boursiquot: 是的,这是入门页面。
Mat Ryer: 你怎么知道这么多?
Johnny Boursiquot: 哦……我认识一些认识一些人的人。
Mat Ryer: 听起来像是个可疑的组织。[笑声] “我认识一些认识一些人的人……” 就像,“哦,你这 GitHub 账号不错。真是可惜,如果它出了什么事……” [笑声]
好吧,今天的时间差不多了。希望你喜欢这次关于 eBPF 的深度探讨。非常技术性,也非常有趣……而且相当令人兴奋。我确实想看看 Gopher 们将用它构建什么。我认为这里有一些令人兴奋的机会。如果你用它构建了什么有趣的东西,请发推告诉我们 @GoTimeFM,我们很想听到你的故事。
非常感谢今天的嘉宾。Derek Parker,Grant Seltzer---
总是很高兴和你们一起交流。你们一定要再来。当然,还有 Johnny Boursiquot……还有我。就这样吧。再见!
这可能不是我最专业的结尾,但……[笑声] 就这样吧。现在我们要播放结束曲了。
Johnny Boursiquot: 是的。
Mat Ryer: 再见……
Johnny Boursiquot: 再见。
更多参考:
libbpf
libbpf/libbpf
深入浅出 eBPF 安全项目 Tracee
bcc 之opensnoop 工具的使用