[关闭]
@levinzhang 2023-06-23T07:34:43.000000Z 字数 14749 阅读 122

学习eBPF以实现更好的可观测性

by

摘要:

本文分享了学习eBPF的经验,eBPF是一种新的云原生技术,其目标是改善可观测性和安全性工作流。本文将会介绍如何使用相关的工具并将其应用到自己的开发中,请逐步迭代自己的知识,并将其用到更高级的使用场景中。


核心要点

本文分享了学习eBPF的经验,eBPF是一种新的云原生技术,其目标是改善可观测性和安全性工作流。我们可能感觉它的入门门槛很高,通过eBPF工具来辅助生产环境调试的步骤会很多。本文将会介绍如何使用相关的工具并将其应用到自己的开发中,请逐步迭代自己的知识,并将其用到更高级的使用场景中。最后,我们会讨论如何在CI/CD中实现自动化开发及其面临的挑战。

如何开始入门eBPF?

我第一次听说eBPF是在2021年,当时它是与可观测性相关的主题一起出现的,起初我并不能真正理解它的含义。描述中声称这是一种收集事件数据的新方法,有助于提升可观测性,也有助于实现安全的可观测性和实际执行。

实际上,我后来才知道,Falco使用eBPF来探查Kubernetes中容器的活动。我的学习历程是将Falco视为云原生的安全工具,而没有去质疑其底层的技术。“Hacking Kubernetes”一书帮助我完善了对容器运行时、eBPF和安全执行的学习。

KubeCon EU 2022上的eBPF日,以及后续的eBPF峰会活动,都有助于说明这一点。eBPF的学习策略与技术领域的其他知识类似,也就是倾听、做笔记,但你依然无法理解它的所有内容。

参加讲座和阅读文章时,我们经常会遇到一些需要认识的术语模式,比如,我立即记住的术语包括eBPF、BPF、bcc、bpftrace和iovisor。Brendan Gregg的博客也经常被提及。

在一个社区聚会上,通过自由讨论营(barcamp)式的演讲,我问到,“如何开始使用eBPF?”。随后,我们使用三张幻灯片拉开了关于它如何运行的讨论,一起验证了相关的知识,并思考了其使用场景。在eBPF峰会上,有一个名为 Capture-the-Flag的环境可以进行学习,这吸引我停下脚步并亲自进行探索挑战。随后,我决定在自己的公共学习平台o11y.love上收集所有的eBPF资源,并决定以公开的方式进行学习,记录在这个过程中遇到的所有错误、误解和问题。

内核开发听起来很难,而且理解和入门eBPF可能存在一定的障碍。对于利用eBPF的工具和库,改变使用它们的方法,并配合生产环境的用例(例如在生产中进行调试),这极大地帮助了我的学习和迭代。对Linux操作系统、资源处理和故障排除的一般理解也很有助益。

更高层次的阐述和ebpf.io上的描述图片有助于对eBPF架构的一般理解。我非常喜欢来自Brendan Gregg的解释

“eBPF对Linux的作用就像JavaScript对HTML的作用。(某种程度上,可以这么说。)因此,JavaScript可以让我们定义在点击鼠标等事件中运行的小型程序,而不再是静态的HTML站点,这些程序会在浏览器的安全虚拟机中运行。有了eBPF之后,我们不再是一个固定的内核,而是可以编写在磁盘I/O等事件上运行的小型程序,这些程序会在内核的安全虚拟机中运行。实际上,eBPF更像是运行JavaScript的v8虚拟机,而不是JavaScript本身。eBPF是Linux内核的一部分。”

eBPF被添加到Linux内核中,以实现小型的沙箱程序。这兼顾了稳定的内核需求和少量的创新可能性,而eBPF程序能够有助于扩展和驱动创新,而不会阻碍内核的发展。

eBPF的用例包括高性能网络和负载均衡、应用程序的追踪和性能问题的排查。此外,细粒度的安全可观测性和应用/容器的运行时安全执行也是我能想到的场景。

编写eBPF程序是很难的,内核期望的是字节码,但是它手动编写的效率并不高。因此,需要有一个抽象层,包括从更高级的编程语言生成字节码的编译器。在这种情况下,经常涉及到的工具是Cilium、bcc和bpftrace。eBPF程序的校验发生在从字节码向机器特定指令集的即时编译过程中。这使得在CI/CD工作流中进行静态校验更加困难。稍后,我们会看到更多这方面的内容。

在了解了需求之后,真正的问题在于,我们有什么实际的例子可以尝试和学习,然后深入研究实际的源码?

正式开始:游乐场

Brendan Gregg的学习eBPF跟踪:教程和样例博文是一个很好的起点。不同的尝试和路线最终都会回到这里进行自学。在深入研究库和eBPF程序如何构建之前,在命令行上尝试不同的工具并测试它们的效果,这是一个很好的策略。

注意:Liz Rice的“Learning eBPF”一书能够有助于进一步降低入门门槛,该书于2023年3月出版。

推荐的入门方式是选择具有最新内核(大约4.17版本)的Linux发行版,如Ubuntu 22.04 LTS。请使用本地虚拟化方法,或在你喜欢的云厂商上生成一个虚拟机。下面的样例使用Hetzner Cloud CLI来生成一个新的Ubuntu虚拟机:

  1. $ hcloud server create --image ubuntu-22.04 --type cx21 --name ebpf-chaos

请根据你的需要重新创建设置过程,可以考虑编写Ansible playbooks或脚本来重复安装步骤。这对跟团队成员分享具体学习环境中使用的工具和库会很有帮助。本文讨论的工具和想法在GitLab上有基于Ansible的样例。有些默认的工具需要安装(git、wget、curl、htop和docker),还有eBPF、混沌实验和可观测性等更具体的用例。

接下来的章节将讨论eBPF工具的样例。要构建和安装它们,需要Linux内核头文件和额外的依赖。在Ubuntu 22 LTS上还有一个额外的步骤就是启用DDebs仓库,以访问调试符号(debug symbol),接下来是一个完整的编译器工具链。该针对eBPF的Ansible配置详细描述了安装步骤。你可以查看Git的历史记录,了解学习的步骤以及这个过程中的错误。下面的几节主要是运行这些工具,并阐述它们的使用场景。

跟踪系统调用

你可能已经使用过strace命令来跟踪运行中的二进制文件的系统调用,查看是否有文件被打开和权限错误等。Brendan Gregg的教程博客建议从提供execsnoop命令的bcc toolchain开始。它可以跟踪exec()系统调用。一个很容易的测试方法是打开SSH连接,或者在另外一个终端上执行curl opsindev.news命令。

  1. $ execsnoop -t
  2. 115.816 curl 879320 879305 0 /usr/bin/curl opsindev.news
  3. 118.481 sshd 879322 67197 0 /usr/sbin/sshd -D -R
  4. 124.287 sshd 879324 67197 0 /usr/sbin/sshd -D -R

我们已经学习了一种跟踪系统调用的新方法。bcc工具链提供了更多实用的工具和用例。从学习的角度来讲,还有哪些工具可以用来深入研究eBPF呢?

bpftrace:高级的跟踪语言

Bpftrace提供了自己的高层级跟踪语言,类似于DTrace这样的调试框架。乍看上去,在线样例可能会让人无所适从,但由于我们使用的是测试虚拟机,所以可以运行这些样例,以后再分析语言。Bpftrace允许我们跟踪更多的系统调用,例如open()。这个方法可以用来打开文件、套接字等,更通用地来讲,是进程可以打开的所有内容,不管是善意还是恶意的。它可以视为strace命令的一种更为现代的方式。

为了使用可预测的样例来测试bpftrace,我们可以使用这个最小化的C程序,它打开一个文件句柄来创建新文件(源码):

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <sys/types.h>
  4. #include <sys/stat.h>
  5. #include <fcntl.h>
  6. #include <unistd.h>
  7. int main()
  8. {
  9. int fd;
  10. if ((fd=open("ebpf-chaos.txt", O_WRONLY | O_CREAT, 0660)) == -1)
  11. {
  12. printf("Cannot open file.");
  13. exit(1);
  14. }
  15. close(fd);
  16. }

使用gcc编译器编译C程序,并在启动bpftrace命令后运行它。如果opensnoop.bt命令在Ubuntu 22 LTS上运行失败的话,请从DDeb仓库安装调试符号。

  1. $ gcc sim-open-file.c -o sim-open-file
  2. $ chmod +x sim-open-file
  3. $ ./sim-open-file
  1. $ bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
  2. Attaching 1 probe...
  3. sim-open-call /etc/ld.so.cache
  4. sim-open-call /lib/x86_64-linux-gnu/libc.so.6
  5. sim-open-call

跟踪语言允许挂钩进入特定的系统调用。要找到正确的系统调用名称,需要慢慢试验,可能还会遇到错误。我不得不将sys_enter_open改为sys_enter_openat来触发C程序中的打开文件的调用。bpftrace -l可以列出所有可跟踪的系统调用。

  1. $ bpftrace -l 'tracepoint:syscalls:sys_enter_open*'
  2. tracepoint:syscalls:sys_enter_open
  3. tracepoint:syscalls:sys_enter_open_by_handle_at
  4. tracepoint:syscalls:sys_enter_open_tree
  5. tracepoint:syscalls:sys_enter_openat
  6. tracepoint:syscalls:sys_enter_openat2

上述代码会将命令和文件名的路径打印到终端上。访问要打印的文件名需要阅读C结构的代码,以了解在这种情况下,哪些属性是可用的。

学习曲线的“顿悟时刻(aha moment)”不仅仅会看到文件打开和写入调用,而且还会加载库的依赖关系(stdlib需要libc)。bfptrace工具对于验证二进制文件是否真的加载了某些库是非常有用的,其次是使用lddnm来窥探依赖关系和调试符号。

  1. $ ldd sim-open-call
  2. linux-vdso.so.1 (0x00007ffe42c78000)
  3. libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f24c7247000)
  4. /lib64/ld-linux-x86-64.so.2 (0x00007f24c747d000)
  5. $ nm sim-open-call | grep open
  6. U open@GLIBC_2.2.5

深入研究源码和eBPF程序

BPF编译器集合(BPF Compiler Collection,BCC)提供了一些样例来学习内核和用户空间之间的数据传输和交互。以前的样例只是挂钩系统调用并立即返回。BCC在C代码中提供了内核插装,并允许使用Python或Lua编写前端用户空间的应用。按照描述,使用场景包括性能分析和网络流量控制,这都是很好的洞察点,并为以后的知识验证增加了学习难度。Python和C语言知识有助于更容易地深入研究这些样例。

另外,基于我的研究过程,推荐libbpf库,因为它的bootstrap项目提供了更多的演示应用。它们提供了真实的程序,可以用来实现自己的第一个eBPF程序。其中有一个样例是使用Rust编写的,允许我们按照XDP规范检查网络流量以及数据包的大小。eXpress Data Path(XDP)允许在大规模网络调用时挂钩发送/接收的网络数据包,这会发生在中断之后和内存分配之前。例如,这可以用来悄悄地丢弃数据包(请注意后面高级的eBPF程序开发用例)。

用户需要指定端口号,这会导致再一轮的试验和错误排查。使用eth0作为接口名称无法成功运行。这个样例的输出源自同一台主机上运行的Prometheus服务器实例,产生的网络流量来自以HTTP端点探查监控目标的输出。

  1. $ apt install rustc cargo clang rustfmt
  2. $ git clone https://github.com/libbpf/libbpf-bootstrap
  3. $ cd libbpf-bootstrap/examples/rust
  4. $ cargo build
  5. $ cargo run
  6. $ sudo ./target/debug/xdp 1 #if number
  7. $ sudo tail -f /sys/kernel/debug/tracing/trace_pipe
  8. prometheus-660 [001] d.s11 295903.782373: bpf_trace_printk: packet size: 74
  9. prometheus-659 [000] d.s11 295903.782735: bpf_trace_printk: packet size: 74
  10. prometheus-659 [000] d.s11 295903.782762: bpf_trace_printk: packet size: 54
  11. prometheus-671 [001] d.s11 295908.509751: bpf_trace_printk: packet size: 352
  12. prometheus-671 [001] d.s11 295908.513184: bpf_trace_printk: packet size: 4162
  13. prometheus-671 [001] d.s11 295908.513218: bpf_trace_printk: packet size: 66
  14. prometheus-671 [001] d.s11 295908.513295: bpf_trace_printk: packet size: 4162
  15. prometheus-671 [001] d.s11 295908.513307: bpf_trace_printk: packet size: 66
  16. prometheus-671 [001] d.s11 295908.513368: bpf_trace_printk: packet size: 1630

在构建和运行更多的样例后,我们并不完全清楚复制或修改源码是否为一个好的策略。如何将XDP样例缩减至最小的尺寸?也许有更好的方法来逐步入手编写eBPF程序代码,并增加学习过程中获得的经验。

eBPF程序开发,学习更多用例

在深入研究如何开发自己的程序之前,了解BPF和eBPF的基础知识是很重要的。eBPF是Berkley Packet Filter(BPF)的一个扩展版本,它提供了一个运行在Linux内核中的抽象虚拟机,在受控的环境中运行eBPF程序。从根本上说,Linux内核中的“老”BPF标准可以被称为“经典BPF”,以便于和eBPF进行区分

我们可以从尝试bcc工具开始,运行bpftrace并识别在日常业务和事件中有助于SRE和DevOps工程师的用例。这可能包括跟踪程序的启动/退出、查看控制组(cgroups)、观察TCP连接、检查网络接口等等。建议尽可能保持用例的简单性,以确保稳定的学习曲线。

在验证了关于eBPF的基础知识并定义了用例之后,请以库和工具链的形式探寻抽象的概念。现代编译器和库可用于Go、Rust和C/C++。在决定编写eBPF程序之前,建议先学习基本的编程语言。根据我自己的经验,在具有C++或Python知识之后,学习Rust是一条可行的发展道路。这有助于避免内存处理相关的运行时错误,与C/C++ eBPF程序相比,可以说这是一种更安全的方法。

Cillium在一个Golang的开源库中实现了它的eBPF功能。除了学习编写自己的eBPF程序外,该库还提供了如下用例:将程序附加到入口/出口、计算egress流量包,以及探查网络接口(请注意XDP术语,以供后续学习)。XDP程序可以用Go编译器工具链进行构建,并接受接口名称作为命令行参数。它使用map来持久化特定IP地址的网络包的数量;对于Kubernetes节点上的任意类型的网络接口,探查容器流量或跟踪嵌入式硬件的流量都是很好的使用场景。

如果你觉得编写Rust代码更舒服的话,aya-rs的维护者提供了一个Rust开发人员工具链,包含一本带有教程的图书。书中的样例实现了一个类似的XDP网络流量场景,可以直接从Cargo构建链中运行,使开发过程更加高效。

  1. $ git clone https://github.com/aya-rs/book aya-rs-book
  2. $ cd examples/xdp-hello
  3. $ cargo install bpf_linker
  4. $ cargo xtask build-ebpf
  5. $ cargo build
  6. $ RUST_LOG=info cargo xtask run

样例程序没有跟踪IP地址及其数据包的数量,但是这可以作为一个很好的练习,模仿Go库样例中的行为。

aya-rs的其他实际用例是持续剖析(profiling),Polar Signals的开发人员将Rust库用到了Parca代理中,用于自动的函数调用栈分析和更好的内存安全性(来自KubeCon EU 2022 eBPF日上的幻灯片Pull Request)。

有不同的方式来着手开发eBPF程序。请记住,该架构遵循将字节码编译的eBPF程序加载到内核,并需要一个用户空间的“收集器(collector)”或“打印器(printer)”。通信是通过套接字或文件句柄进行的。

测试和校验eBPF程序

在CI/CD流水线中自动化测试eBPF程序是很棘手的事情,因为内核会在加载时验证eBPF程序并拒绝潜在的不安全程序。测试将会需要一个新的虚拟机沙箱,加载eBPF程序,并模拟内核和eBPF程序相关的行为。需求包括触发事件,再次触发eBPF程序代码所订阅的钩子。根据不同的目的,这会涉及到不同的内核接口和系统调用(网络、文件访问等)。创建一个独立的单元测试mock是很难的,需要开发人员模拟一个运行中的内核。

有人试图将eBPF验证器转移到内核之外,并允许在CI/CD中测试eBPF程序。同时,在CI/CD中加载eBPF程序需要一个运行中的Linux虚拟机,其CI/CD的runner/executor要具有较高的权限。在Ubuntu 22 LTS中,加载非特权程序默认已被禁用,可能需要通过运行sudo sysctl kernel.unprivileged_bpf_disabled=0来启用。

CI/CD中的持续测试

为了提供持续测试的CI/CD runner环境,建议使用Ansible/Terraform生成一个Linux虚拟机,安装CI/CD runner,将其注册到CI/CD服务器上,并准备好加载和运行eBPF程序的需求。对于不同的供应商来说,这是一个通用的模式。下面的样例使用Ansible安装并注册GitLab Runner到GitLab.com项目中,然后使用它来构建和运行eBPF程序。GitLab Runner注册了标签ebpf,它将只会执行使用了该标签的CI/CD job。

  1. ---
  2. - name: GitLab Runner for eBPF
  3. hosts: all
  4. vars:
  5. ansible_python_interpreter: /usr/bin/python3
  6. tasks:
  7. - name: Get GitLab repository installation script
  8. get_url:
  9. url: "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh"
  10. dest: /tmp/gitlab-runner.script.deb.sh
  11. mode: 0744
  12. - name: Install GitLab repository
  13. command: bash /tmp/gitlab-runner.script.deb.sh
  14. args:
  15. creates: "/etc/apt/sources.list.d/runner_gitlab-runner.list"
  16. become: true
  17. - name: Install GitLab Runner
  18. apt:
  19. name: gitlab-runner
  20. state: present
  21. allow_downgrade: true
  22. become: true
  23. environment:
  24. GITLAB_RUNNER_DISABLE_SKEL: "true"
  25. - name: Allow the gitlab-runner user to run any commands as root with sudo -u root
  26. community.general.sudoers:
  27. name: gitlab-runner sudo
  28. state: present
  29. user: gitlab-runner
  30. runas: root
  31. commands: ALL # Review this for production usage. For demos, it is enabled, and forked MR CI/CD builds won't run.

注册需要gl_runner_registration_token变量,该变量来自GitLab项目中针对CI/CD Runners的配置。

  1. ---
  2. - name: GitLab Runner for eBPF - register once
  3. hosts: all
  4. vars:
  5. ansible_python_interpreter: /usr/bin/python3
  6. tasks:
  7. - name: "Configure GitLab Runner (running to populate config.toml)"
  8. command: >
  9. gitlab-runner register
  10. --non-interactive
  11. --url "https://gitlab.com/"
  12. --executor "shell"
  13. --tag-list ebpf
  14. --registration-token="{{ gl_runner_registration_token }}"

GitLab runner可以在项目设置的CI/CD > Runners中看到。

在CI/CD中测试基于Rust的eBPF程序

我们使用一个实际的eBPF程序来尝试一下CI/CD工作流,这里使用aya-rs Rust库模板作为演示样例。首先,在Linux虚拟机上本地安装Rust和所需的eBPF,以验证一切均能正常运行。

  1. curl https://sh.rustup.rs -sSf | sh
  2. source "$HOME/.cargo/env"
  3. rustup install stable
  4. rustup install nightly
  5. rustup default stable
  6. rustup toolchain add nightly
  7. rustup component add rust-src --toolchain nightly
  8. # required for cargo-generate
  9. apt -y install libssl-dev
  10. cargo install cargo-generate
  11. cargo install bpf-linker
  12. cargo install bindgen-cli

接下来,生成一个模板骨架树,用于使用XDP(eXpress Data Path)类型创建一个演示程序。探查ebpf-chaos-demo-xdp/src/main.rs中的代码,并更新网络接口名。然后,构建并运行程序,将日志级别设置为info(或debug)。

  1. cargo generate --name ebpf-chaos-demo-xdp -d program_type=xdp https://github.com/aya-rs/aya-template.git
  2. RUST_LOG=info cargo xtask run

示例代码由两部分组成:ebpf-chaos-demo-xdp-ebpf/src/main.rs中的内核空间eBPF程序和ebpf-chaos-demo-xdp/src/main.rs中的用户空间程序,后者会加载eBPF程序并将其附加至内核跟踪点。为了只构建eBPF程序,我们可以调用build-ebpf xtask并使用llvm-objdump命令检查字节码:

  1. cargo xtask build-ebpf
  2. llvm-objdump -S target/bpfel-unknown-none/debug/ebpf-chaos-demo-xdp

完整的源代码位于该GitLab项目中,可以使用GitLab CI/CD流水线进行测试。注意,它需要在runner环境中安装Rust工具链。随后的流水线运行将会使用配置好的缓存。该流水线有三个job:

对输出分析进行增强以及思考运行eBPF程序的更多测试报告是留给读者的练习。

  1. # eBPF GitLab Runner required for this project
  2. # Note: Various commands need sudo/root access on the Linux host, see ansible-config/.
  3. # By default, for security reasons, CI/CD pipelines are not run from forks in the parent project.
  4. # See https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html#use-with-forked-projects
  5. default:
  6. tags:
  7. - ebpf
  8. stages:
  9. - pre
  10. - build
  11. - run
  12. variables:
  13. RUST_LOG: "info"
  14. RUNTIME: 300 # set to >= 5*60 = 300s because cargo xtask run also compiles the binary first
  15. # These steps should not take long after subsquent runs on the Linux VM
  16. install-deps:
  17. stage: pre
  18. script:
  19. - sudo apt install libssl-dev # required for cargo-generate on Ubuntu 22 LTS
  20. - curl https://sh.rustup.rs -sSf -o rustup.sh
  21. - sh rustup.sh -y --profile default
  22. - source "$HOME/.cargo/env"
  23. - rustup install stable
  24. - rustup install nightly
  25. - rustup default stable
  26. - rustup toolchain add nightly
  27. - rustup component add rust-src --toolchain nightly
  28. # 'cargo install' is not idempotent. --force takes too long. Treat an error as 'ok, installed' here.
  29. - cargo install cargo-generate bpf-linker bindgen-cli || true
  30. aya-rs-xdp-build-ebpf:
  31. stage: build
  32. script:
  33. - cd examples/ebpf-chaos-demo-xdp
  34. - source "$HOME/.cargo/env"
  35. - cargo xtask build-ebpf
  36. - llvm-objdump -S target/bpfel-unknown-none/debug/ebpf-chaos-demo-xdp
  37. aya-rs-xdp-run:
  38. stage: run
  39. # We need to send the cargo xtask run command into the background, capture stdout, kill it after a defined interval, and generate a test report for CI/CD
  40. script:
  41. - cd examples/ebpf-chaos-demo-xdp
  42. - source "$HOME/.cargo/env"
  43. - rm ${CI_PROJECT_DIR}/run.pid
  44. - nohup cargo xtask run > ${CI_PROJECT_DIR}/nohup.out 2>&1 & echo $! > ${CI_PROJECT_DIR}/run.pid
  45. - sleep $RUNTIME
  46. - kill -s TERM `cat ${CI_PROJECT_DIR}/run.pid` || true
  47. - rm ${CI_PROJECT_DIR}/run.pid
  48. - cat "${CI_PROJECT_DIR}/nohup.out"
  49. - echo "Finished running eBPF program. TODO - analyze the output more."
  50. artifacts:
  51. expire_in: 30 days
  52. paths:
  53. - ${CI_PROJECT_DIR}/nohup.out

该截屏显示了运行eBPF程序的job,以及捕获网络数据包的日志输出。根据对源代码的修改,输出会发生变化并且可以进行测试。一个思路是以机器可读的格式总结捕获到的数据包,并在终止时创建一个汇总表。在CI/CD以及命令行中,这种方式更易于消费和理解。

将进程放入后台的方法可能无法正确地唤醒它,这可能需要更好的信号处理实现。它远远谈不上完美,你可以在这个合并请求中看到我的学习历史。可能有更好的方式来构建要发布的二进制文件,并通过supervisorctlsystemd命令来启动它,这是下一个学习步骤。终止和卸载过程的实现比较棘手。下面的代码片段实现了正确的信号处理,但是无法始终从运行中的内核卸载已注册的XDP链接。另一种方法是为每次的CI/CD运行生成一个新的Linux虚拟机,以避免这些可重复性相关的失败。但是,其缺点是我们需要一个Rust构建的远程缓存,以避免较长的CI/CD构建运行时间。

  1. // Implement signal handling for CTRL+C and SIGTERM
  2. use tokio::signal::unix::{signal, SignalKind};
  3. let program: &mut Xdp = bpf.program_mut("ebpf_chaos_demo_xdp").unwrap().try_into()?;
  4. program.load()?;
  5. program.attach(&opt.iface, XdpFlags::default())
  6. .context("failed to attach the XDP program with default flags - try changing XdpFlags::default() to XdpFlags::SKB_MODE")?;
  7. // Implement signal handling for CTRL+C (SIGINT) and SIGTERM
  8. // CTRL+C can be used for terminal tests
  9. // SIGTERM will be sent from CI/CD jobs to the background process
  10. let mut sigterm = signal(SignalKind::terminate())?;
  11. let mut sigint = signal(SignalKind::interrupt())?;
  12. tokio::select! {
  13. _ = sigterm.recv() => { println!("SIGTERM shutting down") }
  14. _ = sigint.recv() => { println!("SIGINT shutting down") }
  15. }
  16. Ok(())
  17. // Destroying the bpf object will detach and cleanup the loaded program.
  18. // Debug with 'bpftool link show'
  19. }

CI/CD和DevSecOps工作流的额外待办事项

剩下的挑战就是扩展eBPF程序以生成测试报告,并创建运行时测试环境,即通过用curl命令运行网络流量测试周期,并验证输出包的确切大小。另外,架构也很重要,要么eBPF程序被加载到内核中,并且有一个用户空间应用来读取其结果,要么eBPF程序是一个单一的二进制文件,直接附加其探针。后者需要在CI/CD job中将程序发送至后台,捕获它的输出,执行测试,然后合并测试报告。对于DevSecOps工作流来说,这个过程还有许多需要改进的地方,但我相信在不久的将来我们会达到最终的目的。

代码覆盖是测试eBPF程序的另一个新领域。目前并没有太多的工具帮助开发人员理解代码在Linux内核中运行时的路径,哪些代码区域会受到影响,哪些代码没有被覆盖到。bpfcov是由Elastic的工程师创建的,以帮助解决这个问题,让开发人员了解eBPF程序的代码执行路径。在CI/CD中运行自动化的代码质量和安全扫描也是一项挑战:如何确定一个有可能拖慢内核操作的编程错误呢?比较有意思的是,我们可以看一下eBPF程序的持续剖析(continuous profiling)是否可以实现(本身就是使用eBPF的,如Parca项目)。还有一些编程模式会规避内核验证器,并造成对软件供应链的安全攻击,通过贡献的拉取和合并请求,将恶意代码注入到已发布的eBPF程序中。这需要DevSecOps工作流来确保安全措施行之有效。AI可能也会提供一些帮助。

结论

eBPF是一种收集可观测性数据的新方法,它有助于实现网络洞察力,以及安全的可观测性和执行。为了获得最好的库、工具和框架,我们需要一起公开学习,以降低知识的壁垒,并使每个人都能做出贡献。从测试现有的工具到编写eBPF程序的详细教程,我们还有很长的路要走。在CI/CD中进行eBPF程序测试和验证是一项重要的工作,接下来就是将所有的想法带到上游,降低使用和贡献eBPF开源项目的入门门槛。

要想开始相关的工作,需要启动一个Linux虚拟机,使用脚本/Ansible进行可重复的设置,并进行测试和开发。当接口名称和内核技术阻碍学习的进度时,那就回退一步,你并没有必要完全理解eBPF的全部内容。当遇到生产环境中断时,对数据收集有一般化的了解能够提供一定的帮助。最后,但同样重要的是,这里有个提示,那就是当调试eBPF程序时,考虑在多个发行版上进行测试,避免遇到内核相关的缺陷。

关于作者

Michael Friedrich

Michael Friedrich是GitLab的高级开发人员布道者,专注于可观测性、DevSecOps和AI。Michael创建了o11y.love作为可观测性学习平台,并在他的opsindev.news通讯中分享技术趋势以及对day-2ops、eBPF、AI/MLOps的见解。在没有旅行和远程工作的时候,他喜欢搭建乐高模型。

查看英文原文:Learning eBPF for Better Observability

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注