深入浅出运维可观测工具(四):如何使用eBPF绘制网络拓扑图

本文主要是介绍深入浅出运维可观测工具(四):如何使用eBPF绘制网络拓扑图,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

哈喽~又到了我们技术分享环节了。eBPF这个系列自分享以来收到了很多朋友的喜欢,真是让博主又惊又喜,感谢大家的支持。话不多说,今天我们将对如何使用eBPF绘制网络拓扑图做一篇分享,文章较长,干货较多,大家可以一键三连先码后看~

新来的朋友点这里,一键回看前期精彩技术干货。

深入浅出运维可观测工具(三):eBPF如何兼容多架构模式性能管理icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/132697151

擎创技术流 | 深入浅出运维可观测工具(二):eBPF应用中常见问题icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/131919692

擎创技术流 | 深入浅出运维可观测工具(一):聊聊eBPF的前世今生icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/131901910

一、eBPF出现的背景

1.传统架构下排查问题过于依赖技术人员的水平

在传统架构下,应用程序通过发布工具部署到虚拟机环境中。在这种模式下,对于网络性能的监控和观测主要依赖一些常规的技术,比如专门的网络监控工具如sniffer,还有一些网络抓包工具比如Wireshark、tcpdump(bpf技术)等,这些工具可以捕获和分析网络流量,检测网络延迟、丢包等性能问题。

比如对应用服务请求响应超时,无法确认是网络问题还是应用程序自身问题,需要指定一段时间并对应用监听的端口号进行抓包,然后把抓到的数据包逐个分析并找到对应的请求。但是整个过程却非常繁琐费时,十分依赖排障人员的技术能力。

2.云原生的应用导致应用间的调用关系更加复杂

随着云原生架构和容器技术的火热,应用程序上云并且作为微服务容器化部署,各种应用中间件实时动态扩缩容,各种复杂的调用关系,比如Pod之间相互调用、Pod通过service请求应用、pod访问kubernetes外的服务、云外通过ingress访问云内应用等等,都给网络排查带来了巨大的困难,完全无法通过原先传统架构下指定ip和port的抓包方式进行排查。

3.部署方式发生重大变化

由于部署方式发生了较大变化,由原先的单一进程(process),变为了容器(container)、容器组(pod)、工作负载(deployment)、服务(service)等等,网络监控需求也由简单的网络接口抓包,衍变为需要近实时看到容器、容器组、工作负载各个维度的tcp、udp流量、http调用访问、网络拓扑关系等。

4.eBPF灵活强大的优势

eBPF 技术的出现为网络性能管理带来了更强大、更灵活、更有效的工具和方法。它能够实时监测和分析网络流量,提供精细的流量控制和优化能力,并提高网络安全性和故障排查能力,对于云原生复杂的网络环境,ebpf允许用户在内核中编写和执行自定义的代码逻辑,以实时监测和分析网络流量。通过eBPF技术能够捕获每一个进程的网络连接信息以及对应的tcp延时、波动、连接数、重传数等指标,进而可以由进程关联到所属的容器,通过每一个容器可以关联到所在的容器组以及每一个工作负载的网络连接

二、如何使用eBPF绘制网络拓扑图

1.了解tcp发送过程

Linux是一个强大的操作系统,提供了丰富的系统调用接口。我们发送网络数据的时候通过调用系统调用发送网络数据。

要绘制网络拓扑图,我们首先要了解linux处理请求过程:

通过上面的流程我们发现一个RPC最基本的调用流程大致如下:

1)客户端建立连接(Establishing Connection)
  • 客户端向服务端调用connect函数建立连接。

  • 服务端使用accept接收连接。

2)数据传输(Data Transfer)

已建立连接的两端可以可以通过调用send、recv系列函数进行数据的传输。

3)断开连接(Terminating Connection)

当数据传输完毕或不再需要连接时,一方可以通过调用close发送一个FIN(结束)报文段。接收到FIN报文段的一方再次调用close发送一个ACK报文段作为确认。这个过程称为四次挥手,确保连接的安全关闭。

2.总结linux用于网络传输

1)使用的系统调用

我们通过分析<< unix网络编程第一卷 >> 找出网络传输用到的系统调用

基本套接字编程所用到的系统调用

这些函数统一需要传入fd参数,例如send函数,它的系统调用需要入参如下:

size_t send(int sockfd, const void *buf, size_t len, int flags);

我们需要使用eBPF,hook这些函数 找到对应的linux进程下sockfd 所对应的tcp五元组,然后进行汇总统计来绘制网络拓扑图。

3.对监控网络场景进行分析

1)擎创采集器开启后建立的TCP连接

擎创采集器启动后。服务程序通过调用 listen 和 accept 函数,接收客户端的连接请求,并与客户端建立通信。

而对于客户端而言,通过调用 connect 函数来建立和服务程序的连接。这些连接和之后的数据传输过程使用的系统调用,通过使用基于eBPF的技术,都可以eBPF嵌入字节码程序并进行分析。

2)擎创采集器开启前建立的TCP连接

由于调用方和服务器的tcp 连接过程早已创建完成。并不会再触发connect 和 accept的系统调用,只是在数据传输中调用send、recv或者write、read系列的函数。

如果应用程序之间数据传输使用的是write和read,并没有调用sockfd_lookup_light,所以暂时无法通过fd找到对应的sock 五元组。

但是如果应用程序之间的传输使用的是send和recv等系统调用,kernel 在内部会调sockfd_lookup_light,我们可以通过 pid 和fd的组合翻译出对应的sock五元组。

由于connect和accept在采集器启动之前已经建立连接,所以我们无法通过eBPF确认出流量的出入口方向。

例如go的一些应用程序:

https://pkg.go.dev/net@go1.21.0#IPConn.Read

IPConn 只提供了Read和write接口,如果TCP连接在采集器启动之前已经建立了连接,我们暂时无法把fd翻译成TCP五元组,也无法确认流量方向。

4.通过linux源码分析系统调用

1)找到hook点

linux 内核中定义recvmsg

SYSCALL_DEFINE3(recvmsg, int, fd, struct user_msghdr __user *, msg, unsigned int, flags)

内核整个调用过程

SYSCALL_DEFINE3(recvmsg) -> __sys_recvmsg->sockfd_lookup_light->___sys_recvmsg

通过解读recvmsg 我们发现 kernel 会在我们调用系统调用后通过sockfd_lookup_light把传进来的fd整数转化成为kernel的sock数据结构,即在给定的网络命名空间中查找并返回与指定文件描述符(socket 文件描述符)相对应的 struct socket 结构体指针,以便对套接字进行进一步的操作。

sockfd_lookup_light 使用的系统调用有:

read 和 write 并没有使用 sockfd_lookup_light,统一把 read write 处理操作挂到了,文件描述符操作结构体上

struct file_operations { struct module *owner; ......... ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
........ } __randomize_layout;

5.我们使用eBPF

1)hook sockfd_lookup_light

首先我们uprobe 住入口函数,记下进程号和 描述符的对应关系,因为每个进程的fd 都会对应一个sock结构体,所以我们把pid 进程号和 fd 组成一个key。

SEC("kprobe/sockfd_lookup_light")
int kprobe_hook_sockfd_lookup_light(struct pt_regs *ctx) { int sockfd = (int)PT_REGS_PARM1(ctx);u64 pid_tgid = bpf_get_current_pid_tgid(); pid_fd_map key = { .pid = pid_tgid >> 32, .fd = sockfd, }; struct sock **sock = bpf_map_lookup_elem(&sock_by_pid_fd_storage, &key); if (sock != NULL) { return 0; }bpf_map_update_elem(&sockfd_save_args, &pid_tgid, &sockfd, BPF_ANY); return 0; 
}

最后我们再 sockfd_lookup_light 把 pid和fd 的对应关系存储到map

SEC("kretprobe/sockfd_lookup_light")
int kretprobe_hook_sockfd_lookup_light(struct pt_regs *ctx) {u64 pid_tgid = bpf_get_current_pid_tgid(); int *sockfd = bpf_map_lookup_elem(&sockfd_save_args, &pid_tgid); if (sockfd == NULL) { return 0; }struct socket *socket = (struct socket *)PT_REGS_RC(ctx);enum sock_type sock_type = 0; bpf_probe_read_kernel(&sock_type, sizeof(short), &socket->type); ................. // 读出sock 结构体 struct sock *sock = NULL; bpf_core_read(&sock, sizeof(sock), (char *)socket);pid_fd_map pid_fd = { .pid = pid_tgid >> 32, .fd = (*sockfd), };bpf_map_update_elem(pid_fd_by_sock, &sock, &pid_fd, BPF_ANY); 
cleanup: bpf_map_delete_elem(&sockfd_save_args, &pid_tgid);
}

每当内核触发sockfd_lookup_light的时候我们就会把 pid,fd 以及sock 对应关系存储到sockfd_save_args中。

socket 五元组多存储在linux 内核的 struct sock 中

 

6.使用eBPF 绘制网络拓扑

通过hook accept 标记处的入口流量

SEC("kretprobe/inet_csk_accept")

通过hook connect 标记处的出口流量

SEC("kprobe/tcp_connect") SEC("kprobe/tcp_finish_connect")

通过hook sockfd_lookup_light 翻译出进程的fd对应的sock五元组

SEC("kretprobe/sockfd_lookup_light")

程序在调用系统调用的时候,我们就很容易使用 通过pid fd,在eBPF的 sock_by_pid_fd_storage map中 找到对应的sock,找到了kernel sock 结构体我们就可以通过sock 五元组,绘制出流量方向。

绘制网络拓扑图的过程中,我们使用eBPF可以抓取网络数据包并对其进行解析,获取各个节点之间的连接信息,包括源地址、目标地址、协议类型、端口等。通过收集和分析这些数据,我们可以逐步构建整个网络拓扑,并将其可视化为图形化的形式。

最后结合使用eBPF程序和图形数据库,我们完成了网络拓扑的绘制:

以上即为本次eBPF绘制网络拓扑图的全部内容。可观测之eBPF系列,正在不断创作中,欢迎您长期关注~

如果有用,请辛苦点个赞吧!


擎创科技,Gartner连续推荐的AIOps领域标杆供应商。公司专注于通过提升企业客户对运维数据的洞见能力,为运维降本增效,充分体现科技运维对业务运营的影响力。

行业龙头客户的共同选择

了解更多运维干货与行业前沿动态

可以右上角一键关注

我们是深耕智能运维领域近十年的

连续多年获Gartner推荐的AIOps标杆供应商

下期我们不见不散~

这篇关于深入浅出运维可观测工具(四):如何使用eBPF绘制网络拓扑图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/786677

相关文章

springboot security快速使用示例详解

《springbootsecurity快速使用示例详解》:本文主要介绍springbootsecurity快速使用示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录创www.chinasem.cn建spring boot项目生成脚手架配置依赖接口示例代码项目结构启用s

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

GORM中Model和Table的区别及使用

《GORM中Model和Table的区别及使用》Model和Table是两种与数据库表交互的核心方法,但它们的用途和行为存在著差异,本文主要介绍了GORM中Model和Table的区别及使用,具有一... 目录1. Model 的作用与特点1.1 核心用途1.2 行为特点1.3 示例China编程代码2. Tab

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

使用Python实现获取网页指定内容

《使用Python实现获取网页指定内容》在当今互联网时代,网页数据抓取是一项非常重要的技能,本文将带你从零开始学习如何使用Python获取网页中的指定内容,希望对大家有所帮助... 目录引言1. 网页抓取的基本概念2. python中的网页抓取库3. 安装必要的库4. 发送HTTP请求并获取网页内容5. 解

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

使用Python实现网络设备配置备份与恢复

《使用Python实现网络设备配置备份与恢复》网络设备配置备份与恢复在网络安全管理中起着至关重要的作用,本文为大家介绍了如何通过Python实现网络设备配置备份与恢复,需要的可以参考下... 目录一、网络设备配置备份与恢复的概念与重要性二、网络设备配置备份与恢复的分类三、python网络设备配置备份与恢复实