(一)什么是eBPF?为什么eBPF非常重要?

现在将会开始一个较为长期的的笔记系列,来记录eBPF的学习过程。这系列的笔记并不会把eBPF的历史、eBPF的概念等内容抄上来复述一遍,如果遇到不懂的概念还需要自己主动去查找资料去思考和学习,带有思考的阅读价值可能会更大一些。这一系列的笔记主要是参考《Learning eBPF》这本官方给出的电子书,当这一系列结束时,这本书也就阅读完毕,希望写者与读者都能成为eBPF的beginner水平~

前言

  eBPF(extended Berkeley Packet Filter)是一个革命性的内核工具:通过使用这个工具,开发者们可以动态地向内核装载一些自定义代码来改变内核的行为,其意义将在下面展开描述。虽然介绍一件事物的背景显得很枯燥,但是了解一件事物发展路线,可以帮助我们理解每个feature后面的insight,学习最大的快乐就是能相互交织、融会贯通,不是吗?

  eBPF的众多功能中,经常被提及的有:

  • Performance tracing(perf)
  • High-performance networking(efficiency)
  • Detecting and preventing malicious activity(security)

  在后面我们会发现,eBPF的相关工具虽然数量众多,但是仍然无法脱离性能监控、高效和安全这三点。

如果回到eBPF上,会发现与之相关的系统调用叫bpf(),eBPF和BPF的关系也影射了eBPF的部分发展历史,可以去查询相关资料详细了解。

在2014年,内核3.18版本更新时,BPF演变成了我们现在所说的"extended BPF"或者"eBPF"。这次演变的主要包含这几点重要变化(为了避免翻译引起的歧义,这里给出了原文):

  • The BPF instruction set was completely overhauled to be more efficient on 64-bit machines, and the interpreter was entirely rewritten
  • eBPF maps ware introduced, which are data structures that can be accessed by BPF programs and by user space applications, allowing information to be shared between them.
  • The bpf() system call was added so that user space programs can interact with eBPF programs in the kernel.
  • Several BPF helper functions were added.
  • The eBPF verifier was added to ensure that eBPF programs are safe to run.
  • BPF指令集经过了大幅优化来保证在64位操作系统中有更好的性能,并且BPF解释器被完全重写。
  • 处在内核中的数据结构eBPF maps被设计出来,该数据结构可以被所有BPF程序和用户态程序访问,实现信息共享功能。
  • 添加了一个系统调用bpf(),从而允许用户态程序能够与处在内核态的eBPF程序进行交互。
  • 添加了若干个BPF辅助函数(如生成随机数的函数)
  • 增加了eBPF验证器来保证eBPF程序的安全可靠(不会导致系统或者其他进程崩溃)

  事实上,eBPF并非什么小众的黑科技,自2015年内核版本4.2之后,各大公司已经在实际生产环境中开始使用eBPF这一项技术,在2018年之后其应用落地的案例出现的则更加频繁。

如何命名?

  eBPF的应用范围已经远不止报文过滤(packet filtering)这一项功能,其简写——eBPF已经远无法涵盖其具有的强大作用,至今俨然成了一个专有名词。此外由于绝大多数的Linux 内核都已经支持了BPF的扩展部分,因此人们也逐渐将eBPF和BPF混作一谈。在Linux内核源码和eBPF程序中,都使用的术语是BPF,例如eBPF相关的系统调用名字是bpf(), 所有的辅助函数(helper function)都以bpf_开头,BPF程序使用前缀BPF_PROG_TYPE来标识。因此有人说BPF这个词时,极大概率说的是eBPF;当有人说eBPF时,那他就是在说eBPF。随着eBPF热度的不断增长,这种“狭隘的错误”可能还会不断放大。

Linux内核

  Linux内核是硬件和应用程序之间的一层software layer。应用程序运行所在的非特权层称为用户空间(user space),用户空间中的进程无法直接访问硬件。这些应用程序需要使用系统调用(syscall)请求内核来代为操作。硬件访问通常包括文件读写、网络报文的发送和接收、访问内存等等。内核还会负责调度工作,用于协调进程的运行。具体如下图所示(引自书中Fig 1-1)

Linux内核

  一般而言,程序开发者很少会自己调用syscall,高级编程语言通常对系统调用进行了封装。但是如果想直到一个进程运行过程中,内核被调用了多少次(一般发生在调用syscall或者发生中断时),可以使用strace命令来展示应用程序调用的所有syscall。

1
strace -c echo "hello"

strace执行结果

  正因为应用程序和内核交互密切,通过eBPF进行代码注入,我们就能更清晰的看到应用程序究竟访问了哪些文件、使用了哪些端口、访问了哪块内存。但是完成这个代码注入的拦截过程,eBPF程序就需要在内核中添加新的功能。

为内核添加新的功能

  Linux内核有三千多万行代码。对内核的任何修改都要求开发者自身对内核有非常高的熟练程度,除非你是内核的开发者,否则这是非常难的。

  此外,如果你想让你的代码修改贡献到内核中,遇到的不只有技术问题。如果想要让你的代码修改称为正式发行版本的一部分,这部分代码需要让整个社区的人(甚至是Linus Trovalds——creator and main developer of Linux)都接受并认可。据统计,提交的内核补丁中只有1/3被接受。

  即使你的代码修改被认可了,Linux内核的版本的发行需要若干个月时间去等待;即使新的版本中包含了你的修改,还要记得,不同的Linux发行版(Ubuntu、Debian等)并不会直接使用最新的Linux内核,因为需要在内核之上增加一些内容。当你所期待的Linux发行版终于用上带有你提交补丁的内核版本时,时间已经几年过去了。

内核模块 (Kernel Modules)

  如果不想花几年时间去等待内核的改变,还有一种可选方案:使用内核模块。Linux内核支持动态加载和卸除内核模块。如果想要扩展内核功能,可以写一个内核模块来完成。

  但是编写内核仍然是需要内核相关编程经验,此外内核模块的安全性也是非常大的隐患。再一步的说,即使模块能安全的运行也不代表其不会崩溃,进而导致系统或者其他应用程序的异常退出。这一系列的隐患,使得内核模块的使用受到极大的限制。

eBPF程序的动态加载

  迄今为止,内核模块功能添加的困难主要是开发难度、更新周期、安全性保证和功能的可靠性。eBPF程序可以可以动态地从内核中加载和卸载。一旦eBPF程序绑定了一个事件,当该事件触发时(无论因何触发)该eBPF程序都会执行。

譬如当一个eBPF程序绑定了打开文件相关的系统调用时,每当一个进程打开文件时都会触发eBPF程序的执行。

eBPF程序的高性能

  一旦eBPF程序被加载进内核,JIT-compiler就会将其编译为原生的CPU机器指令,保证最高的运行性能。最重要的是,程序的执行没有在内核态和用户态之间的内存复制开销。因此native的机器指令和无内核态用户态之间的内存复制,使得eBPF程序的执行效率非常的高。

云原生环境(Cloud Native Environments)中的eBPF

  在Kubernetes环境中,同一机器上所有Pods中的所有containers都使用一个同一个内核(即共享宿主机的内核)。当使用eBPF对内核进行插桩后,在该节点上的所有容器化实例都能访问并调用该eBPF程序。在云原生环境中,使用eBPF就具有非常大的优势:

  1. 不需要对应用程序的源码或者配置进行修改
  2. 只有eBPF程序加载进内核并绑定相应事件,eBPF程序就能观测已经在运行的应用程序进程。

  在云原生环境中进行插桩注入的工具还有比较知名的sidecar model,它可以为Kubernetes应用提供logging、tracing、security和service mesh功能。使用sidecar model比在源码中进行代码注入要好一些,但是也有一些缺点:

  1. 应用程序所在Pod必须重启从而添加sidecar
  2. 必须要在应用程序的配置yaml文件中添加一些东西(譬如加label来标识要注入的目标,如果没有正确的标识注入目标,sidecar将不会进行相应相应的注入)
  3. sidecar注入会导致Pod启动时间大幅延长、竞态问题。
  4. 如果一些网络相关的功能是基于sidecar实现的话,其网络时延会变得相对较大。因为所有流量都需要反复进出内核态的网络协议栈。

sidecar实现的网络结构延迟较高

  通过使用eBPF就能避免sidecar的这些问题。不需要对应用程序进行任何标注来确定目标,并且对安全性也能有足够的保证(见Learning-eBPF P13)。


(一)什么是eBPF?为什么eBPF非常重要?
https://www.torch-fan.site/2023/04/11/eBPF/
作者
Torch-Fan
发布于
2023年4月11日
更新于
2023年4月11日
许可协议