Loading [MathJax]/jax/input/TeX/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >eunomia-bpf: 让 eBPF 程序的开发和部署尽可能简单

eunomia-bpf: 让 eBPF 程序的开发和部署尽可能简单

作者头像
云微
发布于 2023-02-24 11:59:12
发布于 2023-02-24 11:59:12
87000
代码可运行
举报
运行总次数:0
代码可运行

传统来说, eBPF 的开发方式主要有 BCC、libbpf 等方式。要完成一个 BPF 二进制程序的开发,需要搭建开发编译环境,要关注目标系统的内核版本情况,需要掌握从 BPF 内核态到用户态程序的编写,以及如何加载、绑定至对应的 HOOK 点等待事件触发,最后再对输出的日志及数据进行处理。

我们希望有这样一种 eBPF 的编译和运行工具链,就像其他很多语言一样:

  • 大多数用户只需要关注 bpf.c 程序本身的编写,不需要写任何其他的什么 Python, Clang 之类的用户态辅助代码框架; 这样我们可以很方便地分发、重用 eBPF 程序本身,而不需要和某种或几种语言的生态绑定;
  • 最大程度上和主流的 libbpf 框架实现兼容,原先使用 libbpf 框架编写的代码几乎不需要改动即可移植;eunomia-bpf 编写的 eBPF 程序也可以使用 libbpf 框架来直接编译运行;
  • 本地只需要下载一个很小的二进制运行时,没有任何的 Clang LLVM 之类的大型依赖,可以支持热插拔、热更新; 也可以作为 Lua 虚拟机那样的小模块直接编译嵌入其他的大型软件中,提供 eBPF 程序本身的服务;运行和启动时资源占用率都很低;
  • 让 eBPF 程序的分发和使用像网页和 Web 服务一样自然(Make eBPF as a service): 支持在集群环境中直接通过一次请求进行分发和热更新,仅需数十 kB 的 payload, <100ms 的更新时间,和少量的 CPU 内存占用即可完成 eBPF 程序的分发、部署和更新; 不需要执行额外的编译过程,就能得到 CO-RE 的运行效率;

C 语言 的 Hello World 开始

还记得您第一次写 C 语言Hello World 程序 吗?首先,我们需要一个 .c 文件,它包含一个 main 函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

我们叫它 hello.c,接下来就只需要这几个步骤就好:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# if you are using Ubuntu without a c compiler
sudo apt insalll build-essentials
# compile the program
gcc -o hello hello.c
# run the program
./hello

只需要写一个 c 文件,执行两行命令就可以运行;大多数情况下你也可以把编译好的可执行文件直接移动到其他同样架构的机器或不同版本的操作系统上,然后运行它,也会得到一样的结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Hello World!

eunomia-bpf 的 Hello World

首先,我们需要一个 bpf.c 文件,它就是正常的、合法的 C 语言代码,和 libbpf 所使用的完全相同:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

typedef int pid_t;

char LICENSE[] SEC("license") = "Dual BSD/GPL";

SEC("tp/syscalls/sys_enter_write")
int handle_tp(void *ctx)
{
 pid_t pid = bpf_get_current_pid_tgid() >> 32;
 bpf_printk("BPF triggered from PID %d.\n", pid);
 return 0;
}

假设它叫 hello.bpf.c,并且假设他在 /path/to/repo 目录下,接下来的步骤:

Install

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ # 下载安装 ecli 二进制
$ wget https://aka.pw/bpf-ecli -O /usr/local/ecli && chmod +x /usr/local/ecli
$ # 使用容器进行编译,生成一个 package.json 文件,里面是已经编译好的代码和一些辅助信息
$ docker run -it -v /path/to/repo:/src yunwei37/ebpm:latest
$ # 运行 eBPF 程序(root shell)
$ sudo ecli run package.json  

使用 docker 的时候需要把包含 .bpf.c 文件的目录挂载到容器的 /src 目录下,目录中只有一个 .bpf.c 文件;

它会追踪所有进行 write 系统调用的进程的 pid:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
cat-42755   [003] d...1 48755.529860: bpf_trace_printk: BPF triggered from PID 42755.
             cat-42755   [003] d...1 48755.529874: bpf_trace_printk: BPF triggered from PID 42755.

我们编译好的 eBPF 代码同样可以适配多种内核版本,可以直接把 package.json 复制到另外一个机器上,然后不需要重新编译就可以直接运行(CO-RE:Compile Once Run Every Where);也可以通过网络传输和分发 package.json,通常情况下,压缩后的版本只有几 kb 到几十 kb。

添加 map 记录数据

参考:https://github.com/eunomia-bpf/eunomia-bpf/tree/master/bpftools/examples/bootstrap

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct {
 __uint(type, BPF_MAP_TYPE_HASH);
 __uint(max_entries, 8192);
 __type(key, pid_t);
 __type(value, u64);
} exec_start SEC(".maps");

添加 map 的功能和 libbpf 没有任何区别,只需要在 .bpf.c 中定义即可。

使用 ring buffer 往用户态发送数据

参考:https://github.com/eunomia-bpf/eunomia-bpf/tree/master/bpftools/examples/bootstrap

只需要定义一个头文件,包含你想要发送给用户态的数据格式,以 .bpf.h 作为后缀名:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2020 Facebook */
#ifndef __BOOTSTRAP_H
#define __BOOTSTRAP_H

#define TASK_COMM_LEN 16
#define MAX_FILENAME_LEN 127

struct event {
 int pid;
 int ppid;
 unsigned exit_code;
 unsigned long long duration_ns;
 char comm[TASK_COMM_LEN];
 char filename[MAX_FILENAME_LEN];
 unsigned char exit_event;
};

#endif /* __BOOTSTRAP_H */

在代码中定义环形缓冲区之后,就可以直接使用它:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct {
 __uint(type, BPF_MAP_TYPE_RINGBUF);
 __uint(max_entries, 256 * 1024);
} rb SEC(".maps");

SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
    ......
 e->exit_event = false;
 e->pid = pid;
 e->ppid = BPF_CORE_READ(task, real_parent, tgid);
 bpf_get_current_comm(&e->comm, sizeof(e->comm));
 /* successfully submit it to user-space for post-processing */
 bpf_ringbuf_submit(e, 0);
 return 0;
}

eunomia-bpf 会自动去源代码中找到对应的 ring buffer map,并且把 ring buffer 和类型信息记录在编译好的信息中,并在运行的时候自动完成对于 ring buffer 的加载、导出事件等工作。所有的 eBPF 代码和原生的 libbpf 程序没有任何区别,使用 eunomia-bpf 开发的代码也可以在 libbpf 中无需任何改动即可编译运行。

使用 perf event array 往用户态发送数据

使用 perf event 的原理和使用 ring buffer 非常类似,使用我们的框架时,也只需要在头文件中定义好所需导出的事件,然后定义一下 perf event map:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct {
	__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
	__uint(key_size, sizeof(u32));
	__uint(value_size, sizeof(u32));
} events SEC(".maps");

可以参考:https://github.com/eunomia-bpf/eunomia-bpf/tree/master/bpftools/examples/opensnoop 它是直接从 libbpf-tools 中移植的实现;

eunomia-bpf 的实现原理

eunomia-bpf 主要分为两个部分:

一个运行时库 eunomia-bpfhttps://github.com/eunomia-bpf/eunomia-bpf 一个 eBPF 编译工具链 eunomia-cchttps://github.com/eunomia-bpf/eunomia-cc

使用 github-template 实现远程编译

由于 eunomia-bpf 的编译和运行阶段完全分离,可以实现在 github 网页上编辑之后,通过 github actions 来完成编译,之后在本地一行命令即可启动:

  1. 将此 github.com/eunomia-bpf/ebpm-template 用作 github 模板:请参阅 creating-a-repository-from-a-template
  2. 修改 bootstrap.bpf.c, commit 并等待工作流停止
  3. 我们配置了 github pages 来完成编译好的 json 的导出,之后就可以实现 ecli 使用远程 url 一行命令即可运行:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# curl https://eunomia-bpf.github.io/ebpm-template/package.json | ecli run

通过 API 进行热插拔和分发:

由于 eunomia-cc 编译出来的 ebpf 程序代码和附加信息很小(约数十kb),且不需要同时传递任何的额外依赖,因此我们可以非常方便地通过网络 API 直接进行分发,也可以在很短的时间(大约100ms)内实现热插拔和热更新。我们提供了一个简单的 client 和 server,请参考;

使用 Prometheus 或 OpenTelemetry 进行可观测性数据收集

使用 Rust 编写的可观测性的 exporter,将 eBPF 程序上报的数据暴露给用户态:eunomia-exporter

使用说明

我们使用与 libbpf 相同的 c eBPF 代码,因此大多数 libbpf eBPF c 代码无需任何修改即可运行。

支持的 eBPF 程序类型:kprobe, tracepoint, fentry, lsm 未来我们会增加更多;

如果你想使用环形缓冲区来导出事件,你需要添加 your_program.bpf.h 到你的 repo,并在其中定义导出数据类型,导出数据类型应该是 C struct,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct process_event {
    int pid;
    int ppid;
    unsigned exit_code;
    unsigned long long duration_ns;
    char comm[TASK_COMM_LEN];
    char filename[MAX_FILENAME_LEN];
    int exit_event;
};

导出的名称和字段类型不受限制,但最好使用标准 C 类型。如果标头中存在多个 struct,我们现阶段将使用第一个作为导出的数据类型。仅当我们发现 eBPF 程序中存在 BPF_MAP_TYPE_RINGBUF 的 map 时,才启用环形缓冲区导出的功能。

目前部分低版本内核对于 eBPF 模块的支持并不完善,同时也可能没有附带 BTF 信息,因此有可能一些 eBPF 程序在低版本内核上运行时不能得到预期的结果。详情请参考 libbpf 对应的文档:libbpf/libbpf 我们未来会在这方面继续提升兼容性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
eBPF 入门开发实践教程一:介绍与快速上手
Linux内核一直是实现监控/可观测性、网络和安全功能的理想地方, 但是直接在内核中进行监控并不是一个容易的事情。在传统的Linux软件开发中, 实现这些功能往往都离不开修改内核源码或加载内核模块。修改内核源码是一件非常危险的行为, 稍有不慎可能便会导致系统崩溃,并且每次检验修改的代码都需要重新编译内核,耗时耗力。
云微
2023/02/24
1.6K0
eBPF 入门开发实践教程十一:在 eBPF 中使用 libbpf 开发用户态程序并跟踪 exec() 和 exit() 系统调用
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/08/14
1.2K0
三分钟学会编写 eBPF 程序:使用 eBPF 程序监控打开文件路径并使用 Prometheus 可视化
通过对 open 系统调用的监测,opensnoop可以展现系统内所有调用了 open 系统调用的进程信息。
云微
2023/02/24
1.1K0
三分钟学会编写 eBPF 程序:使用 eBPF 程序监控打开文件路径并使用 Prometheus 可视化
eBPF 入门开发实践教程一:Hello World,基本框架和开发流程
在本篇博客中,我们将深入探讨eBPF(Extended Berkeley Packet Filter)的基本框架和开发流程。eBPF是一种在Linux内核上运行的强大网络和性能分析工具,它为开发者提供了在内核运行时动态加载、更新和运行用户定义代码的能力。这使得开发者可以实现高效、安全的内核级别的网络监控、性能分析和故障排查等功能。
云微
2023/08/14
1.5K0
eBPF 入门实践教程十二:使用 eBPF 程序 profile 进行性能分析
本教程将指导您使用 libbpf 和 eBPF 程序进行性能分析。我们将利用内核中的 perf 机制,学习如何捕获函数的执行时间以及如何查看性能数据。
云微
2023/08/14
1.1K1
eBPF 入门开发实践教程八:在 eBPF 中使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/02/24
6900
eBPF 入门开发实践指南二:Hello World,基本框架和开发流程
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/02/24
9840
eBPF 入门实践教程七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/08/14
6500
eBPF 入门开发实践教程九:捕获进程调度延迟,以直方图方式记录
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/08/14
6390
结合 Wasm 的 eBPF 极简开发工具介绍:eunomia-bpf
对比 Web 的发展,eBPF 与内核的关系有点类似于 JavaScript 与浏览器内核的关系。
云微
2023/02/24
5230
eBPF 入门开发实践指南一:介绍 eBPF 的基本概念、常见的开发工具
Linux内核一直是实现监控/可观测性、网络和安全功能的理想地方,但是直接在内核中进行监控并不是一个容易的事情。在传统的Linux软件开发中,实现这些功能往往都离不开修改内核源码或加载内核模块。修改内核源码是一件非常危险的行为,稍有不慎可能便会导致系统崩溃,并且每次检验修改的代码都需要重新编译内核,耗时耗力。
云微
2023/02/24
8210
操作系统大赛:基于 eBPF 的容器监控工具 Eunomia 初赛报告(系统设计、ebpf 探针设计)
进程的追踪模块本项目主要设置了两个 tracepoint 挂载点。 第一个挂载点形式为
云微
2023/02/24
7110
操作系统大赛:基于 eBPF 的容器监控工具 Eunomia 初赛报告(系统设计、ebpf 探针设计)
Eunomia: 让 ebpf 程序的分发和使用像网页和 web 服务一样自然
eBPF 是一项革命性的技术,它能在操作系统内核中运行沙箱程序。被用于安全并有效地扩展内核的能力而无需修改内核代码或者加载内核模块。
云微
2022/08/26
7530
eBPF 入门开发实践教程八:在 eBPF 中使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具。它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/08/14
4340
eBPF 入门开发实践指南五:在 eBPF 中使用 uprobe 捕获 bash 的 readline 函数调用
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/02/24
1.1K0
eBPF 入门开发实践教程零:介绍 eBPF 的基本概念、常见的开发工具
eBPF 是一项革命性的技术,起源于 Linux 内核,可以在操作系统的内核中运行沙盒程序。它被用来安全和有效地扩展内核的功能,而不需要改变内核的源代码或加载内核模块。eBPF 通过允许在操作系统内运行沙盒程序,应用程序开发人员可以在运行时,可编程地向操作系统动态添加额外的功能。然后,操作系统保证安全和执行效率,就像在即时编译(JIT)编译器和验证引擎的帮助下进行本地编译一样。eBPF 程序在内核版本之间是可移植的,并且可以自动更新,从而避免了工作负载中断和节点重启。
云微
2023/08/12
2.9K0
eBPF 入门开发实践教程零:介绍 eBPF 的基本概念、常见的开发工具
eBPF 入门开发实践教程六:捕获进程发送信号的系统调用集合,使用 hash map 保存状态
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/08/14
5120
在 WebAssembly 中使用 C/C++ 和 libbpf 编写 eBPF 程序
eBPF(extended Berkeley Packet Filter)是一种高性能的内核虚拟机,可以运行在内核空间中,用来收集系统和网络信息。随着计算机技术的不断发展,eBPF 的功能日益强大,进而被用来构建各种效率高效的在线诊断和跟踪系统,以及安全的网络和服务网格。
云微
2023/02/12
6830
在 WebAssembly 中使用 C/C++ 和 libbpf 编写 eBPF 程序
eBPF 入门开发实践指南六:捕获进程发送信号的系统调用集合,使用 hash map 保存状态
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/02/24
4530
eBPF 入门实践指南七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出
eBPF (Extended Berkeley Packet Filter) 是 Linux 内核上的一个强大的网络和性能分析工具,它允许开发者在内核运行时动态加载、更新和运行用户定义的代码。
云微
2023/02/24
4390
推荐阅读
eBPF 入门开发实践教程一:介绍与快速上手
1.6K0
eBPF 入门开发实践教程十一:在 eBPF 中使用 libbpf 开发用户态程序并跟踪 exec() 和 exit() 系统调用
1.2K0
三分钟学会编写 eBPF 程序:使用 eBPF 程序监控打开文件路径并使用 Prometheus 可视化
1.1K0
eBPF 入门开发实践教程一:Hello World,基本框架和开发流程
1.5K0
eBPF 入门实践教程十二:使用 eBPF 程序 profile 进行性能分析
1.1K1
eBPF 入门开发实践教程八:在 eBPF 中使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出
6900
eBPF 入门开发实践指南二:Hello World,基本框架和开发流程
9840
eBPF 入门实践教程七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出
6500
eBPF 入门开发实践教程九:捕获进程调度延迟,以直方图方式记录
6390
结合 Wasm 的 eBPF 极简开发工具介绍:eunomia-bpf
5230
eBPF 入门开发实践指南一:介绍 eBPF 的基本概念、常见的开发工具
8210
操作系统大赛:基于 eBPF 的容器监控工具 Eunomia 初赛报告(系统设计、ebpf 探针设计)
7110
Eunomia: 让 ebpf 程序的分发和使用像网页和 web 服务一样自然
7530
eBPF 入门开发实践教程八:在 eBPF 中使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出
4340
eBPF 入门开发实践指南五:在 eBPF 中使用 uprobe 捕获 bash 的 readline 函数调用
1.1K0
eBPF 入门开发实践教程零:介绍 eBPF 的基本概念、常见的开发工具
2.9K0
eBPF 入门开发实践教程六:捕获进程发送信号的系统调用集合,使用 hash map 保存状态
5120
在 WebAssembly 中使用 C/C++ 和 libbpf 编写 eBPF 程序
6830
eBPF 入门开发实践指南六:捕获进程发送信号的系统调用集合,使用 hash map 保存状态
4530
eBPF 入门实践指南七:捕获进程执行/退出时间,通过 perf event array 向用户态打印输出
4390
相关推荐
eBPF 入门开发实践教程一:介绍与快速上手
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验