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:
RSS
、VSZ
指标相关的参数含义:
- 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原理
要继续排查这个问题,我们就需要先看看容器的内存统计是如何计算的了。
众所周知,操作系统系统的内存设计,有二级,三级物理缓存。为加快运算速度,缓存被大量应用,常用数据会被buffer
、cache
之类占用,在 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
三、缓存测试
回归上述问题,QQ靓号出售地图会不会是 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
监控非常重要,针对应用本身的资源使用情况和系统的各项监控指标要完善,便于及时发现问题。