cgroup 内存泄露造成高内存使用率
配置
出现内存泄漏的主机为集群机器,运行时间约5天,内存使用超90%,其上运行 集群管理软件 和 docker并执行测试脚本反复启停容器。
现象
长时间运行后,集群主机内存占用逐渐增加,出现应用 OOM 现象。
而实际查看时发现主机内存总占用高,但应用实际占用内存低或未见显著异常。
# top
top - 09:52:50 up 19 days, 23 min, 1 user, load average: 0.13, 0.06, 0.06
Tasks: 114 total, 2 running, 112 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.4 us, 0.7 sy, 0.0 ni, 98.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 83.6/3881072 [||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ]
KiB Swap: 0.0/0 [ ]
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1671 root 20 0 150240 21556 5740 S 2.9 0.6 446:05.66 calico-node
24099 root 20 0 161968 2232 1560 R 1.5 0.1 0:00.02 top
1 root 20 0 193628 5344 2808 S 0.0 0.1 12:18.55 systemd
2 root 20 0 0 0 0 S 0.0 0.0 0:00.37 kthreadd
3 root 20 0 0 0 0 S 0.0 0.0 0:14.85 ksoftirqd/0
5 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H
7 root rt 0 0 0 0 S 0.0 0.0 0:30.62 migration/0
8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh
9 root 20 0 0 0 0 S 0.0 0.0 4:33.15 rcu_sched
10 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 lru-add-drain
11 root rt 0 0 0 0 S 0.0 0.0 0:08.13 watchdog/0
12 root rt 0 0 0 0 S 0.0 0.0 0:05.43 watchdog/1
13 root rt 0 0 0 0 S 0.0 0.0 0:26.43 migration/1
14 root 20 0 0 0 0 S 0.0 0.0 0:11.06 ksoftirqd/1
16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/1:0H
17 root rt 0 0 0 0 S 0.0 0.0 0:05.51 watchdog/2
18 root rt 0 0 0 0 S 0.0 0.0 0:26.38 migration/2
19 root 20 0 0 0 0 S 0.0 0.0 0:15.31 ksoftirqd/2
21 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/2:0H
22 root rt 0 0 0 0 S 0.0 0.0 0:05.11 watchdog/3
23 root rt 0 0 0 0 S 0.0 0.0 0:30.14 migration/3
24 root 20 0 0 0 0 S 0.0 0.0 0:10.12 ksoftirqd/3
26 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/3:0H
28 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs
29 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 netns
30 root 20 0 0 0 0 S 0.0 0.0 0:00.89 khungtaskd
31 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 writeback
32 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kintegrityd
33 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset
# free -h
total used free shared buff/cache available
Mem: 3.7G 2.6G 103M 190M 991M 622M
Swap: 0B 0B 0B
可以看到内存占用 83.6% ,而实际top显示的内存占用最高也才 0.6% 没有占用内存过高的应用。
内存占用除了用户应用占用还有内核占用,遂查看内核内存占用。
使用linux文件系统接口查看
# cat /proc/meminfo
MemTotal: 3881072 kB
MemFree: 119732 kB
MemAvailable: 629952 kB
Buffers: 0 kB
Cached: 414472 kB
SwapCached: 0 kB
Active: 465720 kB
Inactive: 168456 kB
Active(anon): 362720 kB
Inactive(anon): 60056 kB
Active(file): 103000 kB
Inactive(file): 108400 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 36 kB
Writeback: 0 kB
AnonPages: 219780 kB
Mapped: 28452 kB
Shmem: 203072 kB
Slab: 3035060 kB
SReclaimable: 587952 kB
SUnreclaim: 2447108 kB
KernelStack: 3424 kB
PageTables: 4292 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 1940536 kB
Committed_AS: 1370808 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 164620 kB
VmallocChunk: 34359326716 kB
HardwareCorrupted: 0 kB
AnonHugePages: 49152 kB
CmaTotal: 0 kB
CmaFree: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 98240 kB
DirectMap2M: 4096000 kB
可以看到占用超高的项目为 slab 内核占用:
Slab: 3035060 kB //slab 内存大小
SReclaimable: 587952 kB //slab 可回收内存大小
SUnreclaim: 2447108 kB //slab 不可回收内存大小
继续查看内核详细占用,按照缓存大小进行排序
# slabtop -s c -o
Active / Total Objects (% used) : 5564843 / 19168412 (29.0%)
Active / Total Slabs (% used) : 416410 / 416410 (100.0%)
Active / Total Caches (% used) : 71 / 97 (73.2%)
Active / Total Size (% used) : 841857.29K / 3022388.82K (27.9%)
Minimum / Average / Maximum Object : 0.01K / 0.16K / 8.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
211776 4758 2% 2.00K 13236 16 423552K kmalloc-2048
101960 10026 9% 4.00K 12745 8 407840K kmalloc-4096
3289262 3287614 99% 0.12K 96743 34 386972K kernfs_node_cache
487704 133512 27% 0.66K 20321 24 325136K ovl_inode
285712 39486 13% 1.00K 17857 16 285712K kmalloc-1024
762048 485348 63% 0.19K 36288 21 145152K kmalloc-192
270064 24116 8% 0.50K 16879 16 135032K kmalloc-512
701778 74628 10% 0.19K 33418 21 133672K dentry
1676736 55595 3% 0.06K 26199 64 104796K kmalloc-64
1098678 412338 37% 0.09K 26159 42 104636K kmalloc-96
157650 750 0% 0.62K 6306 25 100896K sock_inode_cache
2868480 132994 4% 0.03K 22410 128 89640K kmalloc-32
325280 88966 27% 0.25K 20330 16 81320K kmalloc-256
1946466 28141 1% 0.04K 19083 102 76332K selinux_inode_security
520096 187567 36% 0.12K 16253 32 65012K kmalloc-128
79338 965 1% 0.38K 3778 21 30224K mnt_cache
1699584 68608 4% 0.02K 6639 256 26556K kmalloc-16
101904 101904 100% 0.25K 6369 16 25476K kmem_cache
510255 2805 0% 0.05K 6003 85 24012K shared_policy_node
1725952 97281 5% 0.01K 3371 512 13484K kmalloc-8
17766 17275 97% 0.58K 658 27 10528K inode_cache
12992 6106 46% 0.57K 464 28 7424K radix_tree_node
102016 102016 100% 0.06K 1594 64 6376K kmem_cache_node
45942 35707 77% 0.10K 1178 39 4712K buffer_head
4386 4284 97% 0.94K 258 17 4128K xfs_inode
137190 137190 100% 0.02K 807 170 3228K fsnotify_mark_connector
1125 395 35% 2.06K 75 15 2400K idr_layer_cache
184 60 32% 8.00K 46 4 1472K kmalloc-8192
2040 1824 89% 0.64K 85 24 1360K proc_inode_cache
288 252 87% 3.95K 36 8 1152K task_struct
1512 1367 90% 0.66K 63 24 1008K shmem_inode_cache
3366 3334 99% 0.21K 187 18 748K vm_area_struct
270 244 90% 2.06K 18 15 576K sighand_cache
420 392 93% 1.12K 15 28 480K signal_cache
5768 5768 100% 0.07K 103 56 412K Acpi-ParseExt
2376 1280 53% 0.16K 99 24 396K xfs_ili
160 160 100% 1.56K 8 20 256K mm_struct
3060 3060 100% 0.08K 60 51 240K anon_vma
532 532 100% 0.41K 28 19 224K xfs_efd_item
180 180 100% 1.06K 6 30 192K UDP
156 156 100% 1.19K 6 26 192K UDPv6
378 349 92% 0.44K 21 18 168K ip6_dst_cache
450 450 100% 0.31K 18 25 144K bio-2
24 24 100% 5.12K 4 6 128K net_namespace
52 52 100% 2.37K 4 13 128K blkdev_queue
64 64 100% 1.94K 4 16 128K TCP
56 56 100% 2.15K 4 14 128K pid_namespace
60 60 100% 2.12K 4 15 128K TCPv6
可以看到此处:
kmalloc-2048,kmalloc-4096,kernfs_node_cache,kmalloc-1024,kmalloc-192,kmalloc-512 均占用较高,对比了正常主机,已经严重超过正常值。
如果是内核缓存过高则可以尝试进行内核缓存释放:
# echo 3 > /proc/sys/vm/drop_caches
1
但执行上述操作后,内存占用依旧无显著下降,也符合上面看到的 SUnreclaim: 2447108 kB //slab 不可回收内存大小
。这部分内存不能被释放。
kmalloc 为内核进行分配的内存,参考价值较大的为 kernfs_node_cache
占用高,遂搜索该项是作何作用。
很明显,该现象为内核占用严重超标,于是在搜索时加入了 memory leak
关键字,很快发现了该 Issues docker-run --memory slab cache leak on centos7
该 issue 表示 centos7 在反复运行 docker run --rm --memory 1g hello-world
时存在明显的内核内存占用升高,且无法被释放。且现象和当前现象一致。
最终指向内kernel c group内存泄露问题 slab leak causing a crash when using kmem control group
大致原因是在 3.10 内核上如果使用了 kmem limit 参数,会导致cgroup回收时无法释放部分已分配内存。至于更深入的了解,还需要其他时间,先解决目前的问题。
复现
原因大概确定,为了再次确定这个问题,如果能够通过上述手段复则可以确定是该问题。
while true;do docker run --rm --memory 1g hello-world; done
在一台仅运行docker的机器上执行上述语句,查看 slab 内存占用,可以看见内存占用明显上升。且最终表现和已有环境上的问题一致,总内存占用高,用户态内存占用低,内核内存占用高且无法被释放。
解决
既然是内核问题,且知道了明确复现路径,则可以通过两种方式进行解决:
- 对现有3.10内核加入补丁,重新编译。
- 更换没有该问题的内核版本。
最终,进过测试后,选择了更换内核版本,将使用 Ubuntu 18.04 作为新的操作系统。
参考
Linux内核使用层次化内存管理的方法,每一层解决不同的问题,从下至上的关键部分如下:
- 物理内存管理,主要用于描述内存的布局和属性,主要有Node、Zone和Page三个结构,使内存按照Page为单位来进行管理;
- Buddy内存管理,主要解决外部碎片问题,使用get_free_pages等函数以Page的N次方为单位进行申请释放;
- Slab内存管理,主要解决内部碎片问题,可以按照使用者指定的大小批量申请内存(需要先创建对象缓存池);
- 内核缓存对象,使用Slab预先分配一些固定大小的缓存,使用kmalloc、vmalloc等函数以字节为单位进行内存申请释放。
slab是Linux操作系统的一种内存分配机制。其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。
Slab导致的占用内存过高,Slab可以对可回收缓存手动释放,操作如下:
# echo 3 > /proc/sys/vm/drop_caches
1
其中drop_caches的4个值有如下含义:
- 0:不做任何处理,由系统自己管理
- 1:清空pagecache
- 2:清空dentries和inodes
- 3:清空pagecache、dentries和inodes