我司采用的是k8s治理编排容器服务
出现的问题:线上某个应用pod频繁重启,13天内每个pod重启约8~9次
kubectl describe pods okr-indicator-deploy-84bc5779c-55xsn -n rishiqing-v2
State: Running
Started: Sat, 02 Dec 2023 13:55:58 +0800
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Fri, 01 Dec 2023 13:51:19 +0800
Finished: Sat, 02 Dec 2023 13:55:57 +0800
Ready: True
Restart Count: 9
Limits:
cpu: 1500m
memory: 3Gi
Requests:
cpu: 1500m
memory: 3Gi
可以看到
由于pod有探针机制(容器健康度检测),如果容器unhealth会直接restart容器,所以无法在unhealth的状态下使用一些命令工具查看容器内存具体使用情况。
基于以上限制因素,只能先通过日志和监控看下有无线索。
kubectl logs -p xxxxx -n yyyy >/user/root
发现日志并无异样,也没有相关异常报错信息
jvm监控
有突然上升的younggc次数,但是这个时间点58也可能是容器刚启动那一刻
找到pod当时所在node,查看日志路径
/var/log/messages
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: CPU: 0 PID: 2079469 Comm: http-nio-8080-e Tainted: G OE ------------ T 3.10.0-1127.19.1.el7.x86_64 #1
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Hardware name: Alibaba Cloud Alibaba Cloud ECS, BIOS 449e491 04/01/2014
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Call Trace:
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a17ffa5>] dump_stack+0x19/0x1b
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a17a8c3>] dump_header+0x90/0x229
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff89c9c4d8>] ? ep_poll_callback+0xf8/0x220
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff89bc251e>] oom_kill_process+0x25e/0x3f0
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff89b33a51>] ? cpuset_mems_allowed_intersects+0x21/0x30
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff89c40c56>] mem_cgroup_oom_synchronize+0x546/0x570
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff89c400d0>] ? mem_cgroup_charge_common+0xc0/0xc0
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff89bc2dc4>] pagefault_out_of_memory+0x14/0x90
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a178dd3>] mm_fault_error+0x6a/0x157
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a18d8d1>] __do_page_fault+0x491/0x500
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a18da26>] trace_do_page_fault+0x56/0x150
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a18cfa2>] do_async_page_fault+0x22/0xf0
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [<ffffffff8a1897a8>] async_page_fault+0x28/0x30
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Task in /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod943d7e40_3a14_4519_b7a7_a2b6ee3576fb.slice/docker-928e0ea07906597976057ef044c3766df7867e3c629ee6312a5dd1683b545720.scope killed as a result of limit of /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod943d7e40_3a14_4519_b7a7_a2b6ee3576fb.slice
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: memory: usage 3145728kB, limit 3145728kB, failcnt 498567
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: memory+swap: usage 3145728kB, limit 9007199254740988kB, failcnt 0
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: kmem: usage 0kB, limit 9007199254740988kB, failcnt 0
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod943d7e40_3a14_4519_b7a7_a2b6ee3576fb.slice: cache:0KB rss:0KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:0KB inactive_file:0KB active_file:0KB unevictable:0KB
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod943d7e40_3a14_4519_b7a7_a2b6ee3576fb.slice/docker-fe1d7dae3ef237940616fe625a5c520d2de9b2d2f9379b039dc9e42535e8f0d7.scope: cache:0KB rss:40KB rss_huge:0KB mapped_file:0KB swap:0KB inactive_anon:0KB active_anon:40KB inactive_file:0KB active_file:0KB unevictable:0KB
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Memory cgroup stats for /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod943d7e40_3a14_4519_b7a7_a2b6ee3576fb.slice/docker-928e0ea07906597976057ef044c3766df7867e3c629ee6312a5dd1683b545720.scope: cache:156KB rss:3145532KB rss_huge:1538048KB mapped_file:44KB swap:0KB inactive_anon:0KB active_anon:3145500KB inactive_file:36KB active_file:32KB unevictable:0KB
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [285292] 0 285292 239 1 2 0 -998 pause
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: [2076520] 0 2076520 4999026 787017 1968 0 953 java
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Memory cgroup out of memory: Kill process 4113092 (Java2D Disposer) score 1955 or sacrifice child
Dec 2 13:55:56 iZ2ze2ih1s2cnx3x31dpdeZ kernel: Killed process 2076520 (java), UID 0, total-vm:19996104kB, anon-rss:3134504kB, file-rss:13564kB, shmem-rss:0kB
看到一个很有意思的指标 memory failcnt
在13:15的时候有明显突刺
在13:55重启之间也会有不同程度的数值
然后搜索该指标含义,在linux内核官网上有这么一番描述
memory.failcnt # show the number of memory usage hits limits
memory.failcnt 表示内存使用击中限制的次数。
看样子像是代表容器的内存使用率一直处于很高的状态,
于是我对比看了下另一个小组从未重启过的pod的使用情况,果然别人家的孩子一直都是0。
基于此,有观测到了几个有意思的指标
k8s偏注重wss,此说法的依据:
结论:
container_memory_usage_bytes > container_memory_working_set_bytes > container_memory_rss
基于此结论,将观测时间线拉长,发现内存的rss指标总体一直呈上升趋势,直到最后容器重启
难道是内存泄漏吗?
再次回到上面的jvm监控信息上,发现只有非堆在不停增长,已知jdk8将元空间的实现放在非堆内存中,所以决定限制元空间大小观测几天
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
至此该应用没问题了,但是另一个应用自从限制之后开始频繁fullgc,根源还是metaspace空间不够触发的fullgc
至此有两种方案
-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m
-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=50.0
但是以上两种方案尝试过后均没有根治重启的问题,元空间增长到一定程度之后pod还是会重启。
至此基本可以推断出存在一定的内存泄漏问题
内存泄漏:程序分配内存后,该内存区域无法再次使用或释放
运行一段时间后,dump出heapdump文件
借助mat工具查看
io.grpc.netty.shaded.io.netty.buffer.PoolThreadCache
github讨论对应的issue
https://github.com/grpc/grpc-java/issues/5671
怀疑通过线程缓存产生了大量的垃圾,解决方案关闭对应缓存
Dio.netty.allocator.useCacheForAllThreads=false
可能netty内存泄漏问题,但有如下疑虑点:
观察几天后发现容器还是会有类似问题出现,继续排查。
晚上配置打印出类加载信息
-XX:+TraceClassLoading -XX:+TraceClassUnloading
早上峰值来看:大量类似信息,而且一直在加载类似类,且在gc后无任何unload信息
加载
[Loadedma.glasnost.orika.generated.Orika_FieldVO_FieldDetailDTO_Mapper62812963418674919$3899fromfile:/usr/local/tomcat/webapps/task%23workflow/WEB-INF/lib/orika-core-1.5.4.jar]
卸载
查看gc情况:(此处略去,大致提现是元空间持续增长)
结合验证:
(错误,这个是查看的类加载器,和加载的类信息是两个概念,粗心)
实际上应该是sc命令
sc ma.glasnost.orika.generated.*
一个刚启动30min的pod,已经加载类似类750+
另一个从昨晚12点左右启动到今早10:50左右,简单说可以是近2个小时,已经近5000个类似的重复类
这里其实犯了个很低级的错误 Duplicate Classes :重复类,与本次想要观测的指标不是一个东西
问:为什么没有触发fullgc来回收元空间的类?
因为没有限制元空间大小,而元空间属于非堆内存,基本可以理解为宿主机控制。所以pod的oomkilled优先于元空间引发的fullgc,提前一步kill容器,所以一直没有fullgc回收已加载类信息
问:为什么不限制元空间大小?
尝试限制过,但是会引发另一个问题:
- 导致fullgc耗时过长,导致pod的探针健康检测机制触发unhealth,还是会kill容器。
- 这里有考虑过加大探针超时时间,但是考虑到目前配置的是:超时5s,不健康3s。继续加大的话,线上性能肯定会受影响。
- 同时这也只是个治标不治本的方案,因为从观测内存结果来看,内存整体还是一直呈上升趋势
问:为什么这些已加载类没有被gc回收?
- 监控没有触发fullgc,大概率是因为pod的oomkill机制优先于jvm元空间导致的fullgc
- 同时一个已加载类信息要被回收需要同时满足如下条件(相当苛刻)
- 该类所有实例已经被回收:堆中不存在该类任何实例
- 加载该类的类加载器已经被回收:这条几乎很难满足,除非是精心设计过的,如osgi,jsp的重加载
- 该类的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
细致!注意分析工具的英文解释,每一个英文都得关注(粗心)
- 引入jar包一定要深入理解最佳实践后才使用,切勿浅尝辄止能用就行
- 一个已经加载的类被卸载的几率是很小的同时被卸载的时间也是不确定的
- mat似乎不太好查看已加载类信息,比较侧重不同类加载器加载同一个类导致的重复类问题
- visualvm也不太好查看已加载类信息
- arthas不错 功能全面