WEBKT

使用 eBPF 诊断网络传输延迟?这次让你抓到真凶!

53 0 0 0

使用 eBPF 诊断网络传输延迟?这次让你抓到真凶!

作为一名网络工程师,最头疼的事情莫过于用户反馈“网速慢”。但“网速慢”这三个字背后,可能隐藏着各种各样的问题:是服务器响应慢?是网络拥塞?还是客户端自身的问题? 传统的排查手段,比如 pingtraceroute,虽然能提供一些信息,但往往不够精确,难以定位到问题的根源。特别是对于复杂的分布式系统,网络链路长,节点多,问题定位更是难上加难。

这时,eBPF (extended Berkeley Packet Filter) 就派上用场了!它就像一个网络世界的“显微镜”,可以让我们在内核层面上观察网络数据包的流动,精确测量各个环节的延迟,从而找到真正的瓶颈。

为什么选择 eBPF?

  • 高性能: eBPF 程序运行在内核态,避免了用户态和内核态之间频繁的切换,性能损耗极低。
  • 灵活性: 我们可以根据需要,编写自定义的 eBPF 程序,监控各种网络事件,提取各种指标。
  • 安全性: eBPF 程序在运行前会经过内核的验证,确保不会对系统造成危害。

如何使用 eBPF 监控网络传输延迟?

我们的目标是:

  1. 测量数据包的传输延迟:精确到微秒级别。
  2. 区分延迟的来源:是发送端导致的延迟,还是接收端导致的延迟?
  3. 分析延迟的原因:例如,网络拥塞、路由问题等。

下面,我将一步步地介绍如何使用 eBPF 实现这个目标。

1. 准备工作

  • Linux 内核版本: 建议使用 4.14 或更高版本的内核,以获得更好的 eBPF 支持。
  • bcc 工具: bcc (BPF Compiler Collection) 是一个用于创建 eBPF 程序的工具包,提供了 Python 和 C++ 的 API,方便我们编写和部署 eBPF 程序。
    • 安装方法:根据你的 Linux 发行版,使用相应的包管理器安装 bcc。例如,在 Ubuntu 上,可以执行以下命令:
    sudo apt-get update
    sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
  • 网络抓包工具: 例如 tcpdumpwireshark,用于验证 eBPF 程序的结果。

2. 编写 eBPF 程序

我们需要编写两个 eBPF 程序:

  • 发送端程序: 记录数据包发送的时间戳。
  • 接收端程序: 记录数据包接收的时间戳,并计算传输延迟。

下面是一个简单的发送端 eBPF 程序的例子 (基于 bcc 的 Python API):

from bcc import BPF
import socket
import struct
# 定义 eBPF 程序
program = '''
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <net/inet_sock.h>
#include <linux/tcp.h>
struct data_t {
u32 pid;
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u64 ts;
};
BPF_PERF_OUTPUT(events);
int kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size)
{
// 获取进程 ID
u32 pid = bpf_get_current_pid_tgid();
// 获取源 IP 地址和端口
u32 saddr = sk->__sk_common.skc_rcv_saddr;
u16 sport = sk->__sk_common.skc_num;
// 获取目标 IP 地址和端口
u32 daddr = sk->__sk_common.skc_daddr;
u16 dport = sk->__sk_common.skc_dport;
// 获取当前时间戳
u64 ts = bpf_ktime_get_ns();
// 填充数据结构
struct data_t data = {};
data.pid = pid;
data.saddr = saddr;
data.daddr = daddr;
data.sport = sport;
data.dport = dport;
data.ts = ts;
// 发送数据到用户空间
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
'''
# 创建 BPF 实例
bpf = BPF(text=program)
# 定义事件处理函数
def print_event(cpu, data, size):
event = bpf["events"].event(data)
print("PID: %d, Source: %s:%d, Dest: %s:%d, Timestamp: %d" % (
event.pid,
socket.inet_ntoa(struct.pack('I', event.saddr)),
event.sport,
socket.inet_ntoa(struct.pack('I', event.daddr)),
event.dport,
event.ts
))
# 绑定事件处理函数
bpf["events"].open_perf_buffer(print_event)
# 循环读取事件
while True:
try:
bpf.perf_buffer_poll()
except KeyboardInterrupt:
exit()

代码解释:

  • #include 引入必要的头文件,例如 net/sock.h 用于访问 socket 结构体,linux/tcp.h 用于访问 TCP 相关信息。
  • struct data_t 定义了一个数据结构,用于存储我们需要的信息,例如进程 ID、源 IP 地址和端口、目标 IP 地址和端口、时间戳。
  • BPF_PERF_OUTPUT(events) 定义了一个 perf 事件输出,用于将数据从内核空间发送到用户空间。
  • kprobe__tcp_sendmsg 定义了一个 kprobe,它会在 tcp_sendmsg 函数被调用时触发。tcp_sendmsg 是 Linux 内核中发送 TCP 消息的函数。
  • kprobe__tcp_sendmsg 函数中,我们首先获取进程 ID、源 IP 地址和端口、目标 IP 地址和端口,以及当前时间戳。然后,我们将这些信息填充到 data_t 结构体中,并通过 events.perf_submit 函数将数据发送到用户空间。
  • 在 Python 代码中,我们首先创建了一个 BPF 实例,并将 eBPF 程序加载到内核中。然后,我们定义了一个 print_event 函数,用于处理从内核空间接收到的事件。最后,我们通过 bpf["events"].open_perf_buffer(print_event)print_event 函数绑定到 events perf 事件输出上。

类似地,我们可以编写一个接收端 eBPF 程序,在 tcp_recvmsg 函数被调用时记录接收时间戳,并计算传输延迟。接收端程序需要与发送端程序共享一些信息,例如源 IP 地址和端口、目标 IP 地址和端口,以便能够匹配发送和接收事件。

3. 部署 eBPF 程序

将发送端和接收端 eBPF 程序分别部署到发送端和接收端服务器上。

4. 分析 eBPF 程序的结果

运行发送端和接收端 eBPF 程序,它们会将收集到的数据输出到控制台。我们可以将这些数据进行分析,计算传输延迟,并区分延迟的来源。

更进一步:分析延迟的原因

仅仅知道延迟是多少还不够,我们还需要知道延迟的原因。eBPF 还可以帮助我们分析延迟的原因。

  • 网络拥塞: 我们可以使用 eBPF 监控 TCP 拥塞控制算法的指标,例如拥塞窗口 (congestion window)、丢包率 (packet loss rate) 等。如果拥塞窗口减小,丢包率升高,则说明网络可能存在拥塞。
  • 路由问题: 我们可以使用 eBPF 监控路由表的更新,以及数据包的转发路径。如果数据包的转发路径不稳定,或者经过了不合理的路由,则说明网络可能存在路由问题。
  • DNS 解析延迟: 我们可以使用 eBPF 监控 DNS 解析的时间。如果 DNS 解析时间过长,则说明 DNS 服务器可能存在问题。

一些更高级的技巧

  • 使用 BPF 映射 (maps) 存储数据: BPF 映射是一种键值存储,可以在 eBPF 程序和用户空间程序之间共享数据。我们可以使用 BPF 映射存储一些全局信息,例如配置参数、统计数据等。
  • 使用跟踪点 (tracepoints) 替代 kprobes: 跟踪点是内核中预定义的事件点,比 kprobes 更加稳定和高效。我们可以使用跟踪点替代 kprobes,以获得更好的性能。
  • 使用 uprobes 监控用户空间程序: uprobes 可以在用户空间程序的函数被调用时触发。我们可以使用 uprobes 监控用户空间程序的延迟,例如数据库查询延迟、HTTP 请求延迟等。

eBPF 的局限性

  • 学习曲线: eBPF 的学习曲线比较陡峭,需要掌握一定的内核知识和编程技巧。
  • 安全风险: 如果 eBPF 程序编写不当,可能会对系统造成危害。
  • 内核版本兼容性: 不同的内核版本对 eBPF 的支持程度可能不同。

总结

eBPF 是一种强大的网络监控和性能分析工具。它可以让我们在内核层面上观察网络数据包的流动,精确测量各个环节的延迟,从而找到真正的瓶颈。虽然 eBPF 的学习曲线比较陡峭,但只要掌握了基本原理和使用方法,就可以在网络性能优化方面发挥巨大的作用。希望这篇文章能够帮助你入门 eBPF,并在实际工作中应用 eBPF 解决问题。

一些实用的 eBPF 工具

  • bpftrace: 一种高级的 eBPF 跟踪语言,可以让我们用简单的脚本快速编写 eBPF 程序。
  • kubectl trace: 一种 Kubernetes 工具,可以让我们在 Kubernetes 集群中运行 eBPF 程序。
  • Falco: 一种云原生安全工具,使用 eBPF 监控系统调用,检测安全事件。

最后的思考

eBPF 的应用场景非常广泛,不仅可以用于网络性能优化,还可以用于安全监控、故障排查、容器监控等。随着 eBPF 技术的不断发展,相信它将在未来的云计算和网络领域发挥越来越重要的作用。 你是否也开始对 eBPF 感兴趣了呢? 快去尝试一下吧!

网络巡警 eBPF网络延迟性能分析

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/9691