深入浅出运维可观测工具(四):如何使用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

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali

C#中lock关键字的使用小结

《C#中lock关键字的使用小结》在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时,其他线程无法访问同一实例的该代码块,下面就来介绍一下lock关键字的使用... 目录使用方式工作原理注意事项示例代码为什么不能lock值类型在C#中,lock关键字用于确保当一个线程位于给定实例的代码块中时