momo's Blog.

记录一次系统内存持续增高排查

字数统计: 2.2k阅读时长: 11 min
2022/02/25 Share

前言

线上发现一些机器的内存一直在持续的增长, 刚开始怀疑是内存泄漏.

不过我们是通过Dcoker启动的进程,并且容器使用完毕以后就会被销毁, 按理说是不会出现这种情况的。

排查

计算当前所有进程使用的总内存

1
2
3
4
5
6
~# ps aux | awk '{sum+=$6} END {print sum / 1024 / 1024}'
11.7488
~# free -hg
total used free shared buff/cache available
Mem: 62Gi 34Gi 2.5Gi 4.0Mi 25Gi 28Gi
Swap: 0B 0B 0B 0 0 0

可以看到, 当前进程加起来一共使用了11G,但是free命令过滤出来 used 使用了34G.

这岂不是也太奇怪了.

首先, 我们先复习一下free是如何计算的。

  • used Used memory (calculated as total - free - buffers - cache)
  • buffers Memory used by kernel buffers (Buffers in /proc/meminfo)
  • cache Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)
  • buff/cache Sum of buffers and cache

很清晰的逻辑, 使用的内存 = 总内存 - free - buffers - cache

所以上面就是: 62 - 2.5 - 25 = 34.5

这样的算法,直接求出了除了系统可支配的内存, 节点使用的内存。

slab 内存分析

但是有一个问题就是, 34G的内存,究竟是被那些占用了????

在Google的时候, 我看很多都是教你清除cache。

1
echo 2 > /proc/sys/vm/drop_caches

但是系统默认有一个 vfs_cache_pressure的内核参数
参考连接在下方编号: 5

默认是100, 调小的话系统将会尽可能保留cache,如果调0则一直保留。同理,如果调大则会加快回收速度。

如果走默认值,则会让系统自动的处理。原话是这样的。

1
2
3
At the default value of vfs_cache_pressure=100 the kernel will attempt to
reclaim dentries and inodes at a "fair" rate with respect to pagecache and
swapcache reclaim.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# cat /proc/meminfo
MemTotal: 65967212 kB
MemFree: 2035892 kB
MemAvailable: 28986076 kB
Buffers: 1107588 kB
Cached: 9034516 kB
SwapCached: 0 kB
Active: 13007892 kB
Inactive: 5995480 kB
Active(anon): 8863044 kB
Inactive(anon): 3172 kB
Active(file): 4144848 kB
Inactive(file): 5992308 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 568 kB
Writeback: 0 kB
AnonPages: 8657824 kB
Mapped: 281220 kB
Shmem: 4952 kB
Slab: 27912344 kB
SReclaimable: 17133380 kB
SUnreclaim: 10778964 kB
KernelStack: 42112 kB
PageTables: 61316 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 32983604 kB
Committed_AS: 27884200 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
Percpu: 16441728 kB
HardwareCorrupted: 0 kB
AnonHugePages: 612352 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 41185080 kB
DirectMap2M: 25923584 kB
DirectMap1G: 2097152 kB

关注了下面的部分

1
2
3
Slab:           27912344 kB
SReclaimable: 17133380 kB
SUnreclaim: 10778964 kB

不可回收的slab内存差不多10个G。

查看slab占用

slabtop 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
~# slabtop
Active / Total Objects (% used) : 55581636 / 118927019 (46.7%)
Active / Total Slabs (% used) : 2792439 / 2792439 (100.0%)
Active / Total Caches (% used) : 97 / 123 (78.9%)
Active / Total Size (% used) : 12165536.08K / 27438867.98K (44.3%)
Minimum / Average / Maximum Object : 0.01K / 0.23K / 8.00K

OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
40731663 9143202 22% 0.19K 969802 42 7758416K dentry
22982106 10634217 46% 0.09K 547193 42 2188772K kmalloc-96
11154560 1317716 11% 0.06K 174290 64 697160K kmalloc-64
7999936 7998096 99% 0.06K 124999 64 499996K kmem_cache_node
7999572 7997791 99% 0.38K 190466 42 3047456K kmem_cache
7356126 707560 9% 1.06K 245206 30 7846592K ext4_inode_cache
3875430 2434749 62% 0.10K 99370 39 397480K buffer_head
3431648 3426132 99% 0.12K 107239 32 428956K kmalloc-128
3400782 3398492 99% 0.19K 80971 42 647768K kmalloc-192
1523200 1382921 90% 0.03K 11900 128 47600K kmalloc-32
1224612 503648 41% 0.04K 12006 102 48024K ext4_extent_status
1122800 562131 50% 0.57K 40100 28 641600K radix_tree_node
1046784 1046407 99% 0.25K 32712 32 261696K kmalloc-256
675008 661284 97% 0.06K 10547 64 42188K dmaengine-unmap-2
488224 487097 99% 0.50K 15257 32 244112K kmalloc-512
450256 449928 99% 2.00K 28141 16 900512K kmalloc-2048
305024 304960 99% 0.06K 4766 64 19064K anon_vma_chain
296580 296210 99% 1.00K 9269 32 296608K kmalloc-1024
275574 275535 99% 0.20K 7066 39 56528K vm_area_struct
268200 266852 99% 0.13K 8940 30 35760K kernfs_node_cache
264996 264996 100% 0.04K 2598 102 10392K pde_opener
258128 239919 92% 0.05K 3536 73 14144K Acpi-Parse
244136 243898 99% 4.00K 30517 8 976544K kmalloc-4096
203964 203459 99% 0.09K 4434 46 17736K anon_vma
190096 188716 99% 0.25K 5941 32 47528K filp
162127 160216 98% 0.59K 3059 53 97888K inode_cache
151040 151040 100% 0.02K 590 256 2360K kmalloc-16
124544 123705 99% 0.12K 3892 32 15568K pid
113376 106310 93% 0.66K 2362 48 75584K proc_inode_cache
105984 105984 100% 0.01K 207 512 828K kmalloc-8
71008 70844 99% 0.07K 1268 56 5072K eventpoll_pwq
54280 54188 99% 0.69K 1180 46 37760K sock_inode_cache
47460 47460 100% 0.19K 1130 42 9040K cred_jar
44180 44180 100% 0.67K 940 47 30080K ovl_inode
39200 39200 100% 0.25K 1225 32 9800K skbuff_head_cache
25024 25024 100% 1.00K 782 32 25024K UNIX
23736 22736 95% 0.70K 516 46 16512K shmem_inode_cache
23322 23322 100% 0.69K 507 46 16224K files_cache
20550 20550 100% 1.06K 685 30 21920K signal_cache
18120 18120 100% 1.06K 604 30 19328K mm_struct
13880 13723 98% 5.44K 2776 5 88832K task_struct
9408 9251 98% 0.38K 224 42 3584K mnt_cache
8325 8250 99% 2.06K 555 15 17760K sighand_cache
8007 8007 100% 0.08K 157 51 628K inotify_inode_mark
7854 7652 97% 0.31K 154 51 2464K nf_conntrack
6936 6936 100% 0.12K 204 34 816K jbd2_journal_head
5440 5440 100% 0.02K 32 170 128K numa_policy
5304 5304 100% 0.23K 156 34 1248K tw_sock_TCP
5256 5256 100% 0.05K 72 73 288K mbcache
4736 4736 100% 0.03K 37 128 148K fscrypt_info
4416 4416 100% 0.06K 69 64 276K ext4_io_end
4080 4080 100% 0.05K 48 85 192K ftrace_event_field
3978 3978 100% 0.04K 39 102 156K Acpi-Namespace
3210 3210 100% 2.12K 214 15 6848K TCP
3024 3024 100% 0.07K 54 56 216K Acpi-Operand
2760 2760 100% 0.09K 60 46 240K trace_event_file
2720 2720 100% 0.05K 32 85 128K fscrypt_ctx
2592 2592 100% 0.25K 81 32 648K pool_workqueue
2560 2560 100% 0.20K 64 40 512K file_lock_cache

并且看监控, 一直在增加

柳暗花明

上面看slab也确实找不到真正的问题.

在几台dev的机器上去使用

1
echo 2 > /proc/sys/vm/drop_caches

实际上并不会去减少 SUnreclaim 的使用, 而且虽然SUnreclaim使用也很高, 但是加起来也对不上数。其他十几个G的内存都去哪里了?

实在找不到问题, 没办法只能去线上去尝试一下,死马当活马医。

但是, 没想到使用了以后内存真的降下来了。 那就是说明确实有其他的东西在占用内存。 没办法, 只能去对比 /proc/meminfo,前后数据。

发现一个字段 Percpu:

这个字段数值前后对比差异非常明显, 所以初步怀疑是这个字段造成的内存使用。

沿着这个线索在Google一下,发现在Redhat Podman有人反馈过这个问题。参考文档[6]

发现触发这个问题的逻辑差不多是一样的, 我们的进程也是会一直启动销毁容器。

而容器被创建的cgoups并没有及时的释放。

顺着思路, 也查看了一下目前内存依旧很高节点的cgoups

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~# cat /proc/cgroups | column -t
#subsys_name hierarchy num_cgroups enabled
cpuset 10 31 1
cpu 7 102 1
cpuacct 7 102 1
blkio 5 100 1
memory 9 243964 1
devices 4 100 1
freezer 8 31 1
net_cls 3 31 1
perf_event 2 31 1
net_prio 3 31 1
pids 11 107 1
rdma 6 1 1

~# docker ps -a | wc -l
104

cgoups 的 num_cgroups 竟然有243964个, 而目前容器数量才100左右。

继续搜索下去, 在git上也发现了相关文档参考文档[7]

1
I believe 4.4.19 stable kernel has the fix so this is no longer an issue (finally).

但是我们的内核版本: 4.19.0-18-amd64
感觉也不应该啊。

还原

  1. 执行命令

    1
    while true;do id=$(date +%s%N);docker run --name=test${id} -v /data:/data  centos /bin/echo "${id}";done
  2. 等待30分钟

  3. 查看cgoups

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~# cat /proc/cgroups | column -t
#subsys_name hierarchy num_cgroups enabled
cpuset 11 3 1
cpu 5 34 1
cpuacct 5 34 1
blkio 4 32 1
memory 2 3447 1
devices 3 32 1
freezer 10 3 1
net_cls 9 3 1
perf_event 7 3 1
net_prio 9 3 1
pids 8 40 1
rdma 6 1 1
  1. 清理已退出的容器
1
docker ps -aq --filter status=exited | xargs -r -n 10 -P 4 docker rm
  1. 再次查看cgoups
1
2
3
4
5
6
7
8
9
10
11
12
13
14
~# cat /proc/cgroups | column -t
#subsys_name hierarchy num_cgroups enabled
cpuset 11 3 1
cpu 5 34 1
cpuacct 5 34 1
blkio 4 32 1
memory 2 3035 1
devices 3 32 1
freezer 10 3 1
net_cls 9 3 1
perf_event 7 3 1
net_prio 9 3 1
pids 8 40 1
rdma 6 1 1
  1. 确认当前容器数量
1
2
~# docker ps -a | wc -l
8

可以看到, 一共8个仅存容器, 但是cgoups memory num_cgroups 缺有3000+

1
2
3
4
5
6
7
8
9
10
11
free -m
total used free shared buff/cache available
Mem: 3803 590 1839 0 1373 2992
Swap: 0 0 0


grep Per /proc/meminfo
Percpu: 16848 kB

grep SUnreclaim /proc/meminfo
SUnreclaim: 217148 kB

Percpu 一直在增加, 并且 SUnreclaim 也占用了200M

我们使用命令清理一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
echo 1 > /proc/sys/vm/drop_caches

~# sync
~# cat /proc/cgroups | column -t
#subsys_name hierarchy num_cgroups enabled
cpuset 11 3 1
cpu 5 34 1
cpuacct 5 34 1
blkio 4 32 1
memory 2 66 1
devices 3 32 1
freezer 10 3 1
net_cls 9 3 1
perf_event 7 3 1
net_prio 9 3 1
pids 8 40 1
rdma 6 1 1

~# grep Per /proc/meminfo
Percpu: 16848 kB
~# grep SUnreclaim /proc/meminfo
SUnreclaim: 43664 kB
~# free -m
total used free shared buff/cache available
Mem: 3803 307 3148 0 347 3268
Swap: 0 0 0

发现内存和 cgoups 的 num_cgroups 也恢复了正常。

之后又进行了一轮测试, 如果run的时候加一个--rm 内存和cgoups使用则是正常状态.

1
while true;do id=$(date +%s%N);docker run --rm --name=test${id} -v /data:/data  centos /bin/echo "${id}";done

随后又测试了一下内核版本5.10.0-11-amd64发现也同样出现这种问题.

结论

目前看下来:

  1. docker 手动rm不会造成 cgoups 的回收. 但具体是内核的bug,还是docker的,暂时没办法查明,欢迎大佬们留言。
  2. 当在Linux下频繁存取 or 读取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching。

正因为我们的应用一直频繁读取文件,并且容器启停频率很高,所以导致了内存占用的问题。

参考文档

CATALOG
  1. 1. 前言
  2. 2. 排查
    1. 2.1. slab 内存分析
    2. 2.2. 查看slab占用
    3. 2.3. 柳暗花明
    4. 2.4. 还原
  3. 3. 结论
  4. 4. 参考文档