WEBKT

eBPF网络监控故障排查实战-如何监控TCP连接并结合Prometheus/Grafana可视化?

40 0 0 0

1. 为什么选择eBPF?

2. eBPF基础知识回顾

3. 监控TCP连接:一个简单的例子

3.1 编写eBPF程序

3.2 编译eBPF程序

3.3 加载和运行eBPF程序

4. 监控TCP延迟和丢包率

4.1 监控TCP延迟

4.2 监控TCP丢包率

5. 结合Prometheus和Grafana进行可视化

5.1 将eBPF数据导出到Prometheus

5.2 在Grafana中创建仪表盘

6. 总结与展望

作为一名资深运维工程师,我深知网络性能监控和故障排查是保障系统稳定运行的关键。传统的网络监控工具往往存在性能开销大、灵活性不足等问题。近年来,eBPF(extended Berkeley Packet Filter)技术的兴起为网络监控带来了革命性的变革。它允许我们在内核态安全地执行自定义代码,实现高性能、灵活的网络数据捕获和分析。今天,我将分享如何使用eBPF技术监控TCP连接,并结合Prometheus和Grafana进行可视化展示,帮助你更好地了解和优化网络性能。

1. 为什么选择eBPF?

在深入技术细节之前,我们先来了解一下为什么选择eBPF进行网络监控:

  • 高性能: eBPF程序运行在内核态,避免了用户态与内核态之间频繁的上下文切换,显著降低了性能开销。
  • 灵活性: eBPF允许我们编写自定义的监控逻辑,可以根据实际需求灵活地捕获和分析网络数据。
  • 安全性: eBPF程序需要经过内核验证器的安全检查,确保不会破坏系统稳定性。
  • 广泛支持: 现代Linux内核已经原生支持eBPF,并且有越来越多的工具和框架基于eBPF构建。

相比于传统的tcpdump、Wireshark等工具,eBPF在性能和灵活性方面具有明显的优势。例如,我们可以使用eBPF程序来监控特定TCP连接的延迟、丢包率等指标,而无需捕获所有网络数据包,从而大大降低了资源消耗。

2. eBPF基础知识回顾

在开始实践之前,我们需要对eBPF的一些基本概念有所了解:

  • eBPF程序类型: eBPF程序有多种类型,例如kprobeuprobetracepointsocket filter等。不同的程序类型对应不同的事件触发机制。
  • eBPF Map: eBPF Map是一种内核态的键值存储,用于在eBPF程序和用户态程序之间共享数据。
  • BPF Helper函数: BPF Helper函数是内核提供的一组API,eBPF程序可以通过这些API访问内核数据和功能。
  • LLVM/Clang: eBPF程序通常使用C语言编写,然后通过LLVM/Clang编译器编译成BPF字节码。

如果你对eBPF还不太熟悉,建议先阅读相关的入门教程和文档,例如:

3. 监控TCP连接:一个简单的例子

下面,我们通过一个简单的例子来演示如何使用eBPF监控TCP连接的建立和关闭。我们将使用kprobe程序类型,分别在tcp_v4_connecttcp_v4_disconnect函数上挂载eBPF程序,记录连接的源IP、目的IP、源端口、目的端口等信息。

3.1 编写eBPF程序

#include <linux/kconfig.h>
#include <linux/version.h>
#include <linux/bpf.h>
#include <bpf_helpers.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
char LICENSE[] SEC("license") = "Dual BSD/GPL";
// 定义存储连接信息的结构体
struct conn_info {
__u32 saddr;
__u32 daddr;
__u16 sport;
__u16 dport;
__u64 ts;
};
// 定义eBPF Map,用于存储连接信息
BPF_HASH(connections, struct conn_info, __u64, 1024);
// 定义kprobe程序,用于监控tcp_v4_connect函数
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(tcp_v4_connect, struct sock *sk) {
struct conn_info conn = {};
struct sock *skp = sk;
// 获取源IP和目的IP
conn.saddr = skp->__sk_common.skc_rcv_saddr;
conn.daddr = skp->__sk_common.skc_daddr;
// 获取源端口和目的端口
conn.sport = skp->__sk_common.skc_num;
conn.dport = skp->__sk_common.skc_dport;
// 转换为网络字节序
conn.dport = ntohs(conn.dport);
// 获取当前时间戳
conn.ts = bpf_ktime_get_ns();
// 将连接信息存储到eBPF Map中
__u64 value = 1; // 连接建立,value设置为1
connections.update(&conn, &value);
return 0;
}
// 定义kprobe程序,用于监控tcp_v4_disconnect函数
SEC("kprobe/tcp_v4_disconnect")
int BPF_KPROBE(tcp_v4_disconnect, struct sock *sk) {
struct conn_info conn = {};
struct sock *skp = sk;
// 获取源IP和目的IP
conn.saddr = skp->__sk_common.skc_rcv_saddr;
conn.daddr = skp->__sk_common.skc_daddr;
// 获取源端口和目的端口
conn.sport = skp->__sk_common.skc_num;
conn.dport = skp->__sk_common.skc_dport;
// 转换为网络字节序
conn.dport = ntohs(conn.dport);
// 获取当前时间戳
conn.ts = bpf_ktime_get_ns();
// 从eBPF Map中删除连接信息
connections.delete(&conn);
return 0;
}

这段代码定义了两个kprobe程序,分别在tcp_v4_connecttcp_v4_disconnect函数上执行。当有新的TCP连接建立时,tcp_v4_connect程序会记录连接的源IP、目的IP、源端口、目的端口和时间戳,并将这些信息存储到名为connections的eBPF Map中。当连接关闭时,tcp_v4_disconnect程序会从connections Map中删除对应的连接信息。

3.2 编译eBPF程序

将上述代码保存为tcp_conn_monitor.c,然后使用LLVM/Clang编译器将其编译成BPF字节码:

clang -O2 -target bpf -c tcp_conn_monitor.c -o tcp_conn_monitor.o

3.3 加载和运行eBPF程序

可以使用bpftoolbcc等工具加载和运行eBPF程序。这里我们使用bcc工具:

#!/usr/bin/env python
from bcc import BPF
import socket
import struct
# 加载eBPF程序
b = BPF(src_file="tcp_conn_monitor.c")
# 获取connections Map
connections = b["connections"]
# 定义打印连接信息的函数
def print_conn_info(key, value):
saddr = socket.inet_ntoa(struct.pack("<I", key.saddr))
daddr = socket.inet_ntoa(struct.pack("<I", key.daddr))
sport = key.sport
dport = key.dport
ts = key.ts
print(f"{saddr}:{sport} -> {daddr}:{dport} TS: {ts}")
# 循环打印connections Map中的连接信息
while True:
try:
for key, value in connections.items():
print_conn_info(key, value)
time.sleep(1)
except KeyboardInterrupt:
exit()

将上述代码保存为run.py,然后运行:

sudo python run.py

运行后,你将会看到不断打印出的TCP连接信息,包括源IP、目的IP、源端口、目的端口和时间戳。

4. 监控TCP延迟和丢包率

除了监控连接的建立和关闭,我们还可以使用eBPF来监控TCP连接的延迟和丢包率。这需要更复杂的eBPF程序,涉及到对TCP报文的解析和统计。

4.1 监控TCP延迟

要监控TCP延迟,我们需要在TCP报文发送和接收的关键路径上挂载eBPF程序,记录报文发送和接收的时间戳,然后计算延迟。以下是一个简化的示例:

// 定义存储延迟信息的结构体
struct latency_info {
__u64 send_ts;
__u64 recv_ts;
};
// 定义eBPF Map,用于存储延迟信息
BPF_HASH(latencies, __u32, struct latency_info, 1024);
// 定义kprobe程序,用于监控tcp_sendmsg函数
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(tcp_sendmsg, struct sock *sk, struct msghdr *msg, size_t size) {
__u32 pid = bpf_get_current_pid_tgid();
struct latency_info latency = {};
// 记录报文发送时间戳
latency.send_ts = bpf_ktime_get_ns();
// 将延迟信息存储到eBPF Map中
latencies.update(&pid, &latency);
return 0;
}
// 定义kprobe程序,用于监控tcp_cleanup_rbuf函数
SEC("kprobe/tcp_cleanup_rbuf")
int BPF_KPROBE(tcp_cleanup_rbuf, struct sock *sk, int copied) {
__u32 pid = bpf_get_current_pid_tgid();
struct latency_info *latency = latencies.lookup(&pid);
// 检查是否存在对应的发送记录
if (latency) {
// 记录报文接收时间戳
latency->recv_ts = bpf_ktime_get_ns();
// 计算延迟
__u64 latency_ns = latency->recv_ts - latency->send_ts;
// 打印延迟信息
bpf_trace_printk("Latency: %llu ns\n", latency_ns);
// 从eBPF Map中删除延迟信息
latencies.delete(&pid);
}
return 0;
}

这段代码分别在tcp_sendmsgtcp_cleanup_rbuf函数上挂载eBPF程序,记录报文发送和接收的时间戳,并计算延迟。tcp_sendmsg函数在报文发送时被调用,tcp_cleanup_rbuf函数在报文接收时被调用。我们使用eBPF Map latencies来存储报文发送的时间戳,然后在报文接收时查找对应的发送记录,计算延迟。

4.2 监控TCP丢包率

要监控TCP丢包率,我们需要跟踪TCP报文的序列号和确认号,检测是否存在序列号跳跃或重复确认的情况。以下是一个简化的示例:

// 定义存储序列号信息的结构体
struct seq_info {
__u32 seq;
__u32 ack;
};
// 定义eBPF Map,用于存储序列号信息
BPF_HASH(seqs, __u32, struct seq_info, 1024);
// 定义kprobe程序,用于监控tcp_rcv_established函数
SEC("kprobe/tcp_rcv_established")
int BPF_KPROBE(tcp_rcv_established, struct sock *sk, struct sk_buff *skb, const int len) {
__u32 pid = bpf_get_current_pid_tgid();
struct seq_info seq = {};
// 获取序列号和确认号
seq.seq = skb->skb_seq;
seq.ack = skb->skb_ack;
// 从eBPF Map中查找之前的序列号信息
struct seq_info *prev_seq = seqs.lookup(&pid);
// 检查是否存在丢包或重复确认
if (prev_seq) {
if (seq.seq != prev_seq->ack) {
// 丢包
bpf_trace_printk("Packet loss detected!\n");
} else if (seq.ack == prev_seq->ack) {
// 重复确认
bpf_trace_printk("Duplicate ACK detected!\n");
}
}
// 更新序列号信息
seqs.update(&pid, &seq);
return 0;
}

这段代码在tcp_rcv_established函数上挂载eBPF程序,该函数在接收到已建立连接的TCP报文时被调用。我们使用eBPF Map seqs来存储之前的序列号信息,然后与当前报文的序列号和确认号进行比较,检测是否存在丢包或重复确认的情况。

注意: 上述代码仅仅是简化的示例,实际的TCP延迟和丢包率监控需要更复杂的逻辑来处理各种边界情况,例如TCP重传、乱序到达等。你可以参考一些开源的eBPF网络监控工具,例如bccbpftrace,学习它们是如何实现这些功能的。

5. 结合Prometheus和Grafana进行可视化

仅仅在命令行打印监控数据是不够的,我们需要将这些数据集成到Prometheus和Grafana中,实现可视化展示和告警。

5.1 将eBPF数据导出到Prometheus

Prometheus是一个流行的开源监控系统,它使用基于HTTP的拉取模型来收集监控数据。我们可以编写一个用户态程序,从eBPF Map中读取监控数据,然后将其暴露为Prometheus的指标。

以下是一个使用Python和prometheus_client库将TCP连接数导出到Prometheus的示例:

#!/usr/bin/env python
from bcc import BPF
from prometheus_client import start_http_server, Gauge
import time
import socket
import struct
# 定义Prometheus Gauge指标
tcp_connections = Gauge('tcp_connections_total', 'Total number of TCP connections')
# 加载eBPF程序
b = BPF(src_file="tcp_conn_monitor.c")
# 获取connections Map
connections = b["connections"]
# 启动HTTP服务器,暴露Prometheus指标
start_http_server(8000)
# 循环读取connections Map中的连接数,并更新Prometheus指标
while True:
try:
count = len(connections)
tcp_connections.set(count)
time.sleep(1)
except KeyboardInterrupt:
exit()

这段代码首先定义了一个Prometheus Gauge指标tcp_connections_total,用于表示TCP连接数。然后,它加载了之前编写的eBPF程序,并获取了connections Map。最后,它启动了一个HTTP服务器,循环读取connections Map中的连接数,并将其设置为tcp_connections_total指标的值。Prometheus可以通过HTTP请求/metrics端点来获取这些指标数据。

5.2 在Grafana中创建仪表盘

Grafana是一个流行的开源数据可视化工具,它可以从Prometheus等数据源中读取数据,并创建各种图表和仪表盘。

  1. 添加Prometheus数据源: 在Grafana中添加Prometheus数据源,配置Prometheus服务器的地址和端口。
  2. 创建仪表盘: 创建一个新的仪表盘,并添加一个图表面板。
  3. 配置查询: 在图表面板的查询编辑器中,输入Prometheus查询语句,例如tcp_connections_total,即可显示TCP连接数的实时变化曲线。

你可以根据实际需求,创建各种各样的图表和仪表盘,例如:

  • TCP连接数随时间变化的曲线
  • TCP延迟的直方图
  • TCP丢包率的饼图
  • 按源IP或目的IP分组的TCP连接数

通过Grafana的可视化展示,你可以更直观地了解网络性能,及时发现和解决问题。

6. 总结与展望

eBPF技术为网络监控和故障排查带来了革命性的变革。它具有高性能、灵活性、安全性和广泛支持等优点,可以帮助我们更好地了解和优化网络性能。本文介绍了如何使用eBPF监控TCP连接,并结合Prometheus和Grafana进行可视化展示。希望这些实践经验能够帮助你更好地利用eBPF技术,提升网络运维效率。

当然,eBPF技术还有很多其他的应用场景,例如:

  • 网络安全: 使用eBPF进行DDoS攻击防御、入侵检测等。
  • 性能分析: 使用eBPF进行CPU、内存、磁盘IO等性能分析。
  • 应用跟踪: 使用eBPF进行应用调用链跟踪、性能瓶颈分析等。

随着eBPF技术的不断发展,相信它将在更多的领域发挥重要作用。作为一名技术人员,我们应该保持学习和探索的热情,不断掌握新的技术,为业务发展提供更好的支持。

希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎留言交流。

网络小能手 eBPF网络监控Prometheus

评论点评

打赏赞助
sponsor

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

分享

QRcode

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