资讯详情

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

文章目录

  • 一、了解libbpf
    • 1. BPF的可移植性CO-RE (Compile Once – Run Everywhere)
      • BPF 可移植性问题
      • BPF的可移植性CO-RE (Compile Once – Run Everywhere)
    • 2. libbpf和bcc性能对比
    • 3. 了解libbpf
    • 4. libbpf 库
      • 构建基于libbpf的BPF需要使用应用程序BPF CO-RE的步骤
    • 5. libbpf-bootstrap
  • 二、使用go开发ebpf程序
    • 1. Go 语言开发库和选择
      • 关于Cilium?
    • 2. cilium/ebpf库实践
      • cilium/ebpf 库
      • 使用go开发ebpf程序思路
      • 官方demo: tracepoint_in_go

一、了解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)。 因此,

另外,

一旦需要查看原始核心内部数据(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的可移植性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/arm6)

如果您的内核不支持内置的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

要开发和构建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

上述命令可以获得到一个可兼容的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程序能够正确地运行在特定的内核上。如果一切顺利,则

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

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

,BCC底层实现逐步转向libbpf。

4. libbpf 库

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

在这里插入图片描述

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、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 语言开发库时需要注意,

当涉及到选择库和工具来与 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工具链,

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

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

,与内核提供的 libbpf 类似。,更加方便使用 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跟踪点。

标签: 连接器挂钩

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台