@levinzhang
2023-06-23T15:34:43.000000Z
字数 14749
阅读 416
by
本文分享了学习eBPF的经验,eBPF是一种新的云原生技术,其目标是改善可观测性和安全性工作流。本文将会介绍如何使用相关的工具并将其应用到自己的开发中,请逐步迭代自己的知识,并将其用到更高级的使用场景中。
本文分享了学习eBPF的经验,eBPF是一种新的云原生技术,其目标是改善可观测性和安全性工作流。我们可能感觉它的入门门槛很高,通过eBPF工具来辅助生产环境调试的步骤会很多。本文将会介绍如何使用相关的工具并将其应用到自己的开发中,请逐步迭代自己的知识,并将其用到更高级的使用场景中。最后,我们会讨论如何在CI/CD中实现自动化开发及其面临的挑战。
我第一次听说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虚拟机:
$ 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
命令。
$ execsnoop -t
115.816 curl 879320 879305 0 /usr/bin/curl opsindev.news
118.481 sshd 879322 67197 0 /usr/sbin/sshd -D -R
124.287 sshd 879324 67197 0 /usr/sbin/sshd -D -R
我们已经学习了一种跟踪系统调用的新方法。bcc工具链提供了更多实用的工具和用例。从学习的角度来讲,还有哪些工具可以用来深入研究eBPF呢?
Bpftrace提供了自己的高层级跟踪语言,类似于DTrace这样的调试框架。乍看上去,在线样例可能会让人无所适从,但由于我们使用的是测试虚拟机,所以可以运行这些样例,以后再分析语言。Bpftrace允许我们跟踪更多的系统调用,例如open()
。这个方法可以用来打开文件、套接字等,更通用地来讲,是进程可以打开的所有内容,不管是善意还是恶意的。它可以视为strace
命令的一种更为现代的方式。
为了使用可预测的样例来测试bpftrace,我们可以使用这个最小化的C程序,它打开一个文件句柄来创建新文件(源码):
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
if ((fd=open("ebpf-chaos.txt", O_WRONLY | O_CREAT, 0660)) == -1)
{
printf("Cannot open file.");
exit(1);
}
close(fd);
}
使用gcc编译器编译C程序,并在启动bpftrace命令后运行它。如果opensnoop.bt
命令在Ubuntu 22 LTS上运行失败的话,请从DDeb仓库安装调试符号。
$ gcc sim-open-file.c -o sim-open-file
$ chmod +x sim-open-file
$ ./sim-open-file
$ bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
Attaching 1 probe...
sim-open-call /etc/ld.so.cache
sim-open-call /lib/x86_64-linux-gnu/libc.so.6
sim-open-call
跟踪语言允许挂钩进入特定的系统调用。要找到正确的系统调用名称,需要慢慢试验,可能还会遇到错误。我不得不将sys_enter_open
改为sys_enter_openat
来触发C程序中的打开文件的调用。bpftrace -l
可以列出所有可跟踪的系统调用。
$ bpftrace -l 'tracepoint:syscalls:sys_enter_open*'
tracepoint:syscalls:sys_enter_open
tracepoint:syscalls:sys_enter_open_by_handle_at
tracepoint:syscalls:sys_enter_open_tree
tracepoint:syscalls:sys_enter_openat
tracepoint:syscalls:sys_enter_openat2
上述代码会将命令和文件名的路径打印到终端上。访问要打印的文件名需要阅读C结构的代码,以了解在这种情况下,哪些属性是可用的。
学习曲线的“顿悟时刻(aha moment)”不仅仅会看到文件打开和写入调用,而且还会加载库的依赖关系(stdlib
需要libc
)。bfptrace工具对于验证二进制文件是否真的加载了某些库是非常有用的,其次是使用ldd
和nm
来窥探依赖关系和调试符号。
$ ldd sim-open-call
linux-vdso.so.1 (0x00007ffe42c78000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f24c7247000)
/lib64/ld-linux-x86-64.so.2 (0x00007f24c747d000)
$ nm sim-open-call | grep open
U open@GLIBC_2.2.5
BPF编译器集合(BPF Compiler Collection,BCC)提供了一些样例来学习内核和用户空间之间的数据传输和交互。以前的样例只是挂钩系统调用并立即返回。BCC在C代码中提供了内核插装,并允许使用Python或Lua编写前端用户空间的应用。按照描述,使用场景包括性能分析和网络流量控制,这都是很好的洞察点,并为以后的知识验证增加了学习难度。Python和C语言知识有助于更容易地深入研究这些样例。
另外,基于我的研究过程,推荐libbpf库,因为它的bootstrap项目提供了更多的演示应用。它们提供了真实的程序,可以用来实现自己的第一个eBPF程序。其中有一个样例是使用Rust编写的,允许我们按照XDP规范检查网络流量以及数据包的大小。eXpress Data Path(XDP)允许在大规模网络调用时挂钩发送/接收的网络数据包,这会发生在中断之后和内存分配之前。例如,这可以用来悄悄地丢弃数据包(请注意后面高级的eBPF程序开发用例)。
用户需要指定端口号,这会导致再一轮的试验和错误排查。使用eth0
作为接口名称无法成功运行。这个样例的输出源自同一台主机上运行的Prometheus服务器实例,产生的网络流量来自以HTTP端点探查监控目标的输出。
$ apt install rustc cargo clang rustfmt
$ git clone https://github.com/libbpf/libbpf-bootstrap
$ cd libbpf-bootstrap/examples/rust
$ cargo build
$ cargo run
$ sudo ./target/debug/xdp 1 #if number
$ sudo tail -f /sys/kernel/debug/tracing/trace_pipe
prometheus-660 [001] d.s11 295903.782373: bpf_trace_printk: packet size: 74
prometheus-659 [000] d.s11 295903.782735: bpf_trace_printk: packet size: 74
prometheus-659 [000] d.s11 295903.782762: bpf_trace_printk: packet size: 54
prometheus-671 [001] d.s11 295908.509751: bpf_trace_printk: packet size: 352
prometheus-671 [001] d.s11 295908.513184: bpf_trace_printk: packet size: 4162
prometheus-671 [001] d.s11 295908.513218: bpf_trace_printk: packet size: 66
prometheus-671 [001] d.s11 295908.513295: bpf_trace_printk: packet size: 4162
prometheus-671 [001] d.s11 295908.513307: bpf_trace_printk: packet size: 66
prometheus-671 [001] d.s11 295908.513368: bpf_trace_printk: packet size: 1630
在构建和运行更多的样例后,我们并不完全清楚复制或修改源码是否为一个好的策略。如何将XDP样例缩减至最小的尺寸?也许有更好的方法来逐步入手编写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构建链中运行,使开发过程更加高效。
$ git clone https://github.com/aya-rs/book aya-rs-book
$ cd examples/xdp-hello
$ cargo install bpf_linker
$ cargo xtask build-ebpf
$ cargo build
$ 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)”。通信是通过套接字或文件句柄进行的。
在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 runner环境,建议使用Ansible/Terraform生成一个Linux虚拟机,安装CI/CD runner,将其注册到CI/CD服务器上,并准备好加载和运行eBPF程序的需求。对于不同的供应商来说,这是一个通用的模式。下面的样例使用Ansible安装并注册GitLab Runner到GitLab.com项目中,然后使用它来构建和运行eBPF程序。GitLab Runner注册了标签ebpf
,它将只会执行使用了该标签的CI/CD job。
---
- name: GitLab Runner for eBPF
hosts: all
vars:
ansible_python_interpreter: /usr/bin/python3
tasks:
- name: Get GitLab repository installation script
get_url:
url: "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh"
dest: /tmp/gitlab-runner.script.deb.sh
mode: 0744
- name: Install GitLab repository
command: bash /tmp/gitlab-runner.script.deb.sh
args:
creates: "/etc/apt/sources.list.d/runner_gitlab-runner.list"
become: true
- name: Install GitLab Runner
apt:
name: gitlab-runner
state: present
allow_downgrade: true
become: true
environment:
GITLAB_RUNNER_DISABLE_SKEL: "true"
- name: Allow the gitlab-runner user to run any commands as root with sudo -u root
community.general.sudoers:
name: gitlab-runner sudo
state: present
user: gitlab-runner
runas: root
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的配置。
---
- name: GitLab Runner for eBPF - register once
hosts: all
vars:
ansible_python_interpreter: /usr/bin/python3
tasks:
- name: "Configure GitLab Runner (running to populate config.toml)"
command: >
gitlab-runner register
--non-interactive
--url "https://gitlab.com/"
--executor "shell"
--tag-list ebpf
--registration-token="{{ gl_runner_registration_token }}"
GitLab runner可以在项目设置的CI/CD > Runners
中看到。
我们使用一个实际的eBPF程序来尝试一下CI/CD工作流,这里使用aya-rs Rust库模板作为演示样例。首先,在Linux虚拟机上本地安装Rust和所需的eBPF,以验证一切均能正常运行。
curl https://sh.rustup.rs -sSf | sh
source "$HOME/.cargo/env"
rustup install stable
rustup install nightly
rustup default stable
rustup toolchain add nightly
rustup component add rust-src --toolchain nightly
# required for cargo-generate
apt -y install libssl-dev
cargo install cargo-generate
cargo install bpf-linker
cargo install bindgen-cli
接下来,生成一个模板骨架树,用于使用XDP(eXpress Data Path)类型创建一个演示程序。探查ebpf-chaos-demo-xdp/src/main.rs
中的代码,并更新网络接口名。然后,构建并运行程序,将日志级别设置为info(或debug)。
cargo generate --name ebpf-chaos-demo-xdp -d program_type=xdp https://github.com/aya-rs/aya-template.git
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
命令检查字节码:
cargo xtask build-ebpf
llvm-objdump -S target/bpfel-unknown-none/debug/ebpf-chaos-demo-xdp
完整的源代码位于该GitLab项目中,可以使用GitLab CI/CD流水线进行测试。注意,它需要在runner环境中安装Rust工具链。随后的流水线运行将会使用配置好的缓存。该流水线有三个job:
install-deps
准备Rust环境,这需要将CARGO_HOME
变量指定为runner的项目目录。aya-rs-xdp-build-ebpf
构建核心eBPF程序,并运行llvm-objdump
命令。aya-rs-xdp-run
运行用户空间程序,这需要sudo权限。它会将命令放到后台,捕获stdout,睡眠60秒,然后使用pkill
来杀死xtask命令,最后打印捕获到的输出。对输出分析进行增强以及思考运行eBPF程序的更多测试报告是留给读者的练习。
# eBPF GitLab Runner required for this project
# Note: Various commands need sudo/root access on the Linux host, see ansible-config/.
# By default, for security reasons, CI/CD pipelines are not run from forks in the parent project.
# See https://docs.gitlab.com/ee/ci/pipelines/merge_request_pipelines.html#use-with-forked-projects
default:
tags:
- ebpf
stages:
- pre
- build
- run
variables:
RUST_LOG: "info"
RUNTIME: 300 # set to >= 5*60 = 300s because cargo xtask run also compiles the binary first
# These steps should not take long after subsquent runs on the Linux VM
install-deps:
stage: pre
script:
- sudo apt install libssl-dev # required for cargo-generate on Ubuntu 22 LTS
- curl https://sh.rustup.rs -sSf -o rustup.sh
- sh rustup.sh -y --profile default
- source "$HOME/.cargo/env"
- rustup install stable
- rustup install nightly
- rustup default stable
- rustup toolchain add nightly
- rustup component add rust-src --toolchain nightly
# 'cargo install' is not idempotent. --force takes too long. Treat an error as 'ok, installed' here.
- cargo install cargo-generate bpf-linker bindgen-cli || true
aya-rs-xdp-build-ebpf:
stage: build
script:
- cd examples/ebpf-chaos-demo-xdp
- source "$HOME/.cargo/env"
- cargo xtask build-ebpf
- llvm-objdump -S target/bpfel-unknown-none/debug/ebpf-chaos-demo-xdp
aya-rs-xdp-run:
stage: run
# 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
script:
- cd examples/ebpf-chaos-demo-xdp
- source "$HOME/.cargo/env"
- rm ${CI_PROJECT_DIR}/run.pid
- nohup cargo xtask run > ${CI_PROJECT_DIR}/nohup.out 2>&1 & echo $! > ${CI_PROJECT_DIR}/run.pid
- sleep $RUNTIME
- kill -s TERM `cat ${CI_PROJECT_DIR}/run.pid` || true
- rm ${CI_PROJECT_DIR}/run.pid
- cat "${CI_PROJECT_DIR}/nohup.out"
- echo "Finished running eBPF program. TODO - analyze the output more."
artifacts:
expire_in: 30 days
paths:
- ${CI_PROJECT_DIR}/nohup.out
该截屏显示了运行eBPF程序的job,以及捕获网络数据包的日志输出。根据对源代码的修改,输出会发生变化并且可以进行测试。一个思路是以机器可读的格式总结捕获到的数据包,并在终止时创建一个汇总表。在CI/CD以及命令行中,这种方式更易于消费和理解。
将进程放入后台的方法可能无法正确地唤醒它,这可能需要更好的信号处理实现。它远远谈不上完美,你可以在这个合并请求中看到我的学习历史。可能有更好的方式来构建要发布的二进制文件,并通过supervisorctl
或systemd
命令来启动它,这是下一个学习步骤。终止和卸载过程的实现比较棘手。下面的代码片段实现了正确的信号处理,但是无法始终从运行中的内核卸载已注册的XDP链接。另一种方法是为每次的CI/CD运行生成一个新的Linux虚拟机,以避免这些可重复性相关的失败。但是,其缺点是我们需要一个Rust构建的远程缓存,以避免较长的CI/CD构建运行时间。
// Implement signal handling for CTRL+C and SIGTERM
use tokio::signal::unix::{signal, SignalKind};
…
let program: &mut Xdp = bpf.program_mut("ebpf_chaos_demo_xdp").unwrap().try_into()?;
program.load()?;
program.attach(&opt.iface, XdpFlags::default())
.context("failed to attach the XDP program with default flags - try changing XdpFlags::default() to XdpFlags::SKB_MODE")?;
// Implement signal handling for CTRL+C (SIGINT) and SIGTERM
// CTRL+C can be used for terminal tests
// SIGTERM will be sent from CI/CD jobs to the background process
let mut sigterm = signal(SignalKind::terminate())?;
let mut sigint = signal(SignalKind::interrupt())?;
tokio::select! {
_ = sigterm.recv() => { println!("SIGTERM shutting down") }
_ = sigint.recv() => { println!("SIGINT shutting down") }
}
Ok(())
// Destroying the bpf object will detach and cleanup the loaded program.
// Debug with 'bpftool link show'
}
剩下的挑战就是扩展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是GitLab的高级开发人员布道者,专注于可观测性、DevSecOps和AI。Michael创建了o11y.love作为可观测性学习平台,并在他的opsindev.news通讯中分享技术趋势以及对day-2ops、eBPF、AI/MLOps的见解。在没有旅行和远程工作的时候,他喜欢搭建乐高模型。