目录
什么是eBPF?
Linux内核一直是实现监视/可观察性,网络和安全性的理想场所。不幸的是,这通常是不切实际的,因为它需要更改内核源代码或加载内核模块,并导致彼此堆叠的抽象层。eBPF是一项革命性的技术,可以在Linux内核中运行沙盒程序,而无需更改内核源代码或加载内核模块。通过使Linux内核可编程,基础架构软件可以利用现有的层,使其更加智能和功能丰富,而无需继续为系统增加额外的复杂性。
eBPF导致在网络,安全性,应用程序性能分析/性能跟踪和性能故障排除等领域开发了新一代的工具,这些工具不再依赖现有的内核功能,而是在不影响执行效率或安全性的情况下主动重新编程运行时行为。
安全
在看到和理解所有系统调用并将其与所有网络操作的数据包和套接字级别视图相结合的基础上,可以采用革命性的新方法来保护系统。尽管系统调用筛选,网络级筛选和流程上下文跟踪的各个方面通常由完全独立的系统处理,但eBPF允许将各个方面的可见性和控制相结合,以创建在更多上下文和更好控制级别上运行的安全系统。
跟踪和分析
将eBPF程序附加到跟踪点以及内核和用户应用程序探测点的能力,使人们对应用程序和系统本身的运行时行为具有空前的可见性。通过为应用程序端和系统端提供自省功能,可以将两种视图组合在一起,从而提供强大而独特的见解来对系统性能问题进行故障排除。先进的统计数据结构允许以有效的方式提取有意义的可见性数据,而无需像通常由类似系统完成的那样导出大量采样数据。
联网
可编程性和效率的结合使eBPF自然满足网络解决方案的所有分组处理需求。eBPF的可编程性使您能够添加其他协议解析器,并轻松对任何转发逻辑进行编程以满足不断变化的需求,而无需离开Linux内核的数据包处理上下文。JIT编译器提供的效率提供了与本机编译的内核内代码接近的执行性能。
可观察性与监控
eBPF不再依赖于操作系统公开的静态计数器和计量器,而是启用了自定义指标的收集和内核聚合以及基于各种可能来源的可见性事件的生成。通过仅收集所需的可见性数据并通过在事件源处生成直方图和类似的数据结构,而不是依赖于样本的导出,这扩展了可以实现的可见性深度,并显着减少了整个系统的开销。
什么是eBPF.io?
eBPF.io是每个人都可以在eBPF主题上进行学习和协作的地方。eBPF是一个开放社区,每个人都可以参与和共享。无论您是想阅读eBPF的入门指南,查找更多的阅读材料,还是迈出成为主要eBPF项目贡献者的第一步,eBPF.io都会为您提供帮助。
eBPF简介
以下各章是对eBPF的快速介绍。如果您想了解有关eBPF的更多信息,请参阅《eBPF和XDP参考指南》。无论您是要构建eBPF程序的开发人员,还是对利用eBPF的解决方案感兴趣的人员,了解基本概念和体系结构都是很有用的。
挂钩概述
eBPF程序是事件驱动的,并在内核或应用程序通过某个挂钩点时运行。预定义的挂钩包括系统调用,函数进入/退出,内核跟踪点,网络事件以及其他几个。
如果针对特定需要不存在预定义的挂钩,则可以创建内核探针(kprobe)或用户探针(uprobe),以将eBPF程序附加到内核或用户应用程序中的几乎任何位置。
eBPF程序如何编写?
在很多情况下,不是直接使用eBPF,而是通过Cilium,bcc或bpftrace等项目间接使用eBPF,这些项目在eBPF之上提供了抽象,并且不需要直接编写程序,而是提供了指定基于意图的定义的功能,然后使用eBPF实施。
如果不存在更高级别的抽象,则需要直接编写程序。Linux内核希望eBPF程序以字节码的形式加载。虽然当然可以直接编写字节码,但更常见的开发实践是利用LLVM等编译器套件将伪C代码编译为eBPF字节码。
加载程序和验证架构
确定了所需的钩子后,可以使用bpf系统调用将eBPF程序加载到Linux内核中。通常使用可用的eBPF库之一完成此操作。下一节将介绍可用的开发工具链。
当程序被加载到Linux内核中时,在连接到请求的钩子之前,它需要经过两个步骤:
1.验证
验证步骤可确保eBPF程序安全运行。它验证程序是否满足几个条件,例如:
- 加载eBPF程序的过程包含所需的功能(特权)。除非启用了非特权eBPF,否则只有特权进程才能加载eBPF程序。
- 该程序不会崩溃或以其他方式损害系统。
- 程序始终运行到完成状态(即程序不会永远处于循环状态,从而阻止了进一步的处理)。
2.JIT编译
即时(JIT)编译步骤将程序的通用字节码转换为特定于机器的指令集,以优化程序的执行速度。这使得eBPF程序的运行效率与本地编译的内核代码或作为内核模块加载的代码一样有效。
地图
eBPF程序的重要方面是共享收集的信息和存储状态的能力。为此,eBPF程序可以利用eBPF映射的概念来存储和检索各种数据结构中的数据。可以通过系统调用从eBPF程序以及用户空间中的应用程序访问eBPF映射。
以下是不完整的受支持地图类型列表,以帮助您理解数据结构的多样性。对于各种映射类型,可以使用共享版本和每个CPU版本。
- 哈希表,数组
- LRU(最近最少使用)
- 环形缓冲区
- 堆栈跟踪
- LPM(最长前缀匹配)
- ...
辅助呼叫
eBPF程序无法调用任意内核功能。允许这样做会将eBPF程序绑定到特定的内核版本,并使程序的兼容性复杂化。相反,eBPF程序可以将函数调用到辅助函数中,该函数是内核提供的众所周知且稳定的API。
可用的帮助程序调用集在不断发展。可用的帮助程序调用的示例:
- 产生随机数
- 获取当前时间和日期
- eBPF地图访问
- 获取进程/ cgroup上下文
- 处理网络数据包和转发逻辑
尾部和函数调用
eBPF程序可与尾部和函数调用的概念组合。函数调用允许在eBPF程序中定义和调用函数。尾部调用可以调用并执行另一个eBPF程序并替换执行上下文,类似于execve()系统调用对常规进程的操作方式。
eBPF安全
有了强大的力量,就还必须承担巨大的责任。
eBPF是一项非常强大的技术,现在运行在许多关键软件基础架构组件的核心。在eBPF的开发过程中,考虑将eBPF包含在Linux内核中时,eBPF的安全性是最关键的方面。eBPF安全性可通过以下几层来确保:
所需特权
除非启用了非特权eBPF,否则所有打算将eBPF程序加载到Linux内核中的进程都必须在特权模式(root)下运行,或者需要功能CAP_BPF。这意味着不受信任的程序无法加载eBPF程序。
如果启用了非特权eBPF,则非特权进程可以加载某些eBPF程序,但这些程序的功能集有所减少,并且对内核的访问受到限制。
验证者
如果允许进程加载eBPF程序,则所有程序仍将通过eBPF验证程序。eBPF验证程序可确保程序本身的安全。例如,这意味着:
- 程序经过验证以确保它们始终运行到完成状态,例如,eBPF程序可能永远不会阻塞或永远处于循环中。eBPF程序可能包含所谓的有界循环,但是只有在验证者可以确保该循环包含一定可以保证为真的退出条件的情况下,程序才被接受。
- 程序不得使用任何未初始化的变量或超出范围访问内存。
- 程序必须符合系统的大小要求。无法加载任意大的eBPF程序。
- 程序必须具有有限的复杂性。验证者将评估所有可能的执行路径,并且必须能够在配置的复杂度上限内来完成分析。
硬化
成功完成验证后,eBPF程序将根据是从特权进程还是非特权进程加载的程序来执行强化过程。此步骤包括:
- 程序执行保护:存放eBPF程序的内核内存受到保护,并变为只读状态。如果由于某种原因(无论是内核错误还是恶意操纵)而试图修改eBPF程序,则内核将崩溃,而不是允许其继续执行损坏/操纵的程序。
- 缓解Spectre的风险:在推测下,CPU可能会错误预测分支,并留下可通过副通道提取的可观察到的副作用。仅举几个例子:eBPF程序屏蔽了内存访问,以便在瞬态指令下将访问重定向到受控区域,验证程序还遵循仅在推测执行下可访问的程序路径,并且在无法将尾调用转换为直接调用的情况下,JIT编译器会发出Retpolines。
- 常量盲:对代码中的所有常量都盲,以防止JIT喷射攻击。这样可以防止攻击者将可执行代码作为常量注入,而该常量在存在另一个内核错误的情况下,可能使攻击者跳入eBPF程序的内存部分以执行代码。
抽象的运行时上下文
eBPF程序无法直接访问任意内核内存。必须通过eBPF帮助器访问对程序上下文之外的数据和数据结构的访问。这保证了一致的数据访问,并使任何此类访问均受eBPF程序的特权约束,例如,如果可以保证修改的安全性,允许运行的eBPF程序修改某些数据结构的数据。eBPF程序不能随机修改内核中的数据结构。
为什么选择eBPF?
可编程的力量
让我们从一个类比开始。你还记得地理城吗?20年前,网页几乎完全是用静态标记语言(HTML)编写的。网页基本上是一个文档,其中包含能够显示它的应用程序(浏览器)。纵观当今的网页,网页已成为功能完善的应用程序,基于Web的技术已取代了以需要编译的语言编写的绝大多数应用程序。是什么促成了这一发展?
简短的答案是通过引入JavaScript来实现可编程性。它开启了一场巨大的革命,导致浏览器发展成为几乎独立的操作系统。
进化为什么发生?程序员不再局限于运行特定浏览器版本的用户。代替说服标准机构需要新的HTML标签,必需的构建块的可用性使底层浏览器的创新速度与运行在顶部的应用程序脱钩。当然,这有点过分简单了,因为HTML确实随着时间的推移而发展并为成功做出了贡献,但是HTML本身的发展还不够。
在采用此示例并将其应用于eBPF之前,让我们看一下对JavaScript引入至关重要的几个关键方面:
- 安全性:不受信任的代码在用户的浏览器中运行。这可以通过对JavaScript程序进行沙盒处理和抽象化对浏览器数据的访问来解决。
- 持续交付:程序逻辑的发展必须是可能的,而无需不断交付新的浏览器版本。通过提供足以构建任意逻辑的正确的低级构建模块,可以解决此问题。
- 性能:必须以最小的开销提供可编程性。通过引入即时(JIT)编译器解决了这一问题。
对于上述所有原因,出于相同的原因,可以在eBPF中找到确切的计数器零件。
eBPF对Linux内核的影响
现在让我们回到eBPF。为了了解eBPF对Linux内核的可编程性影响,有助于对Linux内核的体系结构以及它与应用程序和硬件的交互方式有一个较高的了解。
Linux内核的主要目的是抽象硬件或虚拟硬件,并提供一致的API(系统调用),允许应用程序运行和共享资源。为了实现这一目标,维护了广泛的子系统和层集来分配这些职责。每个子系统通常允许进行某种程度的配置,以解决用户的不同需求。如果无法配置所需的行为,则历史上需要更改内核,剩下两个选择:
本机支持
- 更改内核源代码,并说服Linux内核社区需要进行更改。
- 请等待几年,以使新的内核版本成为商品。
内核模块
- 编写内核模块
- 定期修复它,因为每个内核版本都可能破坏它
- 由于缺乏安全性边界,可能会损坏Linux内核
使用eBPF,可以使用一个新选项,该选项允许重新编程Linux内核的行为,而无需更改内核源代码或加载内核模块。在许多方面,这与JavaScript和其他脚本语言如何解锁已变得难以更改或代价昂贵的系统演变非常相似。
开发工具链
存在一些开发工具链来协助eBPF程序的开发和管理。它们都满足用户的不同需求:
密件抄送
BCC是一个框架,使用户可以使用内置的eBPF程序编写python程序。该框架主要针对涉及应用程序和系统分析/跟踪的用例,其中使用eBPF程序收集统计信息或生成事件,而用户空间中的对等方收集数据并以人类可读的形式显示。运行python程序将生成eBPF字节码并将其加载到内核中。
bpftrace
bpftrace是Linux eBPF的高级跟踪语言,在最新的Linux内核(4.x)中可用。bpftrace使用LLVM作为后端将脚本编译为eBPF字节码,并利用BCC与Linux eBPF子系统以及现有的Linux跟踪功能进行交互:内核动态跟踪(kprobes),用户级动态跟踪(uprobes)和跟踪点。bpftrace语言受awk,C和先前的跟踪器(例如DTrace和SystemTap)的启发。
eBPF Go库
eBPF Go库提供了一个通用的eBPF库,该库将获取eBPF字节码的过程与eBPF程序的加载和管理解耦。通常通过编写高级语言来创建eBPF程序,然后使用clang / LLVM编译器将其编译为eBPF字节码。
eBPF导致了新一代软件的开发,该软件能够对Linux内核的行为进行重新编程,甚至可以跨传统上完全独立的多个子系统应用逻辑。https://ebpf.io/
libbpf C / C ++库
https://github.com/libbpf/libbpf
libbpf库是基于C / C ++的通用eBPF库,它通过将易于使用的库API用于应用程序。
进一步阅读
如果您想了解有关eBPF的更多信息,请继续使用以下其他材料进行阅读:
文献资料
- BPF和XDP参考指南 Cilium文档,2020年8月
- BPF文档 Linux内核中的BPF文档
- 有关内核相关eBPF问题的BPF设计问答( FAQ)
讲解
- 学习eBPF跟踪:教程和示例 Brendan Gregg的博客,2019年1月
- XDP动手教程 各种作者,2019年
- BCC,libbpf和BPF CO-RE教程 Facebook的BPF博客,2020年
会谈
泛型
- eBPF和Kubernetes:扩展微服务的小帮手 Daniel Borkmann,KubeCon EU,2020年8月
- eBPF-重新思考Linux内核(幻灯片) Thomas Graf,QCon伦敦,2020年4月
- BPF作为一种针对集装箱领域的革命性技术(幻灯片) Daniel Borkmann,FOSDEM,2020年2月
- BPF在Facebook Alexei Starovoitov上的表现峰会,2019年12月
- BPF:一种新型软件(幻灯片) Brendan Gregg,Ubuntu Masters,2019年10月
- eBPF作为技术的普遍存在和必要性 David S.Miller,内核食谱,2019年10月
Cilium
- 使用BPF和XDP进行Kubernetes服务的大规模负载均衡 Daniel Borkmann和Martynas Pumputis,Linux Plumbers,2020年8月
- 从kube-proxy和iptables中解放Kubernetes (幻灯片) Martynas Pumputis,KubeCon US 2019
- 了解Cilium中的eBPF数据路径并对其进行故障排除(幻灯片) Nathan Sweet,KubeCon US 2019
- 使用Envoy,Cilium和BPF进行透明混沌测试(幻灯片) Thomas Graf,KubeCon EU 2019
- Cilium-将BPF革命带入Kubernetes网络和安全性(幻灯片) Thomas Graf,《 All Systems Go!》,柏林,2018年9月
- 如何使用eBPF使Linux具备微服务意识(幻灯片) Thomas Graf,QCon旧金山,2018年
- Linux内核加速特使 Thomas Graf,KubeCon EU 2018
- Cilium-使用BPF和XDP的网络和应用程序安全(幻灯片) Thomas Graf,DockerCon Austin,2017年4月
哈勃
- 哈勃-基于eBPF的Kubernetes Sebastian Wicki的可观察性,欧盟KubeCon,2020年8月
图书
- 系统性能:企业与云,第二版 Brendan Gregg,Addison-Wesley专业计算系列,2020年
- BPF Performance Tools Brendan Gregg,Addison-Wesley Professional Computing Series,2019年12月
- BPF的Linux可观察性 David Calavera,Lorenzo Fontana,O'Reilly,2019年11月
文章和博客
- 安全性和混乱的BPF,于Kubernetes Sean Kerner,LWN,2019年6月
- 新年Linux技术:eBPF Joab Jackson,2018年12月
- eBPF的完整介绍 Matt Fleming,LWN,2017年12月
- Cilium,BPF和XDP Google开放源代码博客,2016年11月
- 自2011年4月以来,关于BPF LWN的各种文章的存档
特色eBPF博客
- 2020年10月9日阿里云如何将Cilium用于高性能云原生网络 Cilium作者
- 2020年9月29日BPF不仅仅是速度 Paul Chaignon
- 2020年8月19日Google宣布Cilium和eBPF作为GKE Cilium作者的新网络数据平台
- 2020年6月22日Cilium 1.8:XDP负载平衡,群集范围内的流量可见性,主机网络策略,本机GKE和Azure模式,会话亲和力,CRD模式可伸缩性,策略审核模式,... Cilium作者
- 2020年4月12日带有LLVM Quentin Monnet的eBPF组件
- 2020年4月11日了解BPF Quentin Monnet的tc“直接行动”模式
- 2020年2月20日HOWTO:BCC到libbpf转换 Andrii Nakryiko
- 2020年2月19日BPF的可移植性和CO-RE Andrii Nakryiko
- 2019年12月22日BPF Theremin,Tetris和打字机 Brendan Gregg
- 2019年12月2日BPF:一种新型软件 Brendan Gregg
- 2019年10月11日GitHub页面中的BPF语法突出显示 Paul Chaignon
- 2019年10月2日strace简介–seccomp-bpf Paul Chaignon
- 2019年7月15日BPF性能工具:Linux系统和应用程序的可观察性(本书) Brendan Gregg
- 2018年8月31日BPF对象的寿命 Alexei Starovoitov
要了解有关eBPF及其用例的更多信息:
源码地址:https://github.com/libbpf/libbpf