公有云vali gateway服务容器重启问题排查
目录
[一、问题描述... 2](#_Toc54605147)
[二、问题复现与排查... 2](#_Toc54605148)
[三、问题分析... 8](#_Toc54605149)
[四,意外情况... 10](#_Toc54605150)
[五、两种内存策略对比压测与复现... 11](#_Toc54605151)
[六、总结与解决方案... 12](#_Toc54605152)
[七、参考... 12](#_Toc54605153)
一、问题描述
------
gateway服务容器发现oom重启
时间:2023/10/10 22点16分
可用区:华南/华东
图(一)
图(二)
二、问题复现与排查
---------
1,生产环境复现压测
内存监控图
图(三)
gateway是go语言开发的
生产环境压测结果存在内存泄露的可能性,使用go tool pprof排查泄露问题
go程序存在泄露的点主要有
Heap
Goroutine
全局变量
Http 未关闭的响应主体
配置go pprof
import _ "net/http/pprof"
然后就可以在浏览器中使用http://localhost:port/debug/pprof/ 直接看到当前web服务的状态,包括CPU占用情况和内存使用情况等。
当然,非WEB的也可以用下面方式启动WEB。
在 main 方法中增加
func main() {
go func() {
http.ListenAndServe("localhost:7777", nil)
}()
pprof可以使用图像方式查看,如下:
图(四)
由于生产环境不能修改程序,在UAT环境重现压测
收集heap/goroutine数据,收集压测前和压测冷却后的方面对比,如果存在
curl -s -v [http://192.168.1.19:7777/debug/pprof/heap](http://192.168.1.19:7777/debug/pprof/heap) > heap1140.out
curl -s -v [http://192.168.1.19:7777/debug/pprof/goroutine](http://192.168.1.19:7777/debug/pprof/goroutine) > goroutine1140.out
curl -s -v [http://192.168.1.19:7777/debug/pprof/heap](http://192.168.1.19:7777/debug/pprof/heap) > heap1549.out
curl -s -v [http://192.168.1.19:7777/debug/pprof/goroutine](http://192.168.1.19:7777/debug/pprof/goroutine) > goroutine1549.out
比较前后的数据
#go tool pprof -base goroutine1350.out goroutine1549.out
go tool pprof -base goroutine1350.out goroutine1549.out
File: vali_gateway
Build ID: b94e9e6eab7c528b19eda2124cd449cac03b6335
Type: goroutine
Time: Oct 15, 2019 at 1:51pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 0, 0% of 2 total
flat flat% sum% cum cum%
1 50.00% 50.00% 0 0% net/http.(*connReader).backgroundRead
-1 50.00% 0% -1 50.00% runtime.gopark
0 0% 0% -1 50.00% internal/poll.(*FD).Read
0 0% 0% -1 50.00% internal/poll.(*pollDesc).wait
0 0% 0% -1 50.00% internal/poll.(*pollDesc).waitRead
0 0% 0% -1 50.00% internal/poll.runtime_pollWait
0 0% 0% -1 50.00% net.(*conn).Read
0 0% 0% -1 50.00% net.(*netFD).Read
0 0% 0% -1 50.00% runtime.netpollblock
#go tool pprof -base heap1350.out heap1549.out
go tool pprof -base heap1350.out heap1549.out
File: vali_gateway
Build ID: b94e9e6eab7c528b19eda2124cd449cac03b6335
Type: inuse_space
Time: Oct 15, 2019 at 1:51pm (CST)
No samples were found with the d华东ult sample value type.
Try "sample_index" command to analyze different sample values.
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 0, 0% of 0 total
flat flat% sum% cum cum%
从heap和goroutine对比看不出有泄露
由于UAT环境没有监控
查看pod使用内存的情况
压测前
/opt/vali # ps aux -o pid,vsz,rss,comm
PID VSZ RSS COMMAND
1 8168 676 run-vali-service
7 1.2g 33m vali_gateway
78 8168 916 sh
96 8168 916 sh
125 8168 908 sh
154 8168 912 sh
164 8168 672 ps
压测中
/opt/vali # ps aux -o pid,vsz,rss,comm
PID VSZ RSS COMMAND
1 8168 676 run-vali-service
7 1.2g 63m vali_gateway
78 8168 916 sh
96 8168 916 sh
125 8168 908 sh
143 8168 912 sh
153 8168 672 ps
压测冷却后
/opt/vali # ps aux -o pid,vsz,rss,comm
PID VSZ RSS COMMAND
1 8168 676 run-vali-service
7 1.2g 33m vali_gateway
78 8168 916 sh
96 8168 916 sh
125 8168 908 sh
154 8168 912 sh
161 8168 672 ps
从以上数据看出内存没有增长的现象,基本排除泄露的可能性
三、问题分析
------
为什么prd会内存一直增长而uat没有,环境的差异入手
操作系统的内核版本不一样
UAT是centos7.2,PRD 是centos7.6
从网络搜索有类似的案例,gateway使用go的版本是1.13
一直以来 go 的 runtime 在释放内存返回到内核时,在 Linux 上使用的是 MADV_DONTNEED,虽然效率比较低,但是会让 RSS(resident set size 常驻内存集)数量下降得很快。不过在 go 1.12 里专门针对这个做了优化,runtime 在释放内存时,使用了更加高效的 MADV_FREE 而不是之前的 MADV_DONTNEED
这样带来的好处是,一次 GC 后的内存分配延迟得以改善,runtime 也会更加积极地将释放的内存归还给操作系统,以应对大块内存分配无法重用已存在的堆空间的问题。不过也会带来一个副作用:RSS 不会立刻下降,而是要等到系统有内存压力了,才会延迟下降。需要注意的是,`MADV_FREE ` 需要 4.5 以及以上内核,否则 runtime 会继续使用原先的 `MADV_DONTNEED` 方式。
但是centos7.6内核版本是
# uname -r
3.10.0-957.el7.x86_64
不过centos发行版会使用backport,高版本中的新特性移植到发行版的低版本中
采用strace查看程序的内存分配(需要在压测时查看,空闲状态没有内存活动)
[root@CLAM400137 ~]# ps aux | grep gateway
root 27723 0.0 0.3 1137740 30508 ? Sl Oct14 1:04 /opt/vali/vali_gateway --server_address=100.71.64.39:0 --registry=consul api --handler=http --namespace=vali.v1.portal
root 28731 3.5 0.4 1285908 33644 ? Sl 11:31 8:53 /opt/vali/vali_gateway --server_address=192.168.1.19:0 --registry=consul api --handler=http --namespace=vali.v1.portal
root 31738 0.0 0.0 112644 948 pts/29 S+ 15:43 0:00 grep --color=auto gateway
[root@CLAM400137 ~]# strace -q -e trace=memory -p 28731
madvise(0xc0028dc000, 8192, MADV_DONTNEED) = 0
madvise(0xc0028d4000, 8192, MADV_DONTNEED) = 0
madvise(0xc0028c8000, 16384, MADV_DONTNEED) = 0
madvise(0xc0028ba000, 16384, MADV_DONTNEED) = 0
madvise(0xc0028aa000, 8192, MADV_DONTNEED) = 0
madvise(0xc0028a0000, 8192, MADV_DONTNEED) = 0
madvise(0xc00289a000, 8192, MADV_DONTNEED) = 0
madvise(0xc002896000, 8192, MADV_DONTNEED) = 0
madvise(0xc00288e000, 24576, MADV_DONTNEED) = 0
madvise(0xc00288a000, 8192, MADV_DONTNEED) = 0
madvise(0xc002884000, 8192, MADV_DONTNEED) = 0
madvise(0xc002880000, 8192, MADV_DONTNEED) = 0
madvise(0xc00286e000, 24576, MADV_DONTNEED) = 0
madvise(0xc002868000, 8192, MADV_DONTNEED) = 0
madvise(0xc002862000, 8192, MADV_DONTNEED) = 0
madvise(0xc002854000, 32768, MADV_DONTNEED) = 0
[root@华东m11672 11759]# ps aux | grep gate
root 603 0.0 0.0 112724 988 pts/1 S+ 18:11 0:00 grep --color=auto gate
root 11759 6.7 0.3 1987748 49732 ? Sl 10月11 456:33 /opt/vali/vali_gateway --server_address=192.168.1.116:0 --api_address=192.168.1.116:443 --registry=consul api --handler=http --namespace=vali.v1.portal
[root@华东m11672 11759]# strace -q -e trace=memory -p 11759
madvise(0xc0010d6000, 8192, MADV_FREE) = 0
madvise(0xc0010cc000, 24576, MADV_FREE) = 0
madvise(0xc0010c6000, 8192, MADV_FREE) = 0
madvise(0xc0010c0000, 16384, MADV_FREE) = 0
madvise(0xc0010ba000, 16384, MADV_FREE) = 0
madvise(0xc0010b4000, 16384, MADV_FREE) = 0
madvise(0xc0010aa000, 16384, MADV_FREE) = 0
madvise(0xc0010a6000, 8192, MADV_FREE) = 0
madvise(0xc00109a000, 16384, MADV_FREE) = 0
madvise(0xc001096000, 8192, MADV_FREE) = 0
madvise(0xc00108a000, 16384, MADV_FREE) = 0
madvise(0xc001086000, 8192, MADV_FREE) = 0
madvise(0xc001082000, 8192, MADV_FREE) = 0
madvise(0xc00107c000, 16384, MADV_FREE) = 0
发现在centos7.6中已经支持MADV_FREE策略,所以导致prd的程序内存占用不断增加,另外docker是共享内核模式,内核回收内存的压力是取决于节点内核空闲情况而非容器。由于采用k8s+docker容器化部署,设置了pod 内存limit为1G,当累积到1G时出现oom
四,意外情况
------
在生产压测gateway的过程,导致consul 实例OOM,经过分析,consul实例的内存limits设置为512M,偏小。另外压测时,gateway收到服务请求会consul查询服务健康状况,所以consul的压力也随着增加。为了保证后续的压测顺利,调整consul的内存limits为2000Mi。
对比复现压测时的资源消耗监控图(五),consul实例消耗内存大于800Mi。
图(五)
五、两种内存策略对比压测与复现
---------------
对比压测思路,根据两种内存策略的特性,MADV_FREE倾向内存页使用后不回收,MADV_DONTNEED倾向回收(可以从图(六)监控图看两种策略的差异)。同时为了加速复现效果,设置内存limit为100M,压力尽可能接近oom,但不能oom,随着时间的增加,MADV_FREE策略的实例内存页由于倾向不加收而不断累积,最终导致OOM,而MADV_DONTNEED策略的实例则不会被OOM。由压力没办法精确控制,从长时间看应该是MADV_FREE策略的实例被OOM的次数应该远大小后者。
图(六)
比对压测结果:
经过三天测试,MADV_FREE策略的实例被OOM 14次,MADV_DONTNEED策略的实例被OOM1次,符合压测预期结果。
六、总结与解决方案
---------
1,结论重启非内存泄露,由内存MADV_REE策略与资源limit设置太小引起的。
2,回退MADV_REE策略为 MADV_DONTNEED,设置 GODEBUG=madvdontneed=1,避免。
3,调整服务实例内存limits,gateway与consul内存limits为2000Mi,其他设置为1500Mi
4,标准化一致的环境才能更好的提供排错复现条件,UAT环境需要与生产环境保持一致。
七、参考
-----
[https://ms2008.github.io/2019/06/30/golang-madvfree/](https://ms2008.github.io/2019/06/30/golang-madvfree/)
[https://www.cnblogs.com/ExMan/p/12175312.html](https://www.cnblogs.com/ExMan/p/12175312.html)
[https://www.v2ex.com/t/666257](https://www.v2ex.com/t/666257)