最近我在回顾思考(写 PPT),整理了现状,发现了这个问题存在多时,经过一番波折,最终确定了元凶和相对可行的解决方案,因此分享一下排查历程,希望能够给大家一些借鉴的经验。

时间线:

  • 在上 Kubernetes 的前半年,只是用 Kubernetes,开发没有权限,业务服务极少,忙着写新业务,风平浪静。

  • 在上 Kubernetes 的后半年,业务服务较少,偶尔会阶段性被运维唤醒,问之 “为什么你们的服务内存占用这么高,赶紧查”。此时大家还在为新业务冲刺,猜测也许是业务代码问题,但没有调整代码去尝试解决。

  • 在上 Kubernetes 的第二年,业务服务逐渐增多,普遍增加了容器限额 Limits,出现了好几个业务服务是内存小怪兽,因此如果不限制的话,服务过度占用会导致驱逐,因此反馈语也就变成了:“为什么你们的服务内存占用这么高,老被 OOM Kill,赶紧查”。据闻也有几个业务大佬有去排查(因为 OOM 反馈),似乎没得出最终解决方案。

不禁让我们思考,为什么个别 Go 业务服务,Memory 总是提示这么高,经常达到容器限额,以至于被动 OOM Kill,是不是有什么安全隐患?

现象

内存居高不下

发现个别业务服务内存占用挺高,触发告警,且通过 Grafana 发现在凌晨(没有什么流量)的情况下,内存占用量依然拉平,没有打算下降的样子,高峰更是不得了,像是个内存炸弹:

image

并且我所观测的这个服务,早年还只是 100MB。现在随着业务迭代和上升,目前已经稳步 4GB,容器限额 Limits 纷纷给它开道,但我想总不能是无休止的增加资源吧,这是一个大问题。

进入重启怪圈

有的业务服务,业务量小,自然也就没有调整容器限额,因此得不到内存资源,又超过额度,就会进入疯狂的重启怪圈:

image

重启将近 300 次,非常不正常了,更不用提所接受到的告警通知。

排查

猜想一:频繁申请重复对象

出现问题的个别业务服务都有几个特点,那就是基本为图片处理类的功能,例如:图片解压缩、批量生成二维码、PDF 生成等。

因此就怀疑是否在量大时频繁申请重复对象,而 Go 本身又没有及时释放内存,因此导致持续占用。

sync.Pool

基本上想解决 “频繁申请重复对象”,我们大多会采用多级内存池的方式,也可以用最常见的 sync.Pool,这里可参考全成所借述的《Go 夜读》上关于 sync.Pool 的分享,关于这类情况的场景:

当多个 goroutine 都需要创建同⼀个对象的时候,如果 goroutine 数过多,导致对象的创建数⽬剧增,进⽽导致 GC 压⼒增大。

形成 “并发⼤-占⽤内存⼤-GC 缓慢-处理并发能⼒降低-并发更⼤”这样的恶性循环。

验证场景

在描述中关注到几个关键字,分别是并发大,Goroutine 数过多,GC 压力增大,GC 缓慢。也就是需要满足上述几个硬性条件,才可以认为是符合猜想的。

通过拉取 PProf goroutine,可得知 Goroutine 数并不高:

image

另外在凌晨长达 6 小时,没有什么流量的情况下,也不符合并发大,Goroutine 数过多的情况,若要更进一步确认,可通过 Grafana 落实其量的高低。

从结论上来讲,我认为与其没有特别直接的关系,但猜想其所对应的业务功能到导致的间接关系应当存在(出问题的业务服务都是类似的功能)。

猜想二:不知名内存泄露

内存居高不下,其中一个反应就是猜测是否存在泄露,而我们的容器中目前只跑着一个 Go 进程,因此首要看看该 Go 应用是否有问题。这时候可以借助 PProf heap(可以使用 base -diff):

image

显然其提示的内存使用不高,那会不会是 PProf 出现了 BUG 呢。接下通过命令也可确定 Go 进程的 RSS 并不高。

但 VSZ 却相对 “高” 的惊人,我在 19 年针对此写过一篇《Go 应用内存占用太多,让排查?(VSZ篇)》 ,这次 VSZ 过高也给我留下了一个念想。

从结论上来讲,也不像 Go 进程内存泄露的问题,因此也将其排除。

猜想三:madvise 策略变更

  • 在 Go1.12 以前,Go Runtime 在 Linux 上使用的是 MADV_DONTNEED 策略,可以让 RSS 下降的比较快,就是效率差点。

  • 在 Go1.12 及以后,Go Runtime 专门针对其进行了优化,使用了更为高效的 MADV_FREE 策略。但这样子所带来的副作用就是 RSS 不会立刻下降,要等到系统有内存压力了才会释放占用,RSS 才会下降。

查看容器的 Linux 内核版本:

$ uname -a
Linux xxx-xxx-99bd5776f-k9t8z 3.10.0-693.2.2.el7.x86_64

但 MADV_FREE 的策略改变,需要 Linux 内核在 4.5 及以上(详细可见 go/issues/23687),显然不符合,因此也将其从猜测中排除。

猜想四:监控/判别条件有问题

会不会是 Grafana 的图表错了,Kubernetes OOM Kill 的判别标准也错了呢,显然不大可能,毕竟我们拥抱云,阿里云 Kubernetes 也运行了好几年。

image

但在这次怀疑中,我了解到 OOM 的判断标准是 container_memory_working_set_bytes 指标,因此有了下一步猜想。

猜想五:容器环境的机制

既然不是业务代码影响,也不是 Go Runtime 的直接影响,那是否与环境本身有关呢,我们可以得知容器 OOM 的判别标准是 container_memory_working_set_bytes(当前工作集)。

而 container_memory_working_set_bytes  是由 cadvisor 提供的,对应下述指标:

image

从结论上来讲,Memory 换算过来是 4GB+,石锤。接下来的问题就是 Memory 是怎么计算出来的呢,显然和 RSS 不对标。

原因

从 cadvisor/issues/638 可得知 container_memory_working_set_bytes 指标的组成实际上是 RSS + Cache。而 Cache 高的情况,常见于进程有大量文件 IO,占用 Cache 可能就会比较高,猜测也与 Go 版本、Linux 内核版本的 Cache 释放、回收方式有较大关系。

image

而各业务模块常见功能,如:

  • 批量图片解压缩。

  • 批量二维码生成。

  • 批量上传渲染后图片。

  • 批量 PDF 生成。

  • ...

只要是涉及有大量文件 IO 的服务,基本上是这个问题的老常客了,这类服务基本写一个中一个。

显然这是一个混合问题,像其它单纯操作为主的业务服务就很 “正常”,不会出现内存居高不下。

解决方案

在本场景中 cadvisor 所提供的判别标准 container_memory_working_set_bytes 是不可变更的,也就是无法把判别标准改为 RSS,因此我们只能考虑掌握主动权。

首先是做好做多级内存池管理,可以缓解这个问题的症状。但这存在难度,从另外一个角度来看,你怎么知道什么时候在哪个集群上会突然出现这类型的服务,何况开发人员的预期情况参差不齐,写多级内存池写出 BUG 也是有可能的。

让业务服务无限重启,也是不现实的,被动重启,没有控制,且告警,存在风险

因此为了掌握主动权,可以在部署环境可以配合脚本做 “手动” HPA,当容器内存指标超过约定限制后,起一个新的容器替换,再将原先的容器给释放掉,就可以在预期内替换且业务稳定了。

image

总结

虽然这问题时间跨度比较长,整体来讲都是阶段性排查,本质上可以说是对 Kubernetes 的不熟悉有关。但综合来讲这个问题涉及范围比较大,因为内存居高不下的可能性有很多种,要一个个排查,开发权限有限,费时费力。

基本排查思路就是:

  1. 怀疑业务代码(PProf)。

  2. 怀疑其它代码(PProf)。

  3. 怀疑 Go Runtime 。

  4. 怀疑工具。

  5. 怀疑环境。

非常感谢在这大段时间内被我咨询的各位大佬们,感觉就是隔了一层纱,捅穿了就很快就定位到了。

大家如果有其它解决方案也欢迎随时沟通。

参考阅读:

  • 类型化消息的一种设计模式

  • Go 新版泛型使用:80余行代码构建一个哈希表

  • 哔哩哔哩「会员购」在流量回放上的探索

  • 为什么我放弃使用 Kotlin 中的协程?

  • 基准测试表明, Async Python 远不如同步方式

本文授权转载自公众号脑子进煎鱼了(id: eddycjy ) ,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建方式


长按二维码 关注「高可用架构」公众号

一次K8S容器内存占用居高不下的排查案例相关推荐

  1. 为什么容器内存占用居高不下,频频 OOM(续)

    在上周的文章<为什么容器内存占用居高不下,频频 OOM> 中,我根据现状进行了分析和说明,收到了很多读者的建议和疑惑,因此有了这一篇文章,包含更进一步的说明和排查. 疑问 在出现系统内存过 ...

  2. 为什么容器内存占用居高不下,频频 OOM

    最近我在回顾思考(写 PPT),整理了现状,发现了这个问题存在多时,经过一番波折,最终确定了元凶和相对可行的解决方案,因此分享一下排查历程,希望能够给大家一些借鉴的经验. 时间线: 在上 Kubern ...

  3. docker mysql 内存大小_docker容器内存占用过高(例如mysql)

    简介 该文章适用于配置低,特别是内存低的服务器,在用容器部署服务时有可能会因为容器占用内存过高导致服务挂掉时参考解决(不是运行在容器里的话,同理也可以修改mysql的配置文件限制内存占用) docke ...

  4. win2003系统+IIS6下,经常出现w3wp.exe和sqlserver.exe的内存占用居高不下

    在IIS6下,经常出现w3wp.exe,sqlserver.exe的内存占用不能及时释放,从而导致服务器响应速度很慢. 由于内存释放不及时严重影响到服务器的正常运营,建议采用以下配置,但请考虑自身服务 ...

  5. Linux系统内存占用过高排查方法

    以下以Ubuntu系统为例,内存占用过高可能是因为某个进程或程序占用了过多的内存,您可以按照以下步骤进行排查: 以上是一些基本的排查步骤,它们可以帮助您确定内存占用过高的原因,并采取相应的措施解决问题 ...

  6. java进程CPU占用高如何排查-案例二

    近期项目新版本上线遇到cpu冲高现象,依据之前的经验,把这次排查过程记录下. 这次排查参考了之前记录的经验,还是很有用的:java进程cpu占用高如何排查_停5s的博客-CSDN博客_java进程cp ...

  7. Docker容器内存占用过高解决方法

    #查看占用过高的应用 docker stats  --动态实时显示 docker stats --no-stream  --静态显示  #修改compose file(版本小于v3) mem_limi ...

  8. httpd内存占用居高不下的问题

    原文:http://www.kangweixi.cn/2014/08/wdcp-apache-vps-optimization.html

  9. Linux内存占用过高排查过程

    一朋友找我说,他们一个项目的服务器内存占用率太高了,让我帮忙看下怎么回事.我正好不是太忙就帮他看了下,并记录如下. 1 查看服务器状态 他的服务器是阿里云的ECS,系统是 CentOS Linux r ...

最新文章

  1. 无需Get更多技能,快速打造一个可持久化的任务调度
  2. 边框回归的损失函数_一文搞懂常用的七种损失函数
  3. 经典C语言程序100例之六六
  4. k8s 基础介绍及概念
  5. zabbix主动、被动检测的详细过程与区别
  6. 树和二叉树【数据结构】
  7. Java高并发编程详解系列-线程通信
  8. QML学习【一】Basic Types
  9. 新疆计算机证相关信息技术,2019新疆中小学教师计算机考试资料:信息技术课程基本理念...
  10. 12_电话拨号器_界面实现
  11. 基于STM32单片机设计指纹考勤机+上位机管理
  12. 构造c语言的上下文无关文法,正则文法和上下文无关文法
  13. 《论文排版札记》part1 论文公式编号—WPS版
  14. 概率论基础 - 6 - 切比雪夫不等式
  15. 执行throw后 后面代码还会执行吗?
  16. 如何理解接口隔离原则?
  17. 2018.6.25课堂笔记
  18. GNSS 导航电文的解读
  19. POJ-2251 Dungeon Master
  20. activiti中的bug列表

热门文章

  1. Android应用开发进阶,一线互联网企业高级Android工程师面试题大全
  2. IDEA——教你mybatis的使用(小白教程)
  3. vue+element实现甘特图
  4. 微信公众号授权第三方PHP踩坑(一)
  5. C++【对象模型】| 【06】类中各种函数的刨析
  6. 一样降价出售,而是干脆地从苹果的产品线中去除掉
  7. Vue使用antV G2简单实例
  8. Fullcalendar日历使用,包括视图选择、事件插入、编辑事件、事件状态更改、事件添加和删除、事件拖动调整,自定义头部,加入el-popover显示图片、图片预览、添加附件链接等,支持手机显示。
  9. CF 807 E. Mark and Professor Koro(权值线段树)
  10. keil和proteus联调