背景

前两篇文章讲了云主机上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 COMMANDroot         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 processnobody     866 13.0  0.3 860164 586748 ?       S    15:13   7:02 nginx: worker processnobody     931 15.6  0.2 759944 486408 ?       R    15:31   5:37 nginx: worker processnobody     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  Mapping0000000000400000    1572     912       0 r-x-- nginx0000000000788000       4       4       4 r---- nginx0000000000789000     148     128     116 rw--- nginx00000000007ae000     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 kBRss:              533596 kBPss:              533596 kBShared_Clean:          0 kBShared_Dirty:          0 kBPrivate_Clean:         0 kBPrivate_Dirty:    533596 kBReferenced:       533596 kBAnonymous:        533596 kBAnonHugePages:         0 kBSwap:                  0 kBKernelPageSize:        4 kBMMUPageSize:           4 kBLocked:                0 kBVmFlags: rd wr mr mw me ac sd

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

gcore pid

失去 "core.pid"文件。

5.gdb 加载内存信息

gdb core.pidsh-4.2$ gdb core.942GNU 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.bin511M    worker-pid.bin

7. 二进制文件剖析

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

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

总结

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