WEBKT

性能优化师的eBPF炼成记:Kubernetes网络延迟诊断实战

52 0 0 0

eBPF:性能诊断的瑞士军刀

准备工作

实战案例:诊断服务A的网络延迟

一些建议

额外思考

作为一名性能优化工程师,面对Kubernetes集群中服务网络延迟的问题,我深知其复杂性。网络拥塞、DNS解析慢、服务自身处理能力不足,甚至内核协议栈的瓶颈都可能成为罪魁祸首。传统的排查方法往往耗时费力,如同大海捞针。但现在,有了eBPF(extended Berkeley Packet Filter),我们就能像外科医生一样精准定位问题。本文将分享我利用eBPF诊断Kubernetes网络延迟的实战经验,希望能帮助你快速解决类似问题。

eBPF:性能诊断的瑞士军刀

eBPF是一种内核技术,允许我们在内核中安全地运行用户自定义的代码,而无需修改内核源码或加载内核模块。它就像一个“内核探针”,能够实时地收集系统性能数据,并进行分析。eBPF的强大之处在于其灵活性和效率,它可以在内核的各个关键点(如网络协议栈、文件系统、调度器等)插入探针,捕获所需的信息,而且对系统性能的影响极小。

准备工作

在开始之前,你需要确保满足以下条件:

  1. Kubernetes集群: 一个正在运行的Kubernetes集群,并且能够访问集群的控制平面。
  2. kubectl: Kubernetes命令行工具,用于与集群交互。
  3. bcc (BPF Compiler Collection): 一套用于创建eBPF程序的工具集,包含了Python库和一些有用的命令行工具。可以通过以下方式安装:
    • Debian/Ubuntu: apt-get install bpfcc-tools linux-headers-$(uname -r)
    • CentOS/Fedora: yum install bpfcc-tools kernel-devel-$(uname -r)
  4. libbpf: 一个用于加载和管理eBPF程序的C库。通常与bcc一起安装。
  5. FlameGraph: 用于可视化eBPF捕获的性能数据的工具。可以从https://github.com/brendangregg/FlameGraph下载。

实战案例:诊断服务A的网络延迟

假设我们的Kubernetes集群中运行着一个名为“service-a”的服务,用户反馈该服务的响应速度较慢。我们需要找出导致延迟的具体原因。

1. 确定延迟发生的位置

首先,我们需要确定延迟发生的位置。是客户端到service-a入口的延迟,还是service-a内部pod之间的延迟?可以使用tcpdump抓包分析,或者在客户端和服务端分别部署监控工具,记录请求的耗时。这里我们假设延迟发生在service-a内部的pod之间。

2. 使用tcpconnect跟踪TCP连接建立过程

tcpconnect是一个bcc工具,可以跟踪TCP连接的建立过程,并记录连接建立的时间。我们可以使用它来查看service-a的pod之间的TCP连接建立是否缓慢。

sudo ./tcpconnect -d -p 8080

这个命令会跟踪所有目标端口为8080的TCP连接建立过程,并打印相关信息,例如连接建立的时间、源IP地址、目标IP地址等。如果发现连接建立时间较长,则说明网络连接存在问题。

3. 使用tcplife跟踪TCP连接的生命周期

tcplife是另一个bcc工具,可以跟踪TCP连接的生命周期,并记录连接的持续时间、发送和接收的数据量等。我们可以使用它来查看service-a的pod之间的TCP连接是否正常。

sudo ./tcplife -d

这个命令会跟踪所有TCP连接的生命周期,并打印相关信息,例如连接的持续时间、发送和接收的数据量等。如果发现连接的持续时间较短,或者发送和接收的数据量较少,则说明连接可能存在问题。

4. 使用runqlat跟踪运行队列延迟

如果TCP连接正常,但服务的响应速度仍然较慢,则可能是服务自身的问题。可以使用runqlat来跟踪进程在运行队列中的延迟。运行队列延迟是指进程准备好运行但由于CPU繁忙而需要等待的时间。如果运行队列延迟较长,则说明CPU资源不足。

sudo ./runqlat -d -p $(pidof service-a)

这个命令会跟踪service-a进程在运行队列中的延迟,并打印延迟的分布情况。如果发现延迟较长,则说明CPU资源不足,需要增加CPU资源,或者优化服务的代码。

5. 使用profile生成火焰图

如果CPU资源充足,但服务的响应速度仍然较慢,则可能是服务的代码存在性能瓶颈。可以使用profile生成火焰图,来分析服务的代码的性能瓶颈。

sudo ./profile -d -F 99 -p $(pidof service-a) 60 > out.stacks
./FlameGraph/stackcollapse.pl < out.stacks > out.folded
./FlameGraph/flamegraph.pl < out.folded > flamegraph.svg

这个命令会使用perf工具来采样service-a进程的CPU使用情况,并将采样结果生成火焰图。火焰图可以清晰地显示服务的代码的性能瓶颈,例如哪个函数占用了大量的CPU时间,哪个函数调用了其他的函数等。根据火焰图的分析结果,可以优化服务的代码,提高服务的性能。

6. 分析DNS解析延迟

在Kubernetes环境中,服务间的通信通常依赖DNS解析。DNS解析慢会直接导致服务间调用延迟增加。可以使用dnssnoop这个eBPF工具来监控DNS查询延迟。

sudo ./dnssnoop -d

这个命令会捕获系统上的所有DNS查询请求,并显示查询的域名、IP地址、查询时间和延迟。如果发现某个域名的查询延迟特别高,则需要检查DNS服务器的配置,或者优化DNS缓存策略。

7. 定制eBPF程序:深入挖掘网络细节

以上工具虽然强大,但有时无法满足我们更深入的分析需求。例如,我们可能需要知道某个特定TCP连接的延迟分布情况,或者需要统计某个时间段内的网络包大小分布。这时,就需要编写定制的eBPF程序。

下面是一个简单的eBPF程序示例,用于统计TCP连接的延迟:

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>
struct data_t {
u32 pid;
u64 ts;
u64 delta;
char comm[TASK_COMM_LEN];
u32 saddr;
u32 daddr;
u16 dport;
};
BPF_PERF_OUTPUT(events);
BPF_HASH(start, struct sock *);
int kprobe__tcp_sendmsg(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size)
{
u64 ts = bpf_ktime_get_ns();
start.update(&sk, &ts);
return 0;
}
int kretprobe__tcp_sendmsg(struct pt_regs *ctx)
{
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
u64 *tsp = start.lookup(&sk);
if (tsp == NULL) {
return 0;
}
u64 ts = *tsp;
u64 delta = bpf_ktime_get_ns() - ts;
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.ts = ts;
data.delta = delta / 1000; // us
bpf_get_current_comm(&data.comm, sizeof(data.comm));
data.saddr = sk->__sk_common.skc_rcv_saddr;
data.daddr = sk->__sk_common.skc_daddr;
data.dport = sk->__sk_common.skc_dport;
events.perf_submit(ctx, &data, sizeof(data));
start.delete(&sk);
return 0;
}

这个程序使用了kprobe和kretprobe分别在tcp_sendmsg函数的入口和出口处插入探针。在入口处,程序记录当前的时间戳,并将时间戳保存在一个BPF哈希表中,key为socket指针。在出口处,程序从哈希表中取出时间戳,计算延迟,并将延迟、进程ID、进程名、源IP地址、目标IP地址、目标端口等信息通过perf event发送到用户空间。

为了编译和运行这个程序,我们需要一个Python脚本:

from bcc import BPF
import socket
import struct
# 加载eBPF程序
program = BPF(src_file="tcp_latency.c")
# 定义perf event回调函数
def print_event(cpu, data, size):
event = program["events"].event(data)
print("%d %-16s %-6d %-10d %-20s %-15s %-5d" % (
cpu,
event.comm.decode('utf-8', 'replace'),
event.pid,
event.delta,
"%s:%d -> %s:%d" % (
socket.inet_ntoa(struct.pack('I', event.saddr)),
socket.ntohs(0),
socket.inet_ntoa(struct.pack('I', event.daddr)),
socket.ntohs(event.dport)
),
socket.gethostbyaddr(socket.inet_ntoa(struct.pack('I', event.daddr)))[0],
socket.ntohs(event.dport)
))
# 打印表头
print("%s %-16s %-6s %-10s %-20s %-15s %-5s" % ("CPU", "COMM", "PID", "LAT(us)", "LADDR -> RADDR", "HOSTNAME", "PORT"))
# 绑定perf event回调函数
program["events"].open_perf_buffer(print_event)
# 循环读取perf event
while True:
try:
program.perf_buffer_poll()
except KeyboardInterrupt:
exit()

将上面的C代码保存为tcp_latency.c,Python代码保存为tcp_latency.py,然后运行sudo python tcp_latency.py,就可以看到TCP连接的延迟信息了。

8. eBPF与火焰图的结合

正如前面提到的,火焰图是分析CPU性能瓶颈的利器。我们可以将eBPF程序与火焰图结合起来,更深入地分析网络延迟的原因。例如,我们可以使用eBPF程序来捕获网络包的发送和接收时间,然后使用火焰图来可视化网络包在内核协议栈中的处理过程。

9. 总结与展望

eBPF为Kubernetes集群的网络性能诊断提供了强大的能力。通过使用bcc工具和编写定制的eBPF程序,我们可以深入了解网络延迟的原因,并快速解决问题。随着eBPF技术的不断发展,相信它将在未来的Kubernetes集群管理中发挥越来越重要的作用。

一些建议

  • 从小范围开始: 在生产环境中部署eBPF程序时,建议从小范围开始,例如只在一个pod上运行,观察一段时间后再逐步扩大范围。
  • 注意安全: eBPF程序运行在内核中,如果编写不当,可能会导致系统崩溃。因此,在编写eBPF程序时,一定要注意安全,避免出现bug。
  • 持续学习: eBPF技术发展迅速,建议持续学习新的技术和工具,以便更好地利用eBPF来解决实际问题。

希望这篇文章能够帮助你利用eBPF诊断Kubernetes网络延迟问题。 祝你排障顺利!

额外思考

除了上述方法,还可以考虑以下因素来排查Kubernetes网络延迟:

  • 网络策略: 检查网络策略是否限制了服务之间的通信。
  • CNI插件: 不同的CNI插件对网络性能有不同的影响,可以尝试更换CNI插件。
  • 内核版本: 较新的内核版本通常包含更多的性能优化,可以尝试升级内核版本。
  • 硬件资源: 检查节点的CPU、内存、网络带宽等硬件资源是否充足。

记住,排查网络延迟是一个复杂的过程,需要综合考虑各种因素,并不断尝试和验证。希望eBPF能成为你解决问题的得力助手!

内核侦探 eBPFKubernetes网络延迟

评论点评

打赏赞助
sponsor

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

分享

QRcode

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