BumbleBee: 如丝般顺滑构建、交付和运行 eBPF 程序
共 5235字,需浏览 11分钟
·
2022-02-17 20:54
1. 前言
几天前,Solo.io[1] 公司在官网博客宣布了开源了一个名称为 BumbleBee[2] 的新项目。该项目专注于简化构建 eBPF 工具的门槛和优化使用体验,通过将 eBPF 程序打包成 OCI 镜像[3],带来了与使用 Docker 一致的体验的构建、分发和运行 eBPF 程序。
BumbleBee 目的是让我们专注于编写 eBPF 代码,其负责自动生成与 eBPF 程序相关的用户空间的代码功能,包括加载 eBPF 程序和将 eBPF 程序的数据作为日志、指标和直方图进行展示。
那么我们为什么需要 BumbleBee 项目来管理 eBPF 程序呢?这需要从 eBPF 技术的特点讲起。
2. 构建和分发 eBPF 工具的挑战
eBPF 技术被称之为近 50 年来操作系统最大的变革,解决了 Linux 内核在上游开发、合并和发行功能缓慢的窘境。eBPF 技术为内核提供了不通过上游实现内核的定制功能的能力,当前已经在可观测、网络和安全等多个领域得到了广泛的应用,尤其是在云原生的技术潮流中,eBPF 技术发挥的能力也越来越重要,诸如当前风头正盛的 Cilium 项目。
但是开发、构建和分发 eBPF 一直以来都是一个高门槛的工作,社区先后推出了 BCC、BPFTrace 等前端绑定工作,大大降低了编写和使用 eBPF 技术的门槛,但是这些工具的源码交付方式,需要运行 eBPF 程序的 Linux 系统上安装配套的编译环境,对于分发带来了不少的麻烦,同时将内核适配的问题在运行时才能验证,也不利于提前发现和解决问题。
近年来,为解决不同内核版本中 eBPF 程序的分发和运行问题,社区基于 BTF 技术推出了 CO-RE 功能(“一次编译,到处运行”),一定程度上实现了通过 eBPF 二进制字节码分发,同时也解决了运行在不同内核上的移植问题,但是对于如何打包和分发 eBPF 二进制代码还未有统一简洁的方式。除了 eBPF 程序,目前我们还需要编写用于加载 eBPF 程序和用于读取 eBPF 程序产生数据的各种代码,这往往涉及到通过源码复制粘贴来解决部分问题。
此外,libbpf-bootstrap[4] 通过 bpftool 工具生成相关的脚手架代码,一定程度上解决了通用代码重复编写的问题,但是对于构建、分发和运行 eBPF 程序上提供的帮助有限。
3. BumbleBee 简介
BumbleBee 项目正是 Solo 公司在企业服务网格 Gloo-Mesh[5] 项目中为方便应用 eBPF 技术而诞生中,其用于解决在构建、分发和运行 eBPF 程序遇到的重复性挑战,
当前该项目还在早期(当前版本 0.0.9),提供的功能场景(Network 和 FileSystem)有限,但是基于特定模板能力来构建 OCI 镜像的思路,为我们在管理 eBPF 程序方面提供了一种高效简洁的实现,值得我们关注。
使用 BumbleBee 工具的前置依赖:运行的 eBPF 的操作系统开启了 BTF 支持,编写的 eBPF 代码也需要使用 CO-RE 相关函数,关于 CO-RE 相关的技术可以参考这里[6]。
BumbleBee 提供了与 Docker 一致的体验感觉。下图是 Docker 的高层次示意图,BumbleBee 工具完全参考了这个流程。
3.1 构建
BumbleBee 打造 "恰到好处" 的 eBPF 工具链,将 eBPF 程序的构建过程自动化,让你专注于代码本身。BumbleBee 的 eBPF 代码打包成一个 OCI 标准镜像,这样就可以在基础设施中进行分发。
下述命令可实现将 eBPF 程序 probe.c 的直接编译和打包成镜像 my_probe:v1 。
$ bee build probe.c username/my_probe:v1
3.2 发布
利用 BTF 和 OCI 打包能力,BumbleBee 编写的 eBPF 代码是可移植的,并且可以嵌入到现有的发布工作流程中。通过将 eBPF 代码构建的镜像,推送到任何符合 OCI 标准的镜像仓库,就可以实现发布给其他用户使用。
下述命令实现了将镜像发布至镜像仓库的功能,使用时可直接使用 bee run 基于镜像运行。
# 推送
$ bee push username/my_probe:v1
# 拉取
$ bee pull username/my_probe:v1
3.3 运行
使用 BumbleBee 提供的 CLI 界面和保存在镜像仓库中的镜像,我们可快速在其他地方运行。BumbleBee 不但构建了用户空间代码,而且可以利用 eBPF map,来展示日志、指标和柱状图信息。BumbleBee 使用了 BTF 格式自审能力,获知到需要显示哪些数据类型。
$ bee run my_probe:v1
下面让我们通过一个完整的样例,来体验 BumbleBee 带给我们管理 eBPF 程序的便利。
4. 完整体验
4.1 bee 安装
首先我们需要一个运行支持 BTF 内核的 Linux 操作系统,这里推荐直接使用 ubuntu 2110 版本,搭载的内核已经默认支持了 BTF。如果你选择使用 Vagrant 来管理虚拟机,BumbleBee 仓库中提供的 Vagrantfile[7] 文件可以直接使用。或者你可以使用 mulipass[8] 工具直接启动一个 ubuntu 2110 版本的系统。
这里使用仓库提供的脚本安装,当然也可以直接通过 git clone 仓库的方式进行。
为了快速体验,避免某些场景中的权限问题,这里建议直接使用 root 用户进行安装。
ubuntu@ubuntu21-10:~# curl -sL https://run.solo.io/bee/install | BUMBLEBEE_VERSION=v0.0.9 sh
Attempting to download bee version v0.0.9
Downloading bee-linux-amd64...
Download complete!, validating checksum...
Checksum valid.
bee was successfully installed 🎉
Add the bumblebee CLI to your path with:
export PATH=$HOME/.bumblebee/bin:$PATH
Now run:
bee init # Initialize simple eBPF program to run with bee
Please see visit the bumblebee website for more info: https://github.com/solo-io/bumblebee
4.2 Bee init 生成 eBPF 程序脚手架
Bee init 命令可通过问题向导模式生成 eBPF 代码脚手架,功能与 libbpf-bootstrap 有些类似,但是通过向导的方式进行更加容易上手。
$ export PATH=$HOME/.bumblebee/bin:$PATH
# ebpf-test && cd ebpf-test
# bee init
Use the arrow keys to navigate: ↓ ↑ → ←
? What language do you wish to use for the filter: # 步骤 选择编写 eBPF 代码的语言
▸ C # 当前仅支持 C,Rust 可能在未来支持
--------------------------------------------- # 步骤 2 选择 eBPF 程序类型
INFO Selected Language: C
Use the arrow keys to navigate: ↓ ↑ → ←
? What type of program to initialize:
▸ Network # 选择编写 eBPF 程序的类型,当前支持 Network 和 File System
File system # 生成的模板分别对应于 tcp_connet 和 open 函数
--------------------------------------------- # 步骤 3 选择 map 类型
INFO Selected Language: C
INFO Selected Program Type: Network
Use the arrow keys to navigate: ↓ ↑ → ←
? What type of map should we initialize:
▸ RingBuffer
HashMap
--------------------------------------------- # 步骤 4 选择 map 导出类型
INFO Selected Language: C
INFO Selected Program Type: Network
INFO Selected Map Type: HashMap
Use the arrow keys to navigate: ↓ ↑ → ←
? What type of output would you like from your map:
▸ print # map 数据的展现方式,日志打印、计数或者指标导出
counter
gauge
--------------------------------------------- # 步骤 5 eBPF 程序保存文件名
INFO Selected Language: C
INFO Selected Program Type: Network
INFO Selected Map Type: HashMap
INFO Selected Output Type: print
✔ BPF Program File Location: probe.c
---------------------------------------------- # 最终完成整个代码生成向导
INFO Selected Language: C
INFO Selected Program Type: Network
INFO Selected Map Type: HashMap
INFO Selected Output Type: print
INFO Selected Output Type: BPF Program File Location probe.c
SUCCESS Successfully wrote skeleton BPF program
# ls -hl
total 4.0K
-rw-rw-r-- 1 ubuntu ubuntu 2.0K Feb 11 11:33 probe.c
通过 init 命令生成的 probe.c 文件格式大体如下:
#include "vmlinux.h"
#include "bpf/bpf_helpers.h"
#include "bpf/bpf_core_read.h"
#include "bpf/bpf_tracing.h"
#include "solo_types.h"
// 1. Change the license if necessary
char __license[] SEC("license") = "Dual MIT/GPL";
struct event_t {
// 2. Add ringbuf struct data here.
} __attribute__((packed));
// This is the definition for the global map which both our
// bpf program and user space program can access.
// More info and map types can be found here: https://www.man7.org/linux/man-pages/man2/bpf.2.html
struct {
__uint(max_entries, 1 << 24);
__uint(type, BPF_MAP_TYPE_RINGBUF);
__type(value, struct event_t);
} events SEC(".maps.print");
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
{
// Init event pointer
struct event_t *event;
// Reserve a spot in the ringbuffer for our event
event = bpf_ringbuf_reserve(&events, sizeof(struct event_t), 0);
if (!event) {
return 0;
}
// 3. set data for our event,
// For example:
// event->pid = bpf_get_current_pid_tgid();
bpf_ringbuf_submit(event, 0);
return 0;
}
基于生成的代码模板,我们需要填写自己的逻辑,这里不是重点,先略过相关代码,完整代码可在官方开始文档[9]中查看。
4.3 构建 eBPF 程序
构建过程需要使用 Docker 或者类型 Docker 的容器引擎,需要提前进行安装。
# apt install docker.io # 安装 docker
# bee build probe.c my_probe:v1
SUCCESS Successfully compiled "probe.c" and wrote it to "probe.o"
SUCCESS Saved BPF OCI image to my_probe:v1
整个构建过程中我们不需要再涉及 clang 等相关编译命令,只需要通过 bee build 命令输入 eBPF 程序文件名和期望生成的镜像即可,编译完成后,eBPF 程序的二进制字节码 probe.o 会自动添加到镜像 my_probe:v1 中,我们可以使用 bee tag 完成镜像仓库的重新定义。
4.4 发布 eBPF 程序
我们可以通过 bee tag 和 push 子命令完成进行镜像仓库的发布工作。
# bee tag my_probe:v1 dwh0403/my_probe:v1
# bee login
# bee push dwh0403/my_probe:v1
看一下上述的几条命令,是不是有些似曾相识的感觉?
4.5 运行 eBPF 程序
构建镜像后,在本地可直接通过 bee run 来运行,运行后 bee 会自动启动 TUI 界面,来展示我们编写 eBPF 程序中的 map 内容,自动生成的 map 名字有些特殊后缀用于 bee TUI 用户空间的程序来读取对应 map 中数据进行展示,比如生成代码模板中的SEC(".maps.print")
,表示该 map 用于打印。
# bee run my_probe:v1
SUCCESS Fetching program from registry: my_probe:v1
SUCCESS Loading BPF program and maps into Kernel
SUCCESS Linking BPF functions to associated probe/tracepoint
INFO Rendering TUI..
5. 总结
至此,我们完成了整个项目功能的体验,bee init 工具可通过向导模式帮助我们生成 eBPF 代码框架,尽管功能还有些单薄,但是对于我们特定场景的使用不失是一种快速便捷的方式。
bee build/push/run 等子命令,将编译的命令、打包镜像、发布镜像和运行镜像的等诸多步骤进行了极大的精简,非常易用,极大地降低了构建、发布和运行 eBPF 程序的重复成本,不得不为作者的思路点赞。
由于通过 bee 生成的工具基于特定场景,功能丰富度还有限,对于编写复杂情况下的 eBPF 程序和功能丰富的用户空间程序还不能适用,但是其构建、发布和运行的整体思路(甚至部分基础功能)却是我们可以直接使用或者借鉴的。
6. 相关资料
Tutorial[10] Solo.io 开源 BumbleBee,用类 Docker 的体验使用 eBPF[11] BumbleBee: Build, Ship, Run eBPF tools[12] eCHO episode 33: Bumblebee[13]
参考资料
Solo.io: https://www.solo.io/
[2]BumbleBee: https://bumblebee.io/
[3]OCI 镜像: https://github.com/opencontainers/image-spec
[4]libbpf-bootstrap: https://github.com/libbpf/libbpf-bootstrap
[5]企业服务网格 Gloo-Mesh: https://www.solo.io/products/gloo-mesh
[6]这里: https://www.ebpf.top/categories/BPF-CORE/
[7]Vagrantfile: https://github.com/solo-io/bumblebee/blob/main/Vagrantfile
[8]mulipass: https://multipass.run/
[9]开始文档: https://github.com/solo-io/bumblebee/blob/main/docs/getting_started.md
[10]Tutorial: https://github.com/solo-io/bumblebee/blob/main/docs/getting_started.md
[11]Solo.io 开源 BumbleBee,用类 Docker 的体验使用 eBPF: https://www.infoq.cn/news/GqMEBjGZZX4G8mcfwSM3
[12]BumbleBee: Build, Ship, Run eBPF tools: https://www.solo.io/blog/solo-announces-bumblebee/
[13]eCHO episode 33: Bumblebee: https://www.youtube.com/watch?v=AYLpK5zHzCo