vlambda博客
学习文章列表

K8S内存消耗,到底该看哪个图?

最近的一项工作,是查看服务在过去一段时间的内存实际使用量,给K8S平台上的POD内存设置一个基于历史数据的合理上限,既不会限制服务的正常运行,也可以尽量减少不必要的占坑。


本来是一个很简单的工作,按理说看看图,确定下最高峰的内存消耗,也就结束了。谁知这个看图的过程中看出些奇妙,事后竟花了2天的时间看了十几篇文章来研究,刚给米国的同事写了一封能翻好几页的邮件来讲这个问题,自己也总结一下。


先说奇妙。结合多个线上的监控图,我发现,使用不同的监控指标,看出来的内存使用情况差距很大。


如果用RSS作为指标,内存一直很稳定:


但是用WORKING SET作为指标,我们的内存好像一直在狂涨,而且分分钟要涨到目前的POD上限...

K8S内存消耗,到底该看哪个图?


那么问题就来了,我们到底应该看哪个指标,来确定POD内存的使用上限呢?


故事一开始,还得从Linux讲起。Linux支持给不同的进程划分Cgroup,也就是拉小群,一个群里的进程共享本群的资源,包括内存CPU等等,Docker底层就是用了Cgroup来达到容器的资源控制。


划分了Cgroup来给不同的进程做资源隔离之后,Linux本身就提供了很多指标,来展示Cgroup内的内存使用情况,这里我们比较关心的值有:

 
   
   
 
$ cat /sys/fs/cgroup/memory/memory.stat cache xxx rss xxxinactive_file xxxactive_file xxx


上面四个值中:

cache自然指的是缓存,包括文件缓存

rss指的是常驻内存,是分配给进程使用的实际物理内存,包括进程使用的栈内存,堆内存,以及共享库的内存

inactive_file和active_file,按照我的理解都是文件缓存,两者的区别是,一个文件第一次被访问,会算做inactive file, 被访问了两次之后,就会从inactive file的小队,归到active file的小队。


到了K8S这边,为了用户监控POD的内存消耗,K8S层面也暴露了很多不同的内存指标,我们这里比较关心的是:

container_memory_cache -- 缓存占用的大小container_memory_rss -- RSS占用的大小container_memory_usage_bytes -- 当前使用内存,包括所有内存,不管有没有被访问container_memory_working_set_bytes -- 当前内存工作集使用量


从K8S的源码可以看出,K8S的指标,实际上就是对上面Linux的指标做了一些计算之后得出的:


RSS的计算方式很直观,就是读取了total rss:

ret.Memory.RSS = s.MemoryStats.Stats["total_rss"]


WORKING SET的计算方式则是K8S自创的,用的是usage减去inactive file:

inactiveFileKeyName := "total_inactive_file"if cgroups.IsCgroup2UnifiedMode() { inactiveFileKeyName = "inactive_file"}workingSet := ret.Memory.Usageif v, ok := s.MemoryStats.Stats[inactiveFileKeyName]; ok {if workingSet < v { workingSet = 0 } else { workingSet -= v }}ret.Memory.WorkingSet = workingSet


从上面可以看出,我们关心的两个指标的来源是不同的:RSS只关心进程实际使用的内存,但是WORKING SET还把active file也算进去了,也就是说,文件缓存这部分也算进去了。


那下一个问题就是,K8S OOM的标准到底是什么?按理说,文件缓存既然是缓存,到了危机时刻,内存压力山大的时候,文件缓存都该让位给进程,这部分缓存应该都是可以写回磁盘,腾出地方来给进程使用的。


但是实际情况是,当缓存占用的空间巨大时,K8S会认为内存达到上限直接杀掉POD重新调度。这个问题在Github上提了个Issue,总有人发现自己POD的RSS很低,Cache很大的时候,也被K8S杀掉了:

K8S内存消耗,到底该看哪个图?


这个Issue从2017年被人发现,直到四天前还有人在回,可见也是一个老毛病了...但至少可以得出结论,缓存所占的内存也是会导致OOM的。


在浏览了各种Issue之后我发现,K8S考虑缓存也许不是没有理由的。在另一个Issue中,有人也遇到类似的坑,最后发现,不是所有的底层文件系统都能支持Dirty文件缓存写回的。那要是要的时候缓存不肯让座儿,进程本身又要更多内存,那可不就,全玩儿完...



综上,虽然文件缓存确实应该不算在进程使用的内存中,但是在K8S上,很显然它的存在对于POD的生死是有决定性的影响的。综上,我认为监测WORKING SET是更符合实际情况的,我们这里的内存上涨也是需要被关注的。


就是这样。


疫情期间上班抢菜已经费尽所有脑力,但还是想好好搞清楚一个问题,也许这就是死理性派的小执着吧...


祝你五一快乐!


Schönes Wochenende!