cgroup 内存泄露问题排查记录

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 内存占用,可以看见内存占用明显上升。且最终表现和已有环境上的问题一致,总内存占用高,用户态内存占用低,内核内存占用高且无法被释放。

解决

既然是内核问题,且知道了明确复现路径,则可以通过两种方式进行解决:

  1. 对现有3.10内核加入补丁,重新编译。
  2. 更换没有该问题的内核版本。

最终,进过测试后,选择了更换内核版本,将使用 Ubuntu 18.04 作为新的操作系统。

参考

Linux内核使用层次化内存管理的方法,每一层解决不同的问题,从下至上的关键部分如下:

  1. 物理内存管理,主要用于描述内存的布局和属性,主要有Node、Zone和Page三个结构,使内存按照Page为单位来进行管理;
  2. Buddy内存管理,主要解决外部碎片问题,使用get_free_pages等函数以Page的N次方为单位进行申请释放;
  3. Slab内存管理,主要解决内部碎片问题,可以按照使用者指定的大小批量申请内存(需要先创建对象缓存池);
  4. 内核缓存对象,使用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

你可能感兴趣的:(cgroup 内存泄露问题排查记录)