关于内存泄漏:难受nginx-worker进程内存持续飘升

13次阅读

共计 3823 个字符,预计需要花费 10 分钟才能阅读完成。

背景

前两篇文章讲了云主机上 lua openresty 我的项目容器化的历程,在测试环境通过一段时间的验证,所有都比较顺利,就在线上开始灰度。

然而,好景不长。灰度没多久,应用 top pod 查看时,发现内存满了,最开始狐疑 k8s 的 resources limit memory(2024Mi) 调配小了,放大后(4096Mi),重启 pod,没多久又满了。

紧接着,狐疑是放量较大,负载高了引起的,扩充hpa,再重启,好家伙,不到两炷香工夫,又满了。

到此,就把眼光投向了 pod 外部的程序了,估摸着又是哪里呈现内存透露了。

坑在哪里

  1. 每过一段时间,内存忽上忽下,并且 nginx worker(子过程)id 号一直减少,是谁杀的过程?
  2. 云主机没有呈现这样的问题,难道是 k8s 引起的?
  3. podresources limit memory 减少和 hpa 减少都没有解决问题。
  4. nginx -s reload能够开释内存,然而没多久后又满了。

解决办法

谁杀的过程?

命令:ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0 252672  5844 ?        Ss   Jun11   0:00 nginx: master process /data/openresty/bin/openresty -g daemon off;
nobody     865 10.1  0.3 864328 590744 ?       S    14:56   7:14 nginx: worker process
nobody     866 13.0  0.3 860164 586748 ?       S    15:13   7:02 nginx: worker process
nobody     931 15.6  0.2 759944 486408 ?       R    15:31   5:37 nginx: worker process
nobody     938 13.3  0.1 507784 234384 ?       R    15:49   2:23 nginx: worker process

发现 woker 过程号曾经靠近 1000 了,那么必定一直的被 kill,而后再调起,那到底是谁干的呢?通过 dmesg命令:

[36812300.604948] dljgo invoked oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=999
[36812300.648057] Task in /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pode4ad18fa_3b8f_4600_a557_a2bc853e80d9.slice/docker-c888fefbafc14b39e42db5ad204b2e5fa7cbfdf20cbd621ecf15fdebcb692a61.scope killed as a result of limit of /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pode4ad18fa_3b8f_4600_a557_a2bc853e80d9.slice/docker-c888fefbafc14b39e42db5ad204b2e5fa7cbfdf20cbd621ecf15fdebcb692a61.scope
[36812300.655582] memory: usage 500000kB, limit 500000kB, failcnt 132
[36812300.657244] memory+swap: usage 500000kB, limit 500000kB, failcnt 0
[36812300.658931] kmem: usage 0kB, limit 9007199254740988kB, failcnt 0
……
[36675871.040485] Memory cgroup out of memory: Kill process 16492 (openresty) score 1286 or sacrifice child

发现当 cgroup 内存不足时,Linux 内核会触发 cgroup OOM 来抉择一些过程 kill 掉,以便能回收一些内存,尽量持续放弃零碎持续运行。

尽管 kill 掉了 nginx worker 过程后开释了内存,短暂性的解决了问题,但根本性问题还没解决。

为啥云主机没问题?

将云主机的 lua 代码拷贝到本地进行比照,发现代码自身并没有什么问题。

那只能是其余方面的问题引起了。

如何是好?

以上两个点,都没能很好的定位问题问题,看来只能通过通过 toppmapgdb等命令去排查问题了。

1. top 内存泄露的过程

通过top 查看哪个过程占用内存高

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                               
  942 nobody    20   0  618.9m 351.7m   4.2m S  18.0  0.2   4:05.72 openresty                                                                                                                                                             
  943 nobody    20   0  413.8m 146.7m   4.2m S  11.7  0.1   1:18.93 openresty                                                                                                                                                             
  940 nobody    20   0  792.0m 524.9m   4.2m S   7.0  0.3   6:25.81 openresty                                                                                                                                                             
  938 nobody    20   0  847.4m 580.2m   4.2m S   3.7  0.3   7:15.97 openresty 
    1 root      20   0  246.8m   5.7m   3.9m S   0.0  0.0   0:00.24 openresty                                                                                                                                               

2. pmap 查看过程的内存调配

通过 pmap -x pid 找出该过程的内存调配,发现 0000000000af7000 存在大内存的调配。

Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000    1572     912       0 r-x-- nginx
0000000000788000       4       4       4 r---- nginx
0000000000789000     148     128     116 rw--- nginx
00000000007ae000     140      28      28 rw---   [anon]
0000000000a15000     904     900     900 rw---   [anon]
0000000000af7000  531080  530980  530980 rw---   [anon]
0000000040048000     128     124     124 rw---   [anon]
……

3. /proc/pid/smaps 定位内存泄露的地址范畴

取得大内存的内存地址后,通过 cat /proc/pid/smaps命令,查看内存段的具体起始地位:

00af7000-21412000 rw-p 00000000 00:00 0                                  [heap]
Size:             533612 kB
Rss:              533596 kB
Pss:              533596 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:    533596 kB
Referenced:       533596 kB
Anonymous:        533596 kB
AnonHugePages:         0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac sd

4. gcore 转存过程映像及内存上下文

gcore pid

失去 “core.pid” 文件。

5.gdb 加载内存信息

gdb core.pid

sh-4.2$ gdb core.942
GNU gdb (GDB) Red Hat Enterprise Linux 7.*
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
[New LWP pid]
Core was generated by `nginx: worker process'.
#0  0x00007ff9435e1463 in ?? ()
"/tmp/core.942" is a core file.
Please specify an executable to debug.
(gdb) 

进入以上命令窗口

6. dump binary 导出泄露内存的内容

第 3 点的时候曾经失去了内存起始地址了,这时候咱们内存地址导出:

dump binary memory worker-pid.bin 0x00af7000 0x21412000

查看导出的文件大小

sh-4.2$ du -sh worker-pid.bin
511M    worker-pid.bin

7. 二进制文件剖析

hex 工具关上二进制文件,发现外面大量的 json 对象,通过剖析 json 对象,发现 pod 上的 so 加密库为旧的(git 上并没有跟新),而云主机依赖的是新的。

替换 so 库,重启 pod,问题解决!~。

总结

生产环境上的内存透露是一个比拟头疼的问题,无妨通过以上的形式去剖析,也是一个不错的思路。

正文完
 0