写点什么

网易数帆内核团队:memory cgroup 泄漏问题的分析与解决

  • 2022-01-05
  • 本文字数:3735 字

    阅读完需:约 12 分钟

网易数帆内核团队:memory cgroup 泄漏问题的分析与解决

memory cgroup 泄露是 K8s(Kubernetes) 集群中普遍存在的问题,轻则导致节点内存资源紧张,重则导致节点无响应只能重启服务器恢复;大多数的开发人员会采用定期 drop cache 或者关闭内核 kmem accounting 进行规避。本文基于网易数帆内核团队的实践案例,对 memory cgroup 泄露问题的根因进行分析,同时提供了一种内核层面修复该问题的方案。

背景

运维监控发现部分云主机计算节点,K8s(Kubernetes) 节点都有出现负载异常冲高的问题,具体表现为系统运行非常卡,load 持续在 40+,部分的 kworker 线程 cpu 使用率较高或者处于 D 状态,已经对业务造成了影响,需要分析具体的原因。

问题定位

现象分析

对于 cpu 使用率异常的问题,perf 是必不可少的工具。通过 perf top 观察热点函数,发现内核函数 cache_reap 使用率会间歇性冲高。

翻开对应的内核代码,cache_reap 函数实现如下:

不难看出,该函数主要是遍历一个全局的 slab_caches 链表,该链表记录的是系统上所有的 slab 内存对象相关信息。

通过分析 slab_caches 变量相关的代码流程发现,每一个 memory cgroup 都会对应一个 memory.kmem.slabinfo 文件。

该文件里面记录的是各个 memory cgroup 组进程申请的 slab 相关信息,同时该 memory cgroup 组的 slab 对象也会被统一添加到全局的 slab_caches 链表中,莫非是因为 slab_caches 链表数据太多,导致遍历时间比较长,进而导致 CPU 冲高?


slab_caches 链表数据太多,那么前提肯定是 memory cgroup 数量要特别多,自然而然也就想到要去统计一下系统上存在多少个 memory cgroup,但当我们去统计/sys/fs/cgroup/memory 目录下的 memory cgroup 组的数量时发现也就只有一百个不到的 memory cgroup,每个 memory cgroup 里面 memory.kmem.slabinfo 文件最多也就包含几十条记录,所以算起来 slab_caches 链表成员个数最多也不会超过一万个,所以怎么看也不会有问题。

最终还是从最原始的函数 cache_reap 入手,既然该函数会比较消耗 CPU,那么直接通过跟踪该函数来分析究竟是代码里面什么地方执行时间比较长。

确认根因

通过一系列工具来跟踪 cache_reap 函数发现,slab_caches 链表成员个数达到了惊人的几百万个,该数量跟我们实际计算出来的数量差异巨大。


再通过 cat /proc/cgroup 查看系统的当前 cgroup 信息,发现 memory cgroup 数量已经累积到 20w+。在云主机计算节点上存在这么多的 cgroup,明显就不是正常的情况,即便是在 K8s(Kubernetes) 节点上,这个数量级的 cgroup 也不可能是容器业务能正常产生的。

那么为什么/sys/fs/cgroup/memory 目录下统计到的 memory cgroup 数量和/proc/cgroups 文件记录的数量会相差如此之大了?因为 memory cgroup 泄露导致!


详细解释参考如下:


系统上的很多操作(如创建销毁容器/云主机、登录宿主机、cron 定时任务等)都会触发创建临时的 memory cgroup。这些 memory cgroup 组内的进程在运行过程中可能会产生 cache 内存(如访问文件、创建新文件等),该 cache 内存会关联到该 memory cgroup。当 memory cgroup 组内进程退出后,该 cgroup 组在/sys/fs/cgroup/memory 目录下对应的目录会被删除。但该 memory cgroup 组产生的 cache 内存并不会被主动回收,由于有 cache 内存还在引用该 memory cgroup 对象,所以也就不会删除该 memory cgroup 在内存中的对象。


在定位过程中,我们发现每天的 memory cgroup 的累积数量还在缓慢增长,于是对节点的 memory cgroup 目录的创建、删除进行了跟踪,发现主要是如下两个触发源会导致 memory cgroup 泄露:

  1. 特定的 cron 定时任务执行 

  2. 用户频繁登录和退出节点


这两个触发源导致 memory cgroup 泄漏的原因都是跟 systemd-logind 登录服务有关系,执行 cron 定时任务或者是登录宿主机时,systemd-logind 服务都会创建临时的 memory cgroup,待 cron 任务执行完或者是用户退出登录后,会删除临时的 memory cgroup,在有文件操作的场景下会导致 memory cgroup 泄漏。

复现方法

分析清楚了 memory cgroup 泄露的触发场景,那就复现问题就容易很多:

核心的复现逻辑就是创建临时 memory cgroup,并进行文件操作产生 cache 内存,然后删除 memory cgroup 临时目录,通过以上的方式,在测试环境能够很快的复现 40w memory cgroup 残留的现场。

解决方案

通过对 memory cgroup 泄漏的问题分析,基本搞清楚了问题的根因与触发场景,那么如何解决泄露的问题呢?

方案一:drop cache

既然 cgroup 泄露是由于 cache 内存无法回收引起的,那么最直接的方案就是通过“echo 3 > /proc/sys/vm/drop_caches”清理系统缓存。


但清理缓存只能缓解,而且后续依然会出现 cgroup 泄露。一方面需要配置每天定时任务进行 drop cache,同时 drop cache 动作本身也会消耗大量 cpu 对业务造成影响,而对于已经形成了大量 cgroup 泄漏的节点,drop cache 动作可能卡在清理缓存的流程中,造成新的问题。

方案二:nokmem

kernel 提供了 cgroup.memory = nokmem 参数,关闭 kmem accounting 功能,配置该参数后,memory cgroup 就不会有单独的 slabinfo 文件,这样即使出现 memory cgroup 泄露,也不会导致 kworker 线程 CPU 冲高了。


不过该方案需要重启系统才能生效,对业务会有一定影响,且该方案并不能完全解决 memory cgroup 泄露这个根本性的问题,只能在一定程度缓解问题。

方案三:消除触发源

上面分析发现的 2 种导致 cgroup 泄露的触发源,都可以想办法消除掉。


针对第 1 种情况,通过与相应的业务模块沟通,确认可以关闭该 cron 任务; 

针对第 2 种情况,可以通过 loginctl enable-linger username 将对应用户设置成后台常驻用户来解决。


设置成常驻用户后,用户登录时,systemd-logind 服务会为该用户创建一个永久的 memory cgroup 组,用户每次登录时都可以复用这个 memory cgroup 组,用户退出后也不会删除,所以也就不会出现泄漏。


到此时看起来,这次 memory cgroup 泄漏的问题已经完美解决了,但实际上以上处理方案仅能覆盖目前已知的 2 个触发场景,并没有解决 cgroup 资源无法被彻底清理回收的问题,后续可能还会出现的新的 memory cgroup 泄露的触发场景。

内核里的解决方案

常规方案

在问题定位过程中,通过 Google 就已经发现了非常多的容器场景下 cgroup 泄漏导致的问题,在 centos7 系列,4.x 内核上都有报告的案例,主要是由于内核对 cgroup kernel memory accounting 特性支持的不完善,当 K8s(Kubernetes)/RunC 使用该特性时,就会存在 memory cgroup 泄露的的问题。

 

而主要的解决方法,不外乎以下的几种规避方案:

  1. 定时执行 drop cache

  2. 内核配置 nokmem 禁用 kmem accounting 功能

  3. K8s(Kubernetes) 禁用 KernelMemoryAccounting 功能

  4. docker/runc 禁用 KernelMemoryAccounting 功能


我们在考虑有没有更好的方案,能在内核层面“彻底”解决 cgroup 泄露的问题?

内核回收线程

通过对 memoy cgroup 泄露问题的深入分析,我们看到核心的问题是,systemd-logind 临时创建的 cgroup 目录虽然会被自动销毁,但由于文件读写产生的 cache 内存以及相关 slab 内存却没有被立刻回收,由于这些内存页的存在,cgroup 管理结构体的引用计数就无法清零,所以虽然 cgroup 挂载的目录被删除了,但相关的内核数据结构还保留在内核里。


根据对社区相关问题解决方案的跟踪分析,以及阿里 cloud linux 提供的思路,我们实现一个简单直接的方案:


在内核中运行一个内核线程,针对这些残留的 memory cgroup 单独做内存回收,将其持有的内存页释放到系统中,从而让这些残留的 memory cgroup 能够被系统正常回收。


这个内核线程具有以下特性:

  1. 只对残留的 memory cgroup 进行回收

  2. 此内核线程优先级设置为最低

  3. 每做一轮 memory cgroup 的回收就主动 cond_resched(),防止长时间占用 cpu


回收线程的核心流程如下:

功能验证

对合入内核回收线程的内核进行功能与性能测试,结果如下:

  • 在测试环境开启回收线程,系统残留的 memory cgroup 能够被及时的清理;

  • 模拟清理 40w 个泄漏的 memory cgroup,回收线程 cpu 使用率最高不超过 5%,资源占用可以接受;

  • 针对超大规格的残留 memory cgroup 进行测试,回收 1 个持有 20G 内存的 memory cgroup,核心回收函数的执行时间分布,基本不超过 64us;不会对其他服务造成影响; 

开启内核回收线程后,正常通过内核 LTP 稳定性测试,不会增加内核稳定性风险。


可以看到通过新增一个内核线程对残留的 memory cgroup 进行回收,以较小的资源使用率,能够有效解决 cgroup 泄露的问题,这个方案已经在网易私有云大量上线使用,有效提升了网易容器业务的稳定性。

总结

以上是我们分享的 memory cgroup 泄露问题的分析定位过程,给出了相关的解决方案,同时提供了内核层面解决该问题的一种思路。


在长期的业务实践中,深刻的体会到 K8s(Kubernetes)/容器场景对 Linux kernel 的使用和需求是全方位的。一方面,整个容器技术主要是基于 kernel 提供的能力构建的,提升 kernel 稳定性,针对相关模块的 bug 定位与修复能力必不可少;另一方面, kernel 针对容器场景的优化/新特性层出不穷。我们也持续关注相关技术的发展,比如使用 ebpf 优化容器网络,增强内核监控能力,使用 cgroup v2/PSI 提升容器的资源隔离及监控能力,这些也都是未来主要推动在网易内部落地的方向。


作者介绍:张亚斌,网易数帆内核专家

2022-01-05 17:048481

评论 5 条评论

发布
用户头像
您好,请问使用内核回收线程方案 需要重启机器吗
2024-05-06 19:49 · 江苏
回复
用户头像
您好,我们也遇到一样的场景,容器化部署的redis,如果遇到load突然飙升或者回收,会导致异常
2022-01-15 11:27
回复
这需要更详细的背景进行分析哈,建议可以适当调大memory limit留一些内存申请的缓冲,然后调高low水位,让系统提前回收部分内存,避免影响业务;
2022-01-19 14:13
回复
用户头像
难得的干货文章!👍
2022-01-10 15:44
回复
感谢支持~
2022-01-19 14:04
回复
没有更多了
发现更多内容

低代码平台适用于大中型企业吗?

力软低代码开发平台

细说JavaScript闭包

hellocoder2029

JavaScript

技术新风口:超级App

FinFish

数字化转型 数字化 技术趋势 Gartner预测

【C语言】continue 关键字

謓泽

一步步带你设计MySQL索引数据结构

程序知音

Java MySQL 数据库 编程 后端技术

SPL比SQL更难了还是更容易了?

石臻臻的杂货铺

sql SPL 11月月更

Wallys Routerboard DR40x9 IPQ4019 IPQ4029 ,802.11AC 2x2 2.4G&5G Support HTTPS Support all the modules of Quectel

Cindy-wallys

源码级深度理解 Java SPI

vivo互联网技术

Java Spring Boot dubbo spi

一个更快的YOLOv5问世,附送全面中文解析教程

OneFlow

人工智能 深度学习 训练数据

解决前端恶意代码侵入的一些思考

FinFish

小程序 安全 安全架构 小程序容器 前端安全

理解Nodejs中的进程间通信

coder2028

node.js

分层架构最容易范的最昂贵错误

风铃架构日知录

分层架构 #java 服务层 封装业务逻辑

详解webpack构建优化

Geek_02d948

webpack

阿里云张建锋:核心云产品全面 Serverless 化

阿里巴巴云原生

阿里云 Serverless 云原生

Web3开发者指南,比较好用的 NFT API 服务推荐!

NFT Research

区块链 数据分析 NFT

手写vue-router核心原理

hellocoder2029

JavaScript

从oracle到mysql模型转换的自动化实现

鲸品堂

语言 & 开发

Webpack中的plugin插件机制

Geek_02d948

webpack

细说nodejs的path模块

coder2028

node.js

什么是代理服务器?它有哪些分类?

wljslmz

服务器 网络技术 11月月更 代理服务器

AntDB数据库与DSG强强联手,助力通信行业核心系统国产化

亚信AntDB数据库

aisware antdb AntDB数据库

从软件工程角度看测试

老张

软件工程 质量保障

Wallys|industrial wifi6 router/ Qualcomm IPQ8072A 4T4R support QCN9074/QCN6024 MOUDLE OPENWRT 802.11AX 10GE port 10G SFP

Cindy-wallys

阿里P8面试官总结的《2022java技术总结》,解决90%以上的技术面

程序知音

Java 程序员 后端技术 Java面试题 Java面试八股文

用 nodejs 搭建脚手架

coder2028

node.js

U-App移动统计算力升级!支持跨应用、多事件的打包计算

分布式任务批处理技术选型与实践

苏格拉格拉

分布式 批处理 分布式任务 数据分片 任务调度

ABCNet:端到端的可训练框架的原理应用及优势对比

合合技术团队

人工智能 模型 端口 图片识别 文本识别

Koordinator 1.0 正式发布:业界首个生产可用、面向规模场景的开源混部系统

阿里巴巴云原生

阿里云 云原生 Koordinator

细说Js中的this

hellocoder2029

JavaScript

K8s 有损发布问题探究

阿里巴巴云原生

阿里云 Kubernetes 云原生

网易数帆内核团队:memory cgroup 泄漏问题的分析与解决_开源_张亚斌_InfoQ精选文章