云原生网络扫雷笔记:探究一条活跃连接却有TCP OOM的奇怪问题

简介: 本文联合作者 @牧原问题的背景某个名字很喜气的大客户的前线同学在一个傍晚找到了我们团队,反馈网络出现了严重的卡顿现象:“这个节点上所有的服务都很卡,扩容之后没几天还会出现!本来以为是AMD的问题,现在换了机型还是一样。”从客户的表述中我们已经了解到,在此之前他们做了很多的尝试,但是现象很明显:新节点调度业务Pod上去后,过一段时间就会出现。和机型没啥关系。随后客户反馈了一个比较关键的信息,他们有注

本文联合作者 @牧原

问题的背景

某个名字很喜气的大客户的前线同学在一个傍晚找到了我们团队,反馈网络出现了严重的卡顿现象:

“这个节点上所有的服务都很卡,扩容之后没几天还会出现!本来以为是AMD的问题,现在换了机型还是一样。”

从客户的表述中我们已经了解到,在此之前他们做了很多的尝试,但是现象很明显:

  1. 新节点调度业务Pod上去后,过一段时间就会出现。
  2. 和机型没啥关系。

随后客户反馈了一个比较关键的信息,他们有注意到TCP OOM出现,但是因为流量很小,觉得和这个没关系。

问题的排查过程

排查客户问题的第一步,就是不要轻易相信客户说的话,于是我们开始验证客户所说的现象。

首先是速率很卡,这个非常好验证,在VNC的操作过程中,我们能很明显的感觉到有卡顿。

随后是客户反馈的TCP OOM,查看了客户异常节点的dmesg信息,确实,客户的节点在问题出现的时候持续有这个现象:

TCP OOM指的是TCP协议能够使用的内存触发了配置的阈值,在Linux内核中,作为socket子系统的一个实现,TCP协议能够使用的内存通过net.ipv4.tcp_mem这个sysctl设置三个值,分别代表着min,pressure,max。

TCP OOM的出现,意味着TCP协议使用的内存已经到达了max,除了fin相关的报文之外,TCP协议只能释放一点内存才能使用一点,所以出现卡顿的原因也就不难理解了。

然后我们开始检查客户的连接数量,对于容器网络来说,不同的Pod通过Linux的netns进行网络上的隔离,因此,我们重点针对不同的Pod开始统计netstat中的TCP连接数量,然而结果显示,确实如客户所说,所有的Pod的连接数都不多,其中能看到有明显的流量出入连接仅仅是个位数:

那么,有没有可能是这些连接本身有巨大的吞吐量呢?我们针对上面有较大流量的Pod进行了观测,结果显示,流量也很小。

到了这一步,能确定的现象是:

  1. 客户确实存在TCP协议消耗了大量的内存。
  2. 客户的流量不高。

两个现象结合在一起,就不得不怀疑客户的节点上出现了socket的泄漏,随后我们查看了节点级别的socket状态:

果然,客户的节点上出现了明显的socket泄漏,而TCP协议的socket占用的内存总量也只比tcp_mem默认的配置上限。sockstat信息和tcp_mem的配置都是节点级别生效的,从上图中的信息不难得出:

  1. TCP协议正常使用的socket(通常就是ESTABLISHED状态的连接)是38,tw状态(TIMEWAIT)也只有49,但是alloc状态(分配成功的socket)的socket却有7385个。
  2. orphan状态的socket是0个,很少,orphan状态比较特殊,他指的是“没有被用户进程持有的socket”,也就是说,已经释放但是还没有被内核回收的socket都归为此类,通常在连接建立频繁的业务上会出现较多,显然客户这个节点不属于这种情况。

那么,alloc但是却不是tw,inuse和orphan的socket,到底去哪儿了呢?

我们回顾以上上面几个socket状态的语义:

  1. inUse,这个很好理解,在内核中通过sock_prot_inuse_get进行统计,属于实时的状态。
  2. tw,这个也不难理解,tw_count也是内核直接维护的变量。
  3. orphans,如上所述,除了inUse和tw,其他没有用户程序持有fd的socket都归类在这里,在内核的tcp_close中增长,在inet_csk_destroy_sock中减少。

那么在这三者之外,最有可能的去处是哪里呢?那就是,已经被关闭了连接,但是仍然被用户程序持有的socket。

理解这个概念其实需要明白socket与TCP连接之间的关系。在Linux中,一切皆文件,socket本身也是sockfs这个文件系统实现的一类文件:

  1. 用户程序可以通过操作文件的方式,例如open/close等操作一个socket文件,相应的也会持有socket的fd。
  2. TCP作为socket的一种类型,将响应的操作实现为TCP协议的动作,例如open会打开一个TCP的socket,write可以实现向TCP流写入数据等。

如果存在socket文件还在用户程序的持有状态,但是TCP会话已经正常关闭,那么我们将能够看到还存在大量的socket类型的fd在某一个进程的活跃句柄中,随机我们对节点上所有进程进行了统计:

for i in $(ps aux |awk '{print $2}');do echo $i;ls -l /proc/$i/fd |awk '/socket/' |wc -l;done

果然我们找到了这样的进程:

可以看到53129这个进程持有了5336而socket类型的fd,看起来socket泄漏的主要元凶就这这个进程。

随后我们和客户确认了这个进程,是一个直播业务,在这个Pod中,只有一条ESTABLISHED状态的连接,出现如此巨大的socket泄漏,显然是客户代码写的有问题,问题排查到这里,客户已经了解了问题的前因后果,但是拒绝提供代码让我们进行分析,于是我们复现了这个场景,以下代码实现了和客户一样的bug:

import socket

def main():
    # 创建socket对象
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 绑定地址和端口
    sock.bind(('0.0.0.0', 8888))
    # 开始监听
    sock.listen(5)
    count = 0
    connections = {}
    while True:
        # 接受连接请求
        conn, addr = sock.accept()
        # 发送连接总数
        count += 1
        connections[addr] = conn
        conn.send(f'当前连接总数:{count}'.encode())
        # 关闭连接
        #conn.close()

if __name__ == '__main__':
    main()

如代码中所示,我们提供了一个监听8888端口的TCP服务,在每次接受请求后,将返回的socket文件描述符宝存在一个dict中防止被自动释放,随后我们再提供一个client脚本用于模拟客户端:

import socket
import time

def client():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('10.1.18.154', 8888))
    print(sock.recv(1024).decode())
    sock.close()

while True:
    client()
    time.sleep(2)

客户端也很简单:

  1. 发起对服务端的连接,然后接受并打印服务端的信息也就是服务端当前已经保存的socket数量,随后主动关闭连接。
  2. 每间隔2s重复一次上述的行为,用来产生泄漏的socket。

我们将server部署到kubernetes中,然后开始进行测试,果不其然,很快就能看到socket数量在上升:

看一下服务端的状态,我们通过kubeskoop-exporter的Pod进行更加高效的检查:

# 选择为与上面的server的Pod同节点kubeskoop-exporter实例即可
kubectl exec -it -n kubeskoop kubeskoop-exporter-ns9kz -- sh

可以发现,此时我们的server的Pod没有任何处于ESTABLISHED状态的连接,而查看进程打开的socket,则会发现有很多:

分析到这里,其实客户此次出现socket泄漏的原因已经非常清晰了,客户还有一个疑问:明明内核已经关闭了TCP连接,所有的数据都被用户程序读取了,为什么泄漏的socket还会占据大量内存呢?

其实这个问题在上面的文章中已经提到过,对于TCP协议而言,他所统计的内存并不仅仅指的是内核占据的部分,对于socket这个文件在用户态申请的内存,也就是用户视角的socket这个文件中存在的内容,也会计算在其中,所以当socket泄漏时,很大一部分的用户态的内存占据了TCP协议使用内存的额度,产生了这个问题。

问题的背后

尽管这个问题是一个客户代码引入的问题,我们还是能够看到,从ECS时代走向云原生时代,容器的抽象带来了排查问题的挑战,对于不了解容器底层原理的同学来说,排查容器的网络问题,往往会有这样的痛点:

  1. 那些参数是节点级别,那些参数又只针对容器生效?
  2. 如何更加高效的收集数据,尤其是在netstat这些传统工具无法应对容器场景的情况下。

为了解决这样的痛苦,我们在kubeskoop项目中提供了大量的功能来帮助不熟悉网络底层原理的同学更加高效的进行排查,例如,在这个案例中的socket类型的fd的打开数量,我们已经在kubeskoop新版本中做了支持:

现在你可以通过一个简单的命令就可以清晰地查看每个Pod打开的fd数量和socket类型的fd数量,结合上面已经就绪的针对TCP和socket的支持,排查这一类问题,将会事半功倍。欢迎大家就kubeskoop项目与我们交流,更多的信息,可以直接查看项目的主页 https://github.com/alibaba/kubeskoop

目录
相关文章
|
9天前
|
机器学习/深度学习 数据可视化 计算机视觉
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
这篇文章详细介绍了如何通过可视化深度学习中每层特征层来理解网络的内部运作,并使用ResNet系列网络作为例子,展示了如何在训练过程中加入代码来绘制和保存特征图。
28 1
目标检测笔记(五):详细介绍并实现可视化深度学习中每层特征层的网络训练情况
|
11天前
|
机器学习/深度学习 数据可视化 Windows
深度学习笔记(七):如何用Mxnet来将神经网络可视化
这篇文章介绍了如何使用Mxnet框架来实现神经网络的可视化,包括环境依赖的安装、具体的代码实现以及运行结果的展示。
29 0
|
9天前
|
机器学习/深度学习 编解码 算法
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
MobileNetV3是谷歌为移动设备优化的神经网络模型,通过神经架构搜索和新设计计算块提升效率和精度。它引入了h-swish激活函数和高效的分割解码器LR-ASPP,实现了移动端分类、检测和分割的最新SOTA成果。大模型在ImageNet分类上比MobileNetV2更准确,延迟降低20%;小模型准确度提升,延迟相当。
29 1
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
|
9天前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
37 1
目标检测笔记(一):不同模型的网络架构介绍和代码
|
3天前
|
安全 网络架构
无线网络:连接未来的无形纽带
【10月更文挑战第13天】
28 8
|
9天前
|
机器学习/深度学习 数据采集 算法
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
这篇博客文章介绍了如何使用包含多个网络和多种训练策略的框架来完成多目标分类任务,涵盖了从数据准备到训练、测试和部署的完整流程,并提供了相关代码和配置文件。
20 0
目标分类笔记(一): 利用包含多个网络多种训练策略的框架来完成多目标分类任务(从数据准备到训练测试部署的完整流程)
|
9天前
|
编解码 人工智能 文件存储
轻量级网络论文精度笔记(二):《YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object ..》
YOLOv7是一种新的实时目标检测器,通过引入可训练的免费技术包和优化的网络架构,显著提高了检测精度,同时减少了参数和计算量。该研究还提出了新的模型重参数化和标签分配策略,有效提升了模型性能。实验结果显示,YOLOv7在速度和准确性上超越了其他目标检测器。
25 0
轻量级网络论文精度笔记(二):《YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object ..》
|
11天前
|
机器学习/深度学习 Python
深度学习笔记(九):神经网络剪枝(Neural Network Pruning)详细介绍
神经网络剪枝是一种通过移除不重要的权重来减小模型大小并提高效率的技术,同时尽量保持模型性能。
31 0
深度学习笔记(九):神经网络剪枝(Neural Network Pruning)详细介绍
|
11天前
|
机器学习/深度学习 算法 TensorFlow
深度学习笔记(五):学习率过大过小对于网络训练有何影响以及如何解决
学习率是深度学习中的关键超参数,它影响模型的训练进度和收敛性,过大或过小的学习率都会对网络训练产生负面影响,需要通过适当的设置和调整策略来优化。
87 0
深度学习笔记(五):学习率过大过小对于网络训练有何影响以及如何解决
|
11天前
|
机器学习/深度学习 算法
深度学习笔记(四):神经网络之链式法则详解
这篇文章详细解释了链式法则在神经网络优化中的作用,说明了如何通过引入中间变量简化复杂函数的微分计算,并通过实例展示了链式法则在反向传播算法中的应用。
23 0
深度学习笔记(四):神经网络之链式法则详解