作者:Arnold van Wijnbergen
我最近听过 Pixie:一个给基于微服务应用程序的开源调试平台。Pixie 是使用 Linux eBPF 技术构建的[1],该技术承诺提供自动监控。除了它的原生跟踪的协议之外[2],Pixie 还有一个特性,使我们能够在集群上执行类似 bpftrace 的脚本,这非常棒。在看到 Pixie 于 2021 年 4 月发布后,我决定研究 Pixie 及其 bpftrace 功能。
为了初步了解实际的实现,我从 Pixie 的参考视频[3]开始,其中转换了 bpftrace 的 tcp-retransmit.bt 到实际的 PxL 脚本。在那个 Youtube 视频中,一切似乎都解释得很好,所以我继续我的旅程。
在这篇文章中,我将向你展示如何使用 Pixie 部署 bpftrace 代码,并分享我为 Pixie 贡献的转换后的 bpftrace 工具脚本。
bpftrace 背景
如果你不熟悉 bpftrace,也没有问题。bpftrace 是一个为 eBPF 提供高级跟踪语言的工具。在后台,它使用 BCC Toolkit(IO Visor 项目[4])和 LLVM 将所有脚本编译为 bpf 字节码。它支持内核探测(Kprobes)、用户级探测(Uprobes)和跟踪点。bpftrace 本身深受 awk、sed 等工具以及 DTrace 和 SystemTap 等跟踪器的启发,因此我们可以创建非常棒的一行程序。
这使得该工具非常强大,但也有一个缺点,因为它只能在本地运行,不能提供在远程系统上分布式运行的功能,也没有一个中央 UI。
Pixie 可以帮我们把这些事搞定。Pixie 可以跨 Kubernetes 集群分发 eBPF 程序,并提供可以从 UI、CLI 或 API 轻松查询的表。
修改 sleepy_snoop 移植到 Pixie
让我们开发第一个 bpftrace PxL 脚本。对于本例,我们将使用一个著名的一行代码,我们将其称为 sleepy_snoop。让我们首先看看实际的代码本身。
kprobe:do_nanosleep { printf("PID %d sleeping\n", pid); }
复制
Pixie 需要做一些小的调整[5],使这些代码在 PxL 脚本中工作:
首先,我们必须转义 printf 双引号。 我们需要一个 printf 语句,其中包含字段名作为 Pixie 表的实际输出,因此我们必须调整 kprobe:do_nanosleep 块中的 printf 语句,以包含 pid 列名。 此外,我们还将使用时间戳和进程名来丰富输出。我们可以在字段名 time_原生使用 nsecs。Pixie 可以识别该字段,并自动显示为人类可读的日期时间格式。为了记录进程名,我们使用内置的 comm 变量。
转换后的 eBPF 程序应该如下所示:
kprobe:do_nanosleep { printf(\"time_:%llu pid:%d comm:%s\", nsecs, pid, comm); }
复制
从 Pixie CLI 运行 sleepy_snoop
现在我们有了 eBPF 代码,可以创建实际的 PxL 脚本了。你可以在这里[6]找到这个脚本。
# Import Pixie's modules for creating traces & querying data
import pxtrace
import px
# Adapted from https://brendangregg.com
program = """
kprobe:do_nanosleep { printf(\"time_:%llu pid:%d comm:%s\", nsecs, pid, comm); }
"""
# sleepy_snoop_func function to create a tracepoint
# and start the data collection.
def sleepy_snoop_func():
table_name = 'sleepy_snoop_table'
pxtrace.UpsertTracepoint('sleepy_snoop_tracer',
table_name,
program,
pxtrace.kprobe(),
"10m")
df = px.DataFrame(table=table_name)
return df
output = sleepy_snoop_func();
# display the tracepoint table data
px.display(output)复制
这个脚本看起来与 PxL 脚本有些不同,后者只是查询已经收集的数据。简而言之,我们:
导入 px 和 pxtrace 库。 创建一个包含 BPF 代码的 program 变量。 创建一个函数来执行跟踪点集合。在我们的例子中是 sleepy_snoop_func。 定义要将结果放入的目标 Pixie 表,称为 sleepy_snoop_table。 定义跟踪点来启动称为 sleepy_snoop_tracer 的 Kprobe。这包括 10m 的生存时间,它会在最后一次脚本执行 10 分钟后自动删除 eBPF 探测。 从结果表中创建一个 DataFrame 对象并将其显示在 UI 中。
你可以使用 Pixie 的 CLI 运行该脚本:
px run -f sleepy_snoop.pxl
复制
有关如何使用 Pixie CLI 的更多帮助,请参阅教程[7]。
下面是 CLI 输出的一个示例。注意,在某些情况下,你可能需要运行脚本两次。这是因为脚本在第一次运行时可能还没有收集到要显示的任何数据。
px run -f sleepy_snoop.pxl
Pixie CLI
Table ID: output
TIME PID COMM
2021-09-27 20:11:15.546971049 +0200 CEST 12123 pem
2021-09-27 20:11:15.614823431 +0200 CEST 4261 k8s_metadata
2021-09-27 20:11:15.615110023 +0200 CEST 4261 k8s_metadata
2021-09-27 20:11:15.615132796 +0200 CEST 8077 metadata
2021-09-27 20:11:15.615196553 +0200 CEST 4261 k8s_metadata
2021-09-27 20:11:15.621200052 +0200 CEST 4261 k8s_metadata
2021-09-27 20:11:15.621290646 +0200 CEST 4261 k8s_metadata
2021-09-27 20:11:15.621375788 +0200 CEST 4261 k8s_metadata
2021-09-27 20:11:15.546333885 +0200 CEST 6952 containerd-shim
2021-09-27 20:11:15.546344427 +0200 CEST 1495 containerd
2021-09-27 20:11:15.546366425 +0200 CEST 1495 containerd
2021-09-27 20:11:15.546429576 +0200 CEST 1495 containerd
2021-09-27 20:11:15.564011412 +0200 CEST 3563 containerd-shim
2021-09-27 20:11:15.566385845 +0200 CEST 1603 kubelet
2021-09-27 20:11:15.566485594 +0200 CEST 1603 kubelet
2021-09-27 20:11:15.615859719 +0200 CEST 4261 k8s_metadata复制
祝贺你,你已经成功地使用 Pixie 创建并部署了第一个 eBPF 程序!
从 Pixie UI 运行 sleepy_snoop
我们也可以使用 Pixie 的 UI[8]运行这个脚本:
打开 Pixie 的 UI 从顶部的 script 下拉菜单中选择 Scratch Pad。 使用 ctrl+e(Windows,Linux)或 cmd+e(Mac)打开脚本编辑器,并粘贴上一节中的脚本。使用相同的键盘命令关闭编辑器。 按下右上角的 RUN 按钮。
在一次成功的运行之后,你会在窗口的左边得到第一个结果,这将是一个表视图,有三个列:TIME_、PID 和 COMM。如前所述,这个 sleepy_snoop 跟踪所有调用 sleep 的 pid。你可以单击表行以查看 JSON 格式的行数据。
使用 OOM 杀手追踪点的真实演示
让我们再做一个例子,查找 OOM 终止的进程。简而言之,OOM 意味着内存不足,我们可以使用这里[9]找到的演示代码在 Kubernetes 集群上轻松地模拟这个情况。为了跟踪这些事件,我们将使用 oomkill.bt 工具。
让我们先看看原代码[10]:
#include <linux/oom.h>
BEGIN
{
printf("Tracing oom_kill_process()... Hit Ctrl-C to end.\n");
}
kprobe:oom_kill_process
{
$oc = (struct oom_control *)arg0;
time("%H:%M:%S ");
printf("Triggered by PID %d (\"%s\"), ", pid, comm);
printf("OOM kill of PID %d (\"%s\"), %d pages, loadavg: ",
$oc->chosen->pid, $oc->chosen->comm, $oc->totalpages);
cat("/proc/loadavg");
}复制
如前所述,我们必须做一些小的调整,成为 PxL 脚本工作:
首先,我们将开始删除 BEGIN 块,因为我们不需要这个 printf 语句。 我们只能有一条 printf 语句,其中包含字段名作为实际输出到 Pixie 表,因此我们必须在 kprobe:oom_kill_process 块中组合这两条 printf 语句。 我们删除了 time 函数,因为我们可以在字段名_time 原生使用 nsecs。 我们删除了 cat 函数,因为它在 Pixie 中还不能使用。
eBPF 程序的最终应该如下所示:
kprobe:oom_kill_process
{
$oc = (struct oom_control *)arg0;
printf(\"time_:%llu triggered_by_pid:%d triggered_by_comm:%s killed_pid:%d killed_comm:%s pages:%d\",
nsecs, pid, comm, $oc->chosen->pid, $oc->chosen->comm, $oc->totalpages);
}复制
为了方便起见,我们可以使用 Pixie 现有的 bpftrace/oom_kill 脚本。从 UI 中的脚本下拉菜单中选择此脚本。
从 UI 的脚本菜单中选择 bpftrace/oom_kill。
现在运行脚本并查看是否有 OOM 事件发生。在正常情况下,你不应该看到任何 OOM 事件。
现在,让我们通过应用以下容器来触发 OOM 杀手:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: memleaky-app
spec:
containers:
- name: memleak
image: avwsolutions/memleak:1.0
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
EOF复制
几分钟后,由于内存限制,这个 pod 将获得状态 OOMKilled。
在 UI 中重新运行 PxL 脚本。
Pixie UI 中 bpftrace/oom_kill.pxl 的脚本输出。
现在我们可以看到哪个 pid 被杀死,由哪个 pid 和相应的名称触发。很酷,对吗?
更多 bpftrace PxL 脚本
要查看所有可用的脚本,请在 Pixie UI 的脚本下拉菜单中键入 bpftrace。我已经贡献了以下转换了的 bpftrace 程序:
bpftrace/capable
用于跟踪对内核 cap_capable 函数调用的工具。这个函数负责执行安全能力检查,在这里我们在 Pixie 表记录每次调用的所有细节。
bpftrace/dc_snoop
用于跟踪目录条目缓存(dcache)查找的工具。当你查找缓存命中(T=R)和缓存未命中(T=M)时,这很有帮助。
bpftrace/nap_time
使用 nanosleep 系统调用跟踪应用程序睡眠的工具。
bpftrace/sync_snoop
用于跟踪文件系统同步事件的工具,这些事件将文件系统缓冲区刷新到存储设备。
bpftrace/tcp_retransmits
用于跟踪由 tcp_retransmit()内核函数重传的网络流量 TCP 包并显示实际计数和相关会话信息的工具。多次重传表明网络连接不良,有助于诊断网络健康状况。
bpftrace/tcp_drop
用于跟踪由 tcp_drop()内核函数丢弃的网络流量 TCP 包的工具,并显示实际计数和相关会话信息。当你正在调查网络性能瓶颈时,当怀疑有高速率下降时,这可以帮助你。
总结
Pixie 是一个使 eBPF 更接近可观察性堆栈的应用程序调试平台。我希望这篇博客文章已经展示了 eBPF 的强大功能,以及使用 Pixie 将 BPF 程序部署到集群中的所有节点是多么容易。一定要查看 Pixie 中现有的所有 bpftrace 脚本,以及即将推出的实验性脚本[11]。
我们也看到了这个特性的局限性。目前,只支持 KProbes,一些文档仍在制作中。希望这篇博客文章对文档部分有所帮助,并激励工程师或开发人员尝试 Pixie。对于社区来说,如果能够构建更棒、更有帮助的 PxL 脚本,那就太棒了。
在结束这个博客的时候,我们要向 Brendan Gregg 和 eBPF 社区鸣谢,感谢他们创造了这些很棒的 bpftrace 工具。最后,我想感谢来自 Pixie 的 Omid Azizi,感谢他的社区信念,并在创建这些脚本时对我的帮助。
资源
“使用 Pixie 部署分布式 bpftrace”教程[12]。
有问题吗?可以在Slack[13]或推特上@pixie_run 找到我们。
参考资料
Pixie 是使用 Linux eBPF 技术构建的: https://docs.px.dev/about-pixie/pixie-ebpf/
[2]原生跟踪的协议之外: https://docs.px.dev/about-pixie/data-sources/#supported-protocols
[3]参考视频: https://www.youtube.com/watch?v=xT7OYAgIV28
[4]IO Visor 项目: https://github.com/iovisor
[5]小的调整: https://docs.px.dev/tutorials/custom-data/distributed-bpftrace-deployment/#output
[6]sleepy_snoop.pxl 脚本: https://github.com/avwsolutions/app-debug-k8s-pixie-demo/blob/main/tracepoint-scripts/sleepy_snoop.pxl
[7]教程: https://docs.px.dev/using-pixie/using-cli/
[8]使用 Pixie 的 UI: https://docs.px.dev/using-pixie/using-live-ui/
[9]OOM 演示代码: https://github.com/avwsolutions/app-debug-k8s-pixie-demo/tree/main/memleak
[10]原代码: https://github.com/iovisor/bpftrace/blob/master/tools/oomkill.bt
[11]实验性脚本: https://github.com/avwsolutions/app-debug-k8s-pixie-demo/tree/main/bpftrace-scripts-experimental
[12]教程: https://docs.px.dev/tutorials/custom-data/distributed-bpftrace-deployment/
[13]Slack: https://slackin.px.dev/
点击【阅读原文】阅读网站原文。
CNCF概况(幻灯片)
扫描二维码联系我们!
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请长按以下二维码进行关注。