bpftune - BPF驱动的自动调优
bpftune旨在提供轻量级的、始终在线的系统行为自动调优。它提供的主要好处是:
- 通过使用BPF可观察性功能,我们可以持续监控和调整系统行为
- 由于我们可以以细粒度观察系统行为(而不是使用粗粒度的全系统统计数据),我们也可以进行更精细的调优(单个套接字策略、单个设备策略等)
问题
Linux内核包含大量可调参数;这些参数通常以sysctl(8)参数的形式出现,通常是为了应对没有一个"正确"答案的配置选择情况而引入的。可用的可调参数数量相当惊人。在6.2内核上,我们看到:
# sysctl --all 2>/dev/null|wc -l
1624
同时,单个系统得到的管理员关注比以前少得多;像"牛而不是宠物"这样的短语就体现了这一点。鉴于现代云架构在大多数部署中的使用,大多数系统在初始配置后从不需要任何人工管理员交互;事实上,考虑到规模要求,这通常是一个明确的设计目标 - "不要SSH登录!"。
这两个观察并非无关;在早期系统较少但规模较大的时代,管理员进行调优更加可行。
这些趋势 - 系统复杂性与最小化管理员交互的结合 - 暗示我们需要重新思考可调参数管理。
围绕这些可调参数积累了大量经验,为了帮助阐明我们为什么开发bpftune,我们将使用一个关于处理可调参数的方法的简化版本:
"找到一组适用于系统永久使用的魔法数字"
这显然是对管理员如何处理问题的讽刺,但它确实突出了一个关键的隐含假设 - 系统是静态的。
这就是bpftune中"BPF"的用武之地;BPF提供了低开销观察系统的方法。因此,我们不仅可以观察系统并进行适当调整,还可以观察调整的效果并在必要时重新调整。
关键设计原则
- 最小化开销。谨慎使用可观察性功能;不要跟踪非常高频的事件。
- 明确说明策略更改,提供"什么" - 做了什么更改 - 以及"为什么" - 它如何有帮助?通过syslog记录使策略操作明确并提供解释。
- 不要妨碍管理员。我们可以使用BPF可观察性来查看管理员是否设置了我们正在自动调整的可调参数值;如果他们这样做了,我们需要避开并禁用相关功能集的自动调整。
- 不要用更多可调参数替换可调参数!bpftune设计为零配置;没有选项,我们尽量避免使用魔法数字。
- 使用推拉方法。例如,对于TCP缓冲区大小调整,我们通常希望避开应用程序并增加tcp sndbuf和rcvbuf,但在某个点上我们可能会耗尽TCP内存。然而,我们可以监控是否接近TCP内存压力,如果是,我们可以调低我们已调高的值。通过这种方式,我们可以让系统在提供资源和耗尽资源之间找到平衡。在某些情况下,我们可能不需要调高值;它们可能已经足够好了。但在其他情况下,这些限制会阻碍最佳性能,如果安全地提高它们 - 同时意识到全局内存限制 - 我们可以避开性能改进的障碍。另一个问题是增加缓冲区大小会导致延迟 - 为了处理这个问题,我们将缓冲区大小变化与TCP平滑往返时间相关联;如果这两者之间的相关性超过阈值(0.7),我们就停止增加缓冲区大小。
概念
关键组件包括:
-
调优器:每个调优器管理可调参数,并处理从BPF程序通过共享环形缓冲区发送到用户空间的事件。每个调优器都有一组相关的可调参数。
-
可选策略:调优器可以指定多个策略;运行一段时间后,策略会超时,我们会评估是否有更好的策略可用。每个策略指定:
- 名称
- 描述
- 超时时间
- 评估函数
- 与策略相关的调优器中的BPF程序名称集
策略是可选的,应该在调优器init()方法中通过bpftune_strategies_add()设置。参见test/strategy获取代码示例。当策略超时时,会调用各种评估函数,最高值评估决定下一个策略。
策略提供了为同一组可调参数提供多种自动调优方案的方法,其中的选择由策略有效性的评估来指导。
-
事件指定:
- 调优器ID:事件的目标调优器
- 场景:发生了什么
- 相关的网络命名空间(如果支持)
- 有关事件的信息(IP地址等)
-
然后调优器在活动策略的指导下响应事件;增加或减少可调参数值等。在日志中描述事件至关重要;这允许管理员了解发生了什么变化以及原因。
架构
- bpftune是一个管理一组.so插件调优器的守护进程;每个调优器都是在启动时加载的共享对象。
- 调优器可以启用或禁用;如果管理员手动更改相关的可调参数,调优器会自动禁用。
- 调优器共享一个全局BPF环形缓冲区,允许从BPF程序向用户空间发布事件。例如,如果sysctl调优器看到设置了sysctl,它会发布一个事件。
- 每个调优器都有一个关联的ID(在加载时设置),发布的事件包含调优器ID。
- 每个调优器都有一个BPF组件(使用BPF骨架构建)和一个用户空间组件。后者有init()、fini()和event_handler()入口点。当收到事件时,使用调优器ID来识别适当的事件处理程序,并运行其event_handler()回调函数。
- init、fini和event_handler函数从调优器.so对象中加载。
- BPF组件应包含bpftune.bpf.h;它包含公共映射定义(ringbuf等)和每个调优器需要的共享变量,如学习率和调优器ID。
支持的调优器
- TCP连接调优器:自动调整拥塞控制算法的选择。参见bpftune-tcp-conn(8)。
- 邻居表调优器:通过在接近满时增大表来自动调整邻居表大小。参见bpftune-neigh(8)。
- 路由表调优器:通过在接近满时增大表来自动调整路由表大小。参见bpftune-route(8)。
- sysctl调优器:监控sysctl设置,如果与自动调整的sysctl值冲突,则禁用相关调优器。参见bpftune-sysctl(8)。
- TCP缓冲区调优器:自动调整最大和初始缓冲区大小。参见bpftune-tcp-buffer(8)。
- 网络缓冲区调优器:自动调整与核心网络相关的可调参数。参见bpftune-net-buffer(8)。
- 网络命名空间调优器:注意网络命名空间的添加和删除,这有助于为整个bpftune提供命名空间感知能力。命名空间感知很重要,因为我们也希望能够自动调整容器。参见bpftune-netns(8)。
代码组织
核心bpftune.c和各个调优器都使用libbpftune库。它处理日志记录、调优器初始化/结束和BPF初始化/结束。
每个调优器共享对象定义了init()、fini()和event_handler()函数。这些函数分别设置和清理BPF,并处理源自BPF代码的事件。
入门
如果手动构建存储库,只需在存储库的顶层运行:
$ make ; sudo make install
bpftune还支持:
$ make pkg
目标,这将制作一个bpftune RPM。参见./buildrpm/bpftune.spec
要构建,需要以下软件包(名称可能因发行版而异):
- libbpf, libbpf-devel >= 0.6
- libcap-devel
- bpftool >= 4.18
- libnl3-devel
- clang >= 11
- llvm >= 11
- python3-docutils
从内核方面来说,内核需要支持BPF环形缓冲区(大约在5.6内核左右,尽管在Oracle Linux上支持5.4,因为环形缓冲区支持被回移),并且需要内核BTF(CONFIG_DEBUG_INFO_BTF=y)。验证/sys/kernel/btf/vmlinux是否存在。
要将bpftune作为服务启用:
$ sudo service bpftune start
...并默认启用它:
$ sudo systemctl enable bpftune
bpftune记录到syslog,因此/var/log/messages将包含任何调优的详细信息。
bpftune还可以作为前台程序运行;要将输出重定向到stdout/stderr,运行:
$ sudo bpftune -s
退出时,bpftune将总结任何已完成的调优。
测试
tests/子目录中为每个调优器提供了测试。"make test"运行所有测试。测试使用网络命名空间来模拟与远程主机的交互。有关更多详细信息,请参见./TESTING.md。
我的系统支持bpftune吗?
只需运行"bpftune -S"即可查看:
$ bpftune -S
bpftune works fully
bpftune supports per-netns policy (via netns cookie)
这里有两个重要方面:
- 系统是否支持fentry/fexit等?如果是,很可能完全支持。
- 系统是否支持网络命名空间cookie?如果是,则支持每个网络命名空间的策略。
演示
简单地启动bpftune并通过/var/log/messages观察所做的更改可能会很有启发性。例如,在使用sysctl默认值的标准VM上,我运行了:
$ service bpftune start
...然后进行正常的开发活动,如从上游克隆git树、构建内核等。从日志中我们可以看到bpftune为适应这些活动所做的一些调整。
$ sudo grep bpftune /var/log/messages
...
4月19日 16:14:59 bpftest bpftune[2778]: bpftune完全可用
4月19日 16:14:59 bpftest bpftune[2778]: bpftune支持每个网络命名空间的策略(通过网络命名空间cookie)
4月19日 16:18:40 bpftest bpftune[2778]: 在全局命名空间中,针对"TCP拥塞控制"可调参数发生了"指定bbr拥塞控制"场景。由于连接的丢包率超过1%,使用bbr拥塞控制算法替代默认算法
4月19日 16:18:40 bpftest bpftune[2778]: 由于145.40.68.75的丢包事件,指定'bbr'拥塞控制算法
4月19日 16:26:53 bpftest bpftune[2778]: 在全局命名空间中,针对'net.ipv4.tcp_rmem'可调参数发生了"需要增加TCP缓冲区大小"场景。需要增加缓冲区大小以最大化吞吐量
4月19日 16:26:53 bpftest bpftune[2778]: 由于需要增加最大缓冲区大小以最大化吞吐量,将net.ipv4.tcp_rmem(最小 默认 最大)从(4096 131072 6291456)更改为(4096 131072 7864320)
4月19日 16:26:53 bpftest bpftune[2778]: 在全局命名空间中,针对'net.ipv4.tcp_rmem'可调参数发生了"需要增加TCP缓冲区大小"场景。需要增加缓冲区大小以最大化吞吐量
4月19日 16:26:53 bpftest bpftune[2778]: 由于需要增加最大缓冲区大小以最大化吞吐量,将net.ipv4.tcp_rmem(最小 默认 最大)从(4096 131072 7864320)更改为(4096 131072 9830400)
4月19日 16:29:04 bpftest bpftune[2778]: 在全局命名空间中,针对"TCP拥塞控制"可调参数发生了"指定bbr拥塞控制"场景。由于连接的丢包率超过1%,使用bbr拥塞控制算法替代默认算法
4月19日 16:29:04 bpftest bpftune[2778]: 由于140.91.12.81的丢包事件,指定'bbr'拥塞控制算法
为了确定性地触发bpftune行为,我们可以采取的一种方法是使用不适当的设置下载一个大文件。
在一个窗口中,将tcp rmem最大值设置为过低的值,并以程序方式运行bpftune,日志输出到标准输出/标准错误(-s):
$ sudo sysctl -w net.ipv4.tcp_rmem="4096 131072 1310720"
net.ipv4.tcp_rmem = 4096 131072 1310720
$ sudo bpftune -s
在另一个窗口中,使用wget下载一个大文件:
$ wget https://yum.oracle.com/ISOS/OracleLinux/OL8/u7/x86_64/OracleLinux-R8-U7-x86_64-dvd.iso
在第一个窗口中,我们可以看到bpftune正在调整rmem:
bpftune: bpftune在传统模式下工作
bpftune: bpftune不支持每个网络命名空间的策略(通过网络命名空间cookie)
bpftune: 在全局命名空间中,针对'net.ipv4.tcp_rmem'可调参数发生了"需要增加TCP缓冲区大小"场景。需要增加缓冲区大小以最大化吞吐量
bpftune: 由于需要增加最大缓冲区大小以最大化吞吐量,将net.ipv4.tcp_rmem(最小 默认 最大)从(4096 131072 1310720)更改为(4096 131072 1638400)
这种情况会多次发生,退出时(按Ctrl+C)我们可以看到所做更改的摘要:
bpftune: 摘要:在全局命名空间中,针对'net.ipv4.tcp_rmem'可调参数发生了9次"需要增加TCP缓冲区大小"场景。需要增加缓冲区大小以最大化吞吐量
bpftune: sysctl 'net.ipv4.tcp_rmem'从(4096 131072 1310720)更改为(4096 131072 9765625)
更多信息
请查看docs/子目录中有关bpftune和相关调节器的手册页。
bpftune在eBPF峰会上进行了介绍;视频在此。
在Liz Rice出色的eCHO eBPF播客中也讨论了bpftune,特别是在使用BPF中的强化学习的背景下。
贡献
本项目欢迎社区贡献。在提交拉取请求之前,请查看我们的贡献指南
安全
请查阅安全指南了解我们负责任的安全漏洞披露流程
许可证
版权所有 (c) 2023 Oracle和/或其附属公司。
本软件根据以下许可证提供给您
SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
根据GNU通用公共许可证版本2的条款。
SPDX-URL: https://spdx.org/licenses/GPL-2.0.html
有关更多详细信息,请参阅许可证文件。