k8s最佳实践:部分业务POD内存持续泄露问题

K8S部分业务POD内存持续泄露问题

1.前言

线上K8S集群有极少量的PHP业务,它们的POD内存持续走高直到OOM,相信与特殊代码场景有关,需要展开分析。

我从POD的内存监控原理入手,分析到底内存用到了哪些地方。

2.分析过程

  • 第一步:分析pod的内存限制原理

    • 容器化依赖Cgroup限制内存资源,Docker采集容器的内存使用量也是基于Cgroup技术
    • 实际上,Cgroup标准做法是把每个子系统作为一棵树(Hierarchy),然后在树里面创建子cgroup做 资源限制。
    • Centos默认创建了这样的N颗树,每棵树管理1个子系统,K8S就是在这些树中创建子目录来使用Cgroup能力。
  • 第二步:分析pod的内存限制是如何实现的(以内存memory为例,我们知道POD可以设置resource limit)

    • 首先docker ps找到目标pod的相关容器,至少有2个容器,一个是pause容器,一个是应用容器

    • 拿着应用容器的container id,执行docker inspect 可以看到label里有一个pod唯一标识uid

    • K8S创建了kubepods子cgroup,仍旧以memory为例

      • ll /sys/fs/cgroup/memory/kubepods/
        
    • K8S资源限制是POD级的,所以K8S还会在这个cgroup下创建POD的子memory cgroup,进行POD级具体的资源限制。

      • 所有POD的总内存限制为30.23G,宿主机是32G内存,其他1G多内存没有纳入cgroup是因为kubelet配置的预留内存导致的。
    • 根据上面找到的POD,就可以继续定位到POD级的cgroup了

    • 再往POD下面一级就是container的cgroup了,是继承了POD级的限制,反正POD级就那么多内存,里面的单个容器最多也就用这些

  • 第二步:分析pod的内存都使用到了哪些地方

    • 发现应用容器占了1.8G左右,快要把POD的内存限制用满了。(也可以通过docker stats命令查看到容器内存占用)

    • 拿着之前发现的pause容器ID,查看一下内存使用,只用了1M左右,因此pause容器的内存占用可以忽略。

    • 详细看应用容器的内存使用统计,会发现total_rss和total_cache加起来不过300MB+,其他内存跑哪里去了?

      • [root@10-42-53-112 ~]# cat /sys/fs/cgroup/memory/kubepods/pod931369e9-2a87-4090-a304-dd02122e7acc/7e75c3921b2157ccecc5cff5055940c782f02cb8227ae080874220bb06124dad/memory.stat 
        
    • 经过了解,cgroup的memory.usage_in_bytes除了计算rss和swap外,还统计了kmem,也就是内核使用内存,我们查看一下实际kmem使用量

      • [root@10-42-53-112 ~]# cat /sys/fs/cgroup/memory/kubepods/pod931369e9-2a87-4090-a304-dd02122e7acc/7e75c3921b2157ccecc5cff5055940c782f02cb8227ae080874220bb06124dad/memory.kmem.usage_in_bytes 
        1564602368
        
      • 果然1.5G左右,和rss加起来大概就是1.8G了,发现这个应用容器大部分内存都被kernel使用了

    • 经验告诉我,这些“看不到”的内存大概率是被 slab 使用了。slab allocator 是 Linux 内核的内存分配机制,是给内核对象分配内存的

    • 现在虽然知道内存是被 slab 所使用了,但是因为 slab 里面有各种不同的内核对象(object),还需要找到是哪些对象占用了内存,可以查看 /proc/slabinfo 文件,发现占用最多的是 dentry 对象:

    • 上述容器使用了790万的dentry,占了1.4G内存;宿主机执行slabtop可以看到整机分配了3000万的dentry,占了6G左右内存。

    • 我们只有个别的应用存在内存泄露情况,怀疑与代码特殊行为有关,尝试strace了一下php-fpm,看是否有大量文件操作导致dentry增加:竟然真的在不停的创建临时文件。

    • 其行为是先读取socket读进来16384字节的数据:然后才创建了1个临时文件开始写入后续数据,最后再把所有数据从临时文件里读进内存,才开始进入PHP脚本的处理逻辑。

    • 我高频抓取了一下/tmp目录,抓到1个临时文件看了一下内容:发现内容就是/comment/bgm_bulk_index接口的POST body体,怀疑PHP-FPM遇到太大的POST体会走临时文件。

    • FPM处理POST表单时,大概会通过php_stream_temp_create_ex创建用于存放解析结果的request_body buffer,第2个参数是内存阈值,一旦超过内存阈值就会写临时文件;

      然后循环解析数据写入这个Buffer,因为上述case的POST body总大小是百K,所以就超过了内存阈值,写了临时文件。

      这个SAPI_POST_BLOCK_SIZE内存阈值是16进制定义的,实际就是16384

3.解决方案

  • 最后,在高内存POD所在的node,进行一次slab dentry cache清理,观察POD内存是否下降:
  • POD内存从1.8G降到了346M,基本吻合了RSS实际占用,说明kmem部分被释放了。
  • 因此,PHP频繁的新建+删除文件,就会不停的分配新的dentry对象,旧的dentry会越来越多直到系统没有更多内存可用才会开始淘汰缓存。
  • 定时任务 drop cache,不过过几天就会反弹
  • 这个案例告诉我们,docker默认将kmem算作cgroup的内存占用是比较坑的,哪个cgroup创建出来的slab对象就会被算到谁的头上,多多少少有点不合理。
  • 所以,也许禁止docker将kmem统计在memory usage内,是不是一个更好的做法呢?网上有诸多讨论,就不赘述了。
  • 能不能关闭cgroup kmem counting来避免slab内存计入cgroup呢?是否有风险呢?

4.pod内存泄露的其他场景

1.场景1:nginx反向代理

该问题发生在nginx+php-fpm技术栈,但不限于此场景。

当访问某URL时,其匹配逻辑如下:

  • 匹配location /,通过if检查是否存在,如果存在就返回静态文件,否则rewrite到index.php重新匹配。
  • 匹配location .php,反向代理请求给PHP-FPM。

也就是说,每个URL都会去磁盘上读一次文件,无论文件是否存在。

这就意味着,有多少种URL,就有多少个slab dentry cache。

当遇到URL美化的场景就有问题了,比如:文章ID是URL的一部分,

/articles/detail/134543

/articles/detail/881929

这种URL的规模是无法估量的,经过nginx先查一次磁盘缓存到dentry,然后再转发给php-fpm进行处理,就必然导致千百万的dentry对象被缓存下来。

类似场景大家可以自行延伸,比如try_files指令也是先找磁盘文件,一样会坑。

场景2:web框架

这个case比较个性化,但也作为一种思路开拓提供给大家。

当我关闭了nginx反向代理先走文件的配置后,发现dentry仍旧在狂涨,因此我就进一步仔细看了一下php-fpm的strace日志。

发现php-fpm每次请求都会去web框架下的cache目录找一个md5样子的文件,难道web框架开启了cache特性?

function _display_cache(&$CFG, &$URI)
	{
		$cache_path = ($CFG->item('cache_path') == '') ? APPPATH.'cache/' : $CFG->item('cache_path');
 
		// Build the file path.  The file name is an MD5 hash of the full URI
		$uri =	$CFG->item('base_url').
				$CFG->item('index_page').
				$URI->uri_string;
 
		$filepath = $cache_path.md5($uri);
 
		if ( ! @file_exists($filepath))
		{
			return FALSE;
		}
}

翻了一下框架代码,发现这个框架实现的确有点问题,在没有开启cache特性的情况下仍旧会去cache目录尝试加载一下缓存文件:

因为文件名是URL的MD5,这就导致因为query string的不同而千变万化,即每次请求都将创建1个dentry cache。

posted @ 2022-05-16 15:31  西*风  阅读(2092)  评论(0编辑  收藏  举报