[转帖]eBPF系列学习(4)了解libbpf、CO-RE (Compile Once – Run Everywhe) | 使用go开发ebpf程序(云原生利器cilium ebpf )

ebpf,系列学习,了解,libbpf,co,re,compile,once,run,everywhe,使用,go,开发,程序,原生,利器,cilium · 浏览次数 : 0

小编点评

**1. Go 语言开发库以及选择 使用 Go 语言管理和分发 ebpf 程序** * 库贡献者活跃度 * 选择库和工具来与 eBPF 进行交互时,会让人有所困惑 **2. cilium/ebpf 库实践** * cilium/ebpf 库 * cilium/ebpf库 github * cilium/ebpf库 github **3. Pure Go 库用于读取,修改和加载EBPF程序** * 使用 go开发ebpf程序思路 * 官方demo: tracepoint_in_go * 官方文档: cilium/ebpf **4. 纯go库用于读取,修改和加载EBPF程序** * 使用 go开发ebpf程序思路 * 官方文档: cilium/ebpf

正文

一、了解libbpf

在使用libbpf前,先使用bcc对eBPF相关知识进行学习运行,学习曲线将更平滑。相对于bcc,libbpf与BPF CO-RE的实际编译部署的难度增大了。

1. BPF的可移植性CO-RE (Compile Once – Run Everywhere)

BPF 可移植性面临的问题

BPF 程序是由用户提供的、经过验证之后在内核上下文中执行的程序。 BPF运行在内核内存空间(kernel memory space)执行,能访问大量的 内核内部状态(internal kernel state)。 这使得 BPF 程序功能极其强大,也是为什么它能成功地应用在大量不同场景的原因之一。

但另一方面,与强大能力相伴而生的是我们如今面临的可移植性问题:BPF 程序 并不控制它运行时所在内核的内存布局(memory layout)。 因此,BPF 程序只能运行在开发和编译这些程序时所在的内核。

另外,内核类型(kernel types)和数据结构(data structures)也在不断变化。 不同的内核版本中,同一结构体的同一字段所在的位置可能会不同 —— 甚至已经 移到一个新的内部结构体(inner struct)中。此外,字段还可能会被重命名、删除、 改变类型,或者(根据不同内核配置)被条件编译掉。

一旦需要查看原始的内核内部数据(raw internal kernel data)—— 例如 常见的表示进程或线程的 struct task_struct,这个结构体中有非常详细的进程信息 —— 那你就只能靠自己了。对于 tracing、monitoring 和 profiling 应用来说这个需求 非常常见,而这类 BPF 程序也是极其有用的。

内核版本不同:字段被重命名或移动位置
在这种情况下,如何保证读到的一定是我们期望读的那个字段呢 —— 例如,

  • 原来的程序是从 struct task_struct offset 8 地址读取数据,
  • 由于新内核加个了 16 字节新字段,那此时正确的方式应该是从 offset 24 地址读,

这还没完:如果这个字段被改名了呢?例如,thread_struct 的 fs 字段(获取 thread-local storage 用), 在 4.6 到 4.7 内核升级时就被重命名为了 fsbase。

内核版本相同但配置不同:字段在编译时被移除(compile out)
另一种情况:内核版本相同,但内核编译时的配置不同,导致 结构体的某些字段在编译器时被完全移除了。

总结: 依赖开发环境本地的内核头文件编译的 BPF 程序, 是无法直接分发到其他机器运行 —— 然后期待它们返回正确结果的。 这是由于不同版本的内核头文件所假设的内存布局是不同的。

这里有个很强的前提:内核头文件在目标机器上一定存在。 在大部分情况下这都不是问题,但有时可能会带来麻烦。
这对内核开发者来说也尤其头疼,因为他们经常要编译和部署一次性的内核,用于在 开发过程中验证某些问题。而机器上没有指定的、版本正确的内核头文件包,基于 BCC 的应用就无法正常工作。

这种方式会拖慢开发和迭代速度。

总体来说,虽然 bcc 是一个很伟大的工具 —— 尤其是用于快速原型、实验和开发小工具 —— 但 当用于广泛部署生产 BPF 应用时,它存在非常明显的不足。

为了更彻底地解决 BPF 移植性问题,我们设计了 BPF CO-RE,并相信这是BPF 程序的未来开发方式,尤其适用于开发复杂、真实环境中的 BPF 应用。

BPF的可移植性CO-RE (Compile Once – Run Everywhere)

官方:BPF CO-RE (Compile Once – Run Everywhere)
参考URL: https://github.com/libbpf/libbpf#bpf-co-re-compile-once–run-everywhere
BPF的可移植性和CO-RE (Compile Once – Run Everywhere)
参考URL: https://www.cnblogs.com/charlieroro/p/14206214.html

eBPF 的内核无关是有限的,需要 eBPF 机制和开发者共同努力实现。除了 BCC 这种即时编译的方案,还有另外一种名为 CO-RE (Compile Once – Run Everywhere) 的编译方式,其核心依赖于 BTF(更加先进的 DWARF 替代方案)。

文章BPF Portability and CO-R 指出,为了提高BPF程序的便携性,即在不同内核版本上正常工作,而无需为每个特定内核重新编译的能力,社区提出了一个称为BPF CO-RE(Compile Once – Run Everywhere)的解决方案。

Libbpf+BPF CO-RE的理念是,BPF程序与任何"正常"用户空间程序没有太大区别:它们应该汇编成小型二进制文件,然后以紧凑的形式进行部署,以瞄准主机。Libbpf 扮演 BPF 程序装载机的角色,执行平凡的设置工作(重定位、加载和验证 BPF 程序、创建 BPF map、连接到 BPF 挂钩等),让开发人员只担心 BPF 程序的正确性和性能。这种方法将开销保持在最低水平,消除沉重的依赖关系,使整体开发人员体验更加愉快。

BPF CO-RE的目标是帮助BPF开发者使用一个简单的方式解决简单的可移植性问题(如读取结构体字段),并使用它来定位复杂的可移植性问题(如不兼容的数据结构,复杂的用户空间控制条件等)。使得开发者的BPF程序能够"一次编译–随处运行"

Libbpf supports building BPF CO-RE-enabled applications, which, in contrast to BCC, do not require Clang/LLVM runtime being deployed to target servers and doesn’t rely on kernel-devel headers being available.

Libbpf 支持构建 BPF CO-RE-enabled 的应用程序,与BCC相比,它不需要部署的Clang/LLVM运行时,并且不依赖 kernel-devel 内核头文件。

It does rely on kernel to be built with BTF type information, though. Some major Linux distributions come with kernel BTF already built in:

不过,它确实依靠内核来使用BTF类型信息构建。一些主要的Linux发行版本已内置的内核BTF:
Fedora 31+
RHEL 8.2+
OpenSUSE Tumbleweed (in the next release, as of 2020-06-04)
Arch Linux (from kernel 5.7.1.arch1-1)
Manjaro (from kernel 5.4 if compiled after 2021-06-18)
Ubuntu 20.10
Debian 11 (amd64/arm64)

如果您的内核不支持内置的BTF附带,则需要构建自定义内核。你需要:

  • pahole 1.16+ tool (part of dwarves package), which performs DWARF to BTF conversion;
  • kernel built with CONFIG_DEBUG_INFO_BTF=y option;
  • you can check if your kernel has BTF built-in by looking for /sys/kernel/btf/vmlinux file:
$ ls -la /sys/kernel/btf/vmlinux
-r--r--r--. 1 root root 3541561 Jun  2 18:16 /sys/kernel/btf/vmlinux
  • 1
  • 2

要开发和构建BPF程序,您将需要Clang/LLVM 10+。默认情况下,以下发行版具有clang/llvm 10+打包:

  • Fedora 32+
  • Ubuntu 20.04+
  • Arch Linux
  • Ubuntu 20.10 (LLVM 11)
  • Debian 11 (LLVM 11)
  • Alpine 3.13+

2. libbpf和bcc性能对比

性能优化大师 Brendan Gregg 在用 libbpf + BPF CO-RE 转换一个 BCC 工具后给出了性能对比数据:

As my colleague Jason pointed out, the memory footprint of opensnoopas CO-RE is much lower than opensnoop.py. 9 Mbytes for CO-RE vs 80 Mbytes for Python.

这句话原文暂未找到,TODO!

我们可以看到在运行时相比 BCC 版本,libbpf + BPF CO-RE 版本节约了近 9 倍的内存开销。

3. 了解libbpf

摆脱对内核头文件的依赖
除了使用内核的BTF信息进行字段的重定位意外,还可以将BTF信息生成一个大(基于5.10.1版本生成的长度有106382行)的头文件(“vmlinux.h”),其中包含了所有的内核内部类型,可以避免对系统范围的内核头文件的依赖。可以使用如下方式生成vmlinux.h:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
  • 1

上述命令可以获得到一个可兼容的C头文件(即"vmlinux.h"),包含所有的内核类型("所有"意味着包含那些不会通过kernel-devel包暴露的头文件)。

当使用了vmlinux.h,此时就不需要依赖像#include <linux/sched.h>, #include <linux/fs.h>这样的头文件,仅需要#include "vmlinux.h"即可。该头文件包含了所有的内核类型:暴露了UAPI,通过kernel-devel提供的内部类型,以及其他一些更加内部的内核类型。

不幸的是,BTF(即DWARF)不会记录#define宏,因此在vmlinux.h中丢失一些常用的宏。但大多数通常不存在的宏可以通过libbpf的bpf_helpers.h(即libbpf提供的内核侧的库)头文件提供。

libbpf知道如何将BPF程序代码匹配到特定的内核。它会查看程序记录的BTF类型和重定位信息,然后将这些信息与内核提供的BTF信息进行匹配。 libbpf解析并匹配所有的类型和字段,更新必要的偏移以及重定位数据,确保BPF程序能够正确地运行在特定的内核上。如果一切顺利,则BPF应用开发人员会获得一个BPF程序,这种方式可以针对目标主机上的内核进行“量身定制”,就好像程序是专门针对这个内核编译的,但无需在应用程序中分发Clang以及在目标主机上的运行时中执行编译,就可以实现所有这些目标。

libbpf 是一个比BCC更新的 BPF 开发库,也是最新的 BPF 开发推荐方式!

2015年11月 Kernel 4.3 引入标准库 libbpf, 该标准库由Huawei 2012 OS内核实验室的王楠提交。

2018年 为解决BCC的缺陷,CO-RE(Compile Once, Run Everywhere)的想法被提出并实现,最后达成共识:libbpf + BTF + CO-RE代表了eBPF的未来,BCC底层实现逐步转向libbpf。

4. libbpf 库

libbpf/bpftool 项目地址:https://github.com/libbpf/libbpf

libbpf库是基于C / C ++的通用eBPF库,提供了一些加载bpf程序的方法,封装了内核提供的bpf()系统调用。
在这里插入图片描述

eBPF 程序加载的本质是 BPF 系统调用,Linux 内核通过 BPF 系统调用提供 eBPF 相关的一切操作,比如:程序加载、map 创建删除等。常见的 loader 都是对这个系统调用的封装,部分 loader 提供更加原生接近系统调用的操作,部分 loader 则是进行了更多封装使得编程更便捷。

内核实现的 libbpf 库,封装了 BPF 系统调用,使得加载 BPF 程序更便捷。libbpf 不像 iproute2,它能够使 BPF 相关操作更为便捷,没有做过多封装。如果要将程序加载到内核,则需要自己实现一个用户态程序,调用 libbpf 的 API 去加载到内核。如果要复用 pinned 在 BPF 文件系统的 MAP,也需要用户态程序调用 libbpf 的 API,在加载程序时进行相关处理。

典型案例:Facebook 开源的 katran 项目使用 libbpf 加载 eBPF 程序。

构建基于libbpf的BPF应用需要使用BPF CO-RE的步骤

构建基于libbpf的BPF应用需要使用BPF CO-RE包含的几个步骤:

  • 生成带所有内核类型的头文件vmlinux.h;
  • 使用Clang(版本10或更新版本)将BPF程序的源代码编译为.o对象文件;
  • 使用 libbpfgo 将其编译为二进制文件,加载到内核中并监听输出。

5. libbpf-bootstrap

原文链接:Building BPF applications with libbpf-bootstrap

开始使用 BPF 在很大程度上仍然令人生畏,因为即使为简单的"Hello World"般的 BPF 应用程序设置构建工作流,也需要一系列步骤,对于新的 BPF 开发人员来说,这些步骤可能会令人沮丧和令人生畏。这并不复杂,但知道必要的步骤是一个(不必要的)困难的部分。

libbpf-bootstrap 就是这样一个 BPF 游乐场,它已经尽可能地为初学者配置好了环境,帮助他们可以直接步入到 BPF 程序的书写。它综合了 BPF 社区多年来的最佳实践,并且提供了一个现代化的、便捷的工作流。libbpf-bootstrap 依赖于 libbpf 并且使用了一个很简单的 Makefile。对于需要更高级设置的用户,它也是一个好的起点。即使这个 Makefile不会被直接使用到,也可以很轻易地迁移到别的构建系统上。

二、使用go开发ebpf程序

ebpf的核心程序是通过c编写,clang进行编译的。在编译好ebpf程序后,我们需要将其加载到内核中。目前有很多个项目对ebpf的编写调试运行的流程进行了优化,比较有名的是bcc和libbpf。很多时候我们希望能够更加方便的进行程序编写和部署,也希望程序能够在不同的linux发行版和内核上使用(即BPF CO-RE),bcc的运行依赖内核的头文件,也引入了繁重的整个clang llvm工具链,libbpf只能使用C/C++进行外部程序的开发

BCC、libbpf都主要使用 C 语言开发 eBPF 程序,而实际的应用程序可能会以多种多样的编程语言进行开发。所以,开源社区也开发和维护了很多不同语言的接口,方便这些高级语言跟 eBPF 系统进行交互。

BCC 就提供了 Python、C++ 等多种语言的接口,而使用 BCC 的 Python 接口去加载 eBPF 程序,要比 libbpf 的方法简单得多。

随着ebpf的发展,开源社区中也诞生了各种编程语言的开发库,特别是 Go 和 Rust 这两种语言,其开发库尤为丰富。

1. Go 语言开发库以及选择

使用 Go 语言管理和分发 ebpf 程序
参考URL: https://www.ebpf.top/post/ebpf_go/

目前使用 Go 开发 eBPF 程序可以使用的框架有 IO Visor-gobpf、Dropbox-goebpf和 Cilium-ebpf等,考虑到 Cilium 的社区活跃度和未来的发展,使用 Cilium 的 ebpf 是一个比较不错的选择。

在这里插入图片描述
每个库都有各自的范围和限制:

  • Calico 在用 bpftool 和 iproute2 实现的 CLI 命令基础上实现了一个 Go 包装器。
  • Aqua 实现了对 libbpf C 库的 Go 包装器。
  • Dropbox 支持一小部分程序,但有一个非常干净和方便的用户API。
  • IO Visor 的 gobpf 是 BCC 框架的 Go 语言绑定,它更注重于跟踪和性能分析。
  • Cilium 和 Cloudflare 维护一个 纯 Go 语言编写的库 (以下简称 “libbpf-go”),它将所有 eBPF 系统调用抽象在一个本地 Go 接口后面。

在使用这些 Go 语言开发库时需要注意,Go 开发库只适用于用户态程序中,可以完成 eBPF 程序编译、加载、事件挂载,以及 BPF 映射交互等用户态的功能,而内核态的 eBPF 程序还是需要使用 C 语言来开发的。

当涉及到选择库和工具来与 eBPF 进行交互时,会让人有所困惑。在选择时,你必须在基于 Python 的 BCC 框架、基于 C 的 libbpf 和一系列基于 Go 的 Dropbox、Cilium、Aqua 和 Calico 等库中选择。

库贡献者的活跃度
总结: cilium/ebpf > iovisor/gobpf > dropbox/goebpf > aquasecurity/libbpfgo

关于Cilium?

cilium
n. 纤毛;睫毛;
[例句]Cilium is the major power source of pallium to transport materials.
纤毛是外套膜进行物质运输的主要动力来源。
[其他] 复数:cilia

eBPF是一项革命性的技术,可以在Linux内核中运行沙盒程序,而无需重新编译内核或加载内核模块。在过去的几年中,eBPF已成为解决以前依赖于内核更改或内核模块的问题的标准方法。

eBPF 在动态跟踪、网络、安全以及云原生等领域的广泛应用。

Cilium是一个开源项目,它的基础是基于eBPF的Linux内核技术,用于透明地提供和保护使用Linux容器管理平台部署的应用程序服务之间的网络和API连接。以解决容器工作负载的新可伸缩性,安全性和可见性要求。Cilium超越了传统的容器网络接口(CNI),可提供服务解析,策略执行等功能。

在这里插入图片描述
Cilium已迅速成为Kubernetes生态系统中的领先技术,为Google Kubernetes Engine(GKE)提供了网络数据平面,并在其他领先的云原生最终用户(包括Adobe,DataDog,GitLab和DigitalOcean)中得到采用。

Cilium 母公司 Isovalent
Liz Rice,是Isovalent的首席开源官,这家公司是Cilium网络项目的幕后推手。也是CNCF技术监督委员会主席

Cilium 是一个用于容器网络领域的开源项目,主要是面向容器而使用,用于提供并透明地保护应用程序工作负载(如应用程序容器或进程)之间的网络连接和负载均衡。

2. cilium/ebpf库实践

cilium/ebpf 纯 Go 程序编写,从而实现了程序最小依赖;与此同时其还提供了 bpf2go 工具,可用来将 eBPF 程序编译成 Go 语言中的一部分,使得交付更加方便。

因此,本文也选择基于 cilium/ebpf 库来开发和实践。

cilium/ebpf 库

cilium/ebpf库
github: https://github.com/cilium/ebpf

ebpf的核心程序是通过c编写,clang进行编译的。在编译好ebpf程序后,我们需要将其加载到内核中。目前有很多个项目对ebpf的编写调试运行的流程进行了优化,比较有名的是bcc和libbpf。很多时候我们希望能够更加方便的进行程序编写和部署,也希望程序能够在不同的linux发行版和内核上使用(即BPF CO-RE),bcc的运行依赖内核的头文件,也引入了繁重的整个clang llvm工具链,libbpf只能使用C/C++进行外部程序的开发。

如果想使用go编写,有两个选择:cilium的ebpf项目和libbpf-go,考虑的社区活跃度和未来的发展,也许使用cilium的ebpf工具比较合适。

eBPF 程序加载的本质是 BPF 系统调用,Linux 内核通过 BPF 系统调用提供 eBPF 相关的一切操作,比如:程序加载、map 创建删除等。常见的 loader 都是对这个系统调用的封装,部分 loader 提供更加原生接近系统调用的操作,部分 loader 则是进行了更多封装使得编程更便捷。

cilium/ebpf 库是一个 GO 语言版本的 libbpf 库,它封装了 BPF 系统调用,与内核提供的 libbpf 类似。使用 cilium/ebpf 库实现用户态程序加载 eBPF 到内核,在很多方面都类似 libbpf,区别在于这个库是 GO 语言的,更加方便使用 GO 语言构建一套 eBPF 程序的控制面方案。

纯go库用于读取,修改和加载EBPF程序,并将其连接到Linux内核中的各种钩子上。

使用go开发ebpf程序思路

官方github: https://github.com/cilium/ebpf
使用 Go 语言开发 ebpf 程序
https://houmin.cc/posts/adca5ae5/

开发者只需要实现内核态C文件,用户态go文件,用户态event消息结构体三个文件即可,框架会自动加载执行。

  • 运行在内核态用C写eBPF代码,llvm编译为eBPF字节码。
  • 用户态使用golang编写,cilium/ebpf纯go类库,做eBPF字节码的内核加载,kprobe/uprobe HOOK对应函数。
  • 用户态使用golang做事件读取、解码、处理。

cilium/ebpf是一个纯GO库,可提供用于加载,编译和调试EBPF程序的实用程序。它具有最小的外部依赖性,旨在用于长期运行的过程中。

官方demo: tracepoint_in_go

官方demo参考路径:
examples/tracepoint_in_go/main.go

//此程序演示如何将eBPF程序附加到跟踪点。
//程序附加到syscall/sys_enter_openat跟踪点,并且
//每次 syscall 时,打印出整数123。

//基于预先存在的内核hook(tracepoint)打开跟踪事件。
//每次用户空间程序使用’openat()’ 系统调用时,eBPF
//将执行上面指定的程序,并显示“123”值 在 perf ring 中

纯go库用于读取,修改和加载EBPF程序,并将其连接到Linux内核中的各种钩子上。
这是官方完全go写的demo,演示了 syscall/sys_enter_openat跟踪点。

文章知识点与官方知识档案匹配,可进一步学习相关知识
云原生入门技能树首页概览9070 人正在系统学习中

与[转帖]eBPF系列学习(4)了解libbpf、CO-RE (Compile Once – Run Everywhe) | 使用go开发ebpf程序(云原生利器cilium ebpf )相似的内容:

[转帖]eBPF系列学习(4)了解libbpf、CO-RE (Compile Once – Run Everywhe) | 使用go开发ebpf程序(云原生利器cilium ebpf )

文章目录 一、了解libbpf1. BPF的可移植性CO-RE (Compile Once – Run Everywhere)BPF 可移植性面临的问题BPF的可移植性CO-RE (Compile Once – Run Everywhere) 2. libbpf和bcc性能对比3. 了解libbpf

[转帖]eBPF监控工具bcc系列一启航

https://blog.51cto.com/u_15333820/3453313 在eBPF篇中,我们知道虽然可用 C 来实现 BPF,但编译出来的却仍然是 ELF 文件,开发者需要手动析出真正可以注入内核的代码。工作有些麻烦,于是就有人设计了 BPF Compiler Collection(BC

[转帖]eBPF文章翻译(2)——BCC介绍(附实验环境)

nevermosby eBPF学习计划可以看这里。 该篇为入门文章翻译系列第二篇,第一篇看这里。 原文名称:An introduction to the BPF Compiler Collection,原文地址:https://lwn.net/Articles/742082/ 目录 BCC是什么 一

[转帖]eBPF文章翻译(3)——XDP原始设计介绍

https://cloud.tencent.com/developer/inventory/600/article/1626929 eBPF学习计划可以看这里。 该篇为入门文章翻译系列第三篇,第一篇看这里,第二篇看这里。 原文名称:Early packet drop — and more — wit

[转帖]你的第一个XDP BPF 程序

https://cloud.tencent.com/developer/inventory/600/article/1626925 这是一个实战系列文章,它是eBPF学习计划里面的应用场景之网络部分,终极目标是源码级别学习云原生网络方案Cilium(声明:下文提到的BPF字样是泛指,包括cBPF和e

[转帖]BPF数据传递的桥梁——BPF Map(一)

https://cloud.tencent.com/developer/inventory/600/article/1644458 这是一个实战系列文章,它是eBPF学习计划里面的应用场景之网络部分,终极目标是源码级别学习云原生环境下使用eBPF的场景,比如Cilium、Falco等(声明:下文提到

[转帖]eBPF 技术实践:加速容器网络转发,耗时降低 60%+

https://my.oschina.net/u/6150560/blog/5587717 背景 Linux 具有功能丰富的网络协议栈,并且兼顾了非常优秀的性能。但是,这是相对的。单纯从网络协议栈各个子系统的角度来说,确实做到了功能与性能的平衡。不过,当把多个子系统组合起来,去满足实际的业务需求,功

[转帖]eBPF 技术实践:加速容器网络转发,耗时降低60%+

https://new.qq.com/rain/a/20221103A03ZHE00 作者 | 王栋栋 背 景 Linux 具有功能丰富的网络协议栈,并且兼顾了非常优秀的性能。但是,这是相对的。单纯从网络协议栈各个子系统的角度来说,确实做到了功能与性能的平衡。不过,当把多个子系统组合起来,去满足实际

[转帖]eBPF介绍

https://blog.51cto.com/u_15155099/2767325 1.BPF起源BPF源头起源于一篇1992年的论文,这篇论文主要提出一种新的网络数据包的过滤的框架,如下图所示。提出bpf的原因其实也很简单,早期我们从网卡中接收到很多的数据包,我们要想从中过滤出我们想要的数据包,我

[转帖]eBPF 完全入门指南.pdf(万字长文)

https://zhuanlan.zhihu.com/p/492185920 图片 eBPF 源于 BPF[1],本质上是处于内核中的一个高效与灵活的虚类虚拟机组件,以一种安全的方式在许多内核 hook 点执行字节码。BPF 最初的目的是用于高效网络报文过滤,经过重新设计,eBPF 不再局限于网络协