一次关于k8s kubectl top 和 contained ps 不一致的问题探究

2021-06-29 00:00:00 缓存 内存 容器 进程 占用

k8s kubectl top命令和contained内部 ps 看到的进程内存占用不一致。下午的时候,我被这个问题问倒了。具体如图

kubectltop-vmtop-vm

网上搜索了下,难得看到有认真研判问题的IT文章了。这篇帖子推荐给大家。


  • 一、问题背景

  • 二、Buffer & cache原理

  • 三、缓存测试

  • 四、生产环境内存飙升解决方案的建议


目录

时间线:

  • 在上 Kubernetes 的前半年,只是用 Kubernetes,开发没有权限,业务服务极少,忙着写新业务,风平浪静。
  • 在上 Kubernetes 的后半年,业务服务较少,偶尔会阶段性被运维唤醒,问之 “为什么你们的服务内存占用这么高,赶紧查”。此时大家还在为新业务冲刺,猜测也许是业务代码问题,但没有调整代码去尝试解决。
  • 再后面,出过几次OOM的问题,普遍增加了容器限额 Limits,出现了好几个业务服务是内存小怪兽,因此如果不限制的话,服务过度占用会导致驱逐,因此反馈语也就变成了:“为什么你们的服务内存占用这么高,老被 OOM Kill,赶紧查”。

一、问题背景

kubernetes 运行的java应用,内存占用持续在增长。思路如下:

kubectl exec -it pod -n xxx /bin/bash

执行 top 命令查看下当前 pod 正在运行的进程,发现在容器里面有一个 7 号进程 VSZ 占用 6522m

应用内存占用 17 G之多,那很显然,并不是这个进程在捣鬼,但整个容器里面确实就只有这个进程在运行着,并且该 Java 进程还设置了分配内存的限制,大不会超过 4g,可是内存还是一直在涨。

容器内部ps

而且容器里面执行 top 看到的信息很少,我们对比下实际操作系统的 top 命令执行结果多了很多列,例如RES、 %MEM 等等。

top命令

小TIPS:

RSSVSZ指标相关的参数含义:

  • RSS是Resident Set Size(常驻内存大小)的缩写,用于表示进程使用了多少内存(RAM中的物理内存),RSS不包含已经被换出的内存。RSS包含了它所链接的动态库并且被加载到物理内存中的内存。RSS还包含栈内存和堆内存。
  • VSZ是Virtual Memory Size(虚拟内存大小)的缩写。它包含了进程所能访问的所有内存,包含了被换出的内存,被分配但是还没有被使用的内存,以及动态库中的内存。

使用top 需注意:

  • 虚拟内存通常并不会全部分配给物理内存;

  • 共享内存 SHR 并不一定是共享,例如程序的代码段、非共享的动态链接库,也都算在 SHR 里。其中,SHR 也包括了进程间真正共享的内存。

    因此,计算多个进程的内存使用时,不建议把所有进程的 SHR 直接相加得出结果。

所以只从 top 看是不准确的,/proc/pid/status会更精准显示进程内存占用:

cat /proc/7/status

查看当前 pid 的状态,其中有一个字段VmRSS 表示当前进程所使用的内存,然而我们发现用户当前进程所占用的内存才2.3G 左右。

proc实际内存显示

但事实是 kubectl top pod` 查看 pod 的内存占用 确实发现该 pod 内存占用确实高达 17 G ,推断并不是容器内进程内存泄露导致的问题,那这就奇怪了,是什么原因导致占用这么多内存呢?

二、Buffer & cache原理

要继续排查这个问题,我们就需要先看看容器的内存统计是如何计算的了。

众所周知,操作系统系统的内存设计,有二级,三级物理缓存。为加快运算速度,缓存被大量应用,常用数据会被buffercache之类占用,在 Linux 操作系统中会把这部分内存算到已使用。

对于容器来讲,也会把某容器引发的cache占用算到容器占用的内存上,要验证这个问题,我们可以启动一个容器, dd 创建一个大文件观察下内存变化。

[root@8e3715641c31 /]# dd if=/dev/zero of=my_new_file count=1024000 bs=3024

1024000+0 records in
1024000+0 records out
3096576000 bytes (3.1 GB, 2.9 GiB) copied, 28.7933 s, 108 MB/s

你会发现,系统的 buff/cache 这一列会不断的增大。

[root@8e3715641c31 /]# free -h
              total        used        free      shared  buff/cache   available
Mem:          3.7Gi       281Mi       347Mi       193Mi       3.1Gi       3.0Gi
Swap:            0B          0B          0B

三、缓存测试

回归上述问题,会不会是 Java 程序在不停的往磁盘写文件,导致 cache 不断的增大呢?

kubectl logs -f pod-name -n namespace-name 

查看,发现整屏幕不断的输出 debug 日志。然后回到开头的图,我们会发现cache 空间高达 20g 左右。

topinfo

我们尝试把 cache 清掉下看看内存是否会下降,通过配置 drop_caches强行清楚系统缓存。

sync; echo 3 > /proc/sys/vm/drop_caches
sync; echo 1 > /proc/sys/vm/drop_caches
sync; echo 2 > /proc/sys/vm/drop_caches  

当执行完如上命令后,该 pod 的内存瞬间变小,同时磁盘 I/O 持续飙升,印证是 cache 问题导致的。告知开发把日志级别从  debug 改成 info,内存问题得到解决。

TIPS

/proc/sys是虚拟文件系统,是与kernel实体间进行通信的桥梁。通过修改/proc中的文件,来对当前kernel的行为做出调整。通过调整/proc/sys/vm/drop_caches来释放内存。其默认数值为0。

  • 1

    表示仅清除页面缓存(PageCache):

  • 2

    表示清除目录项和inode

  • 3

    //表示清空所有缓存(pagecache、dentries 和 inodes

四、生产环境内存飙升解决方案的建议

  • 建议1

合理的规划资源,对每个 Pod 配置Limit,限制资源使用。kubernetes 提供了针对 pod 级别的资源限制功能,但默认没有 CPU 和内存的限额。这意味着系统中的任何 Pod 将能够像执行该 Pod 所在的节点一样,消耗足够多的 CPU 和内存。这容易导致node节点的资源被无限占用。k8s官方也并不推荐该方式。

建议方案:通过 limits 来限制 Pod 的内存和 CPU ,这样一来一旦内存达到使用限制,pod 会自动重启,而不会影响到其他 pod。

resources:
      requests:
        cpu"200m"
        memory"128Mi"
      limits:
        cpu"500m"
        memory"512Mi"
  • 建议2

针对应用本身也需要加上资源使用限制,例如 Java 程序可以限制堆内存和非堆内存的使用:

堆内存分配:

  • JVM 大分配的内存由**-Xmx** 指定,默认是物理内存的 1/4;

  • JVM 初始分配的内存由**-Xms** 指定,默认是物理内存的 1/64;

  • 默认空余堆内存小于 40% 时,JVM 就会增大堆直到-Xmx 的大限制;空余堆内存大于 70% 时,JVM 会减少堆直到 -Xms 的小限制;

    因此,服务器的推荐设置是:-Xms、-Xmx 相等以避免在每次 GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

非堆内存分配:

  • 由 XX:MaxPermSize 设置大非堆内存的大小,默认是物理内存的 1/4;
  • JVM 使用**-XX:PermSize** 设置非堆内存初始值,默认是物理内存的 1/64;
  • -Xmn2G:设置年轻代大小为 2G;
  • -XX:SurvivorRatio,设置年轻代中 Eden 区与 Survivor 区的比值。
  • 建议3

应用本身优化,如本案例中所类似的问题要尽量避免在生产环境发生,即在生产环境开 debug 模式。即有安全风险,又会因为频繁的写日志会把 cache 打的非常高。建议将日志收集到专业的日志管理工具中,例如 ELK或SLS

  • 建议4

监控非常重要,针对应用本身的资源使用情况和系统的各项监控指标要完善,便于及时发现问题。

参考:https://blog.csdn.net/yucaifu1989/article/details/108084086

来自:https://mp.weixin.qq.com/s/5-TLnSYAsv2X_9O62BC8DA

相关文章