Kubernetes,简单的SpringBoot应用程序OOMKilled

2022-09-02 22:19:44

我正在使用OpenJDK 11和一个非常简单的SpringBoot应用程序,它几乎唯一拥有的就是启用SpringBoot执行器,所以我可以调用/执行器/健康等。

我在GCE上也有一个非常简单的kubernetes集群,只有一个带有容器的pod(当然包含这个应用程序)

我的配置有一些我想强调的关键点,它有一些要求和限制

resources:
  limits:
    memory: 600Mi
  requests:
    memory: 128Mi

它有一个就绪探测器

readinessProbe:
  initialDelaySeconds: 30
  periodSeconds: 30
  httpGet:
    path: /actuator/health
    port: 8080

我还设置了一个JVM_OPTS(我的程序显然正在使用)

env:
- name: JVM_OPTS
  value: "-XX:MaxRAM=512m"

问题

我启动这个,它每次都会在大约3小时内被OOM杀死!

我从来没有自己称呼任何东西,唯一的调用是kubernetes每30秒进行一次就绪探测,这足以耗尽内存?我也没有实现任何不寻常的东西,只是一个Get方法,沿着所有SpringBoot导入说你好世界,以拥有执行器

如果我运行 kubectl top pod XXXXXX 我实际上看到如何逐渐变大

我已经尝试了很多不同的配置,提示等,但似乎任何东西都可以与基本的SpringBoot应用程序一起使用

有没有办法以Java可以引发OutOfMemory异常的方式实际硬限制内存?还是为了防止这种情况发生?

提前致谢


编辑:运行15小时后

NAME                        READY   STATUS    RESTARTS   AGE
pod/test-79fd5c5b59-56654   1/1     Running   4          15h

描述 pod 说...

State:          Running
  Started:      Wed, 27 Feb 2019 10:29:09 +0000
Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
  Started:      Wed, 27 Feb 2019 06:27:39 +0000
  Finished:     Wed, 27 Feb 2019 10:29:08 +0000

最后的时间跨度大约是4个小时,只有483次调用/actuator/health,显然这足以使java超过MaxRAM提示?


编辑:近17小时

它即将再次死亡

$ kubectl top pod test-79fd5c5b59-56654

NAME                    CPU(cores)   MEMORY(bytes)   
test-79fd5c5b59-56654   43m          575Mi      

编辑:在23h失去任何希望

NAME                        READY   STATUS    RESTARTS   AGE
pod/test-79fd5c5b59-56654   1/1     Running   6          23h

描述 pod:

State:          Running
      Started:      Wed, 27 Feb 2019 18:01:45 +0000
    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Wed, 27 Feb 2019 14:12:09 +0000
      Finished:     Wed, 27 Feb 2019 18:01:44 +0000

编辑:新发现

昨天晚上,我做了一些有趣的阅读:

https://developers.redhat.com/blog/2017/03/14/java-inside-docker/ https://banzaicloud.com/blog/java10-container-sizing/ https://medium.com/adorsys/jvm-memory-settings-in-a-container-environment-64b0840e1d9e

TL;DR 我决定删除内存限制并再次启动该过程,结果非常有趣(运行11小时后)

NAME                    CPU(cores)   MEMORY(bytes)   
test-84ff9d9bd9-77xmh   218m         1122Mi  

所以。。。WTH与那个CPU?我期望内存使用量有一个很大的数字,但是CPU会发生什么?

我能想到的一件事是,GC运行得很疯狂,认为MaxRAM是512m,他使用的是1G以上。我想知道,Java检测人体工程学是否正确?(我开始怀疑它)

为了测试我的理论,我设置了512m的限制并以这种方式部署应用程序,我发现从一开始就存在一个不寻常的CPU负载,它必须是GC非常频繁地运行

kubectl create ...

limitrange/mem-limit-range created 
pod/test created

kubectl exec -it test-64ccb87fd7-5ltb6 /usr/bin/free
              total        used        free      shared  buff/cache   available
Mem:        7658200     1141412     4132708       19948     2384080     6202496
Swap:             0           0           0

kubectl top pod ..
NAME                    CPU(cores)   MEMORY(bytes)   
test-64ccb87fd7-5ltb6   522m         283Mi    

522m是太多的vCPU,所以我的逻辑下一步是确保我在这种情况下使用最合适的GC,我以这种方式更改了JVM_OPTS:

  env:
  - name: JVM_OPTS
    value: "-XX:MaxRAM=512m -Xmx128m -XX:+UseSerialGC"
  ...
    resources:
      requests:
        memory: 256Mi
        cpu: 0.15
      limits:
        memory: 700Mi

这再次将vCPU使用率带到了合理的状态,之后kubectl top pod

NAME                    CPU(cores)   MEMORY(bytes)   
test-84f4c7445f-kzvd5   13m          305Mi 

搞砸Xmx的MaxRAM显然会影响JVM,但是如何无法控制我们在虚拟化容器上的内存量?我知道该命令将报告主机可用的RAM,但OpenJDK应该使用cgroups rihgt?。free

我仍然在监视内存...


编辑:新的希望

我做了两件事,第一件事是再次删除我的容器限制,我想分析它会增长多少,我还添加了一个新的标志,看看进程如何使用本机内存。-XX:NativeMemoryTracking=summary

一开始,一切都很正常,这个过程开始消耗300MB,所以我让它运行了大约4个小时,然后......kubectl top pod

kubectl top pod

NAME                    CPU(cores)   MEMORY(bytes)
test-646864bc48-69wm2   54m          645Mi

有点意料之中,对吧?但后来我检查了本机内存使用情况

jcmd <PID> VM.native_memory summary

Native Memory Tracking:

Total: reserved=2780631KB, committed=536883KB
-                 Java Heap (reserved=131072KB, committed=120896KB)
                            (mmap: reserved=131072KB, committed=120896KB)

-                     Class (reserved=203583KB, committed=92263KB)
                            (classes #17086)
                            (  instance classes #15957, array classes #1129)
                            (malloc=2879KB #44797)
                            (mmap: reserved=200704KB, committed=89384KB)
                            (  Metadata:   )
                            (    reserved=77824KB, committed=77480KB)
                            (    used=76069KB)
                            (    free=1411KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=122880KB, committed=11904KB)
                            (    used=10967KB)
                            (    free=937KB)
                            (    waste=0KB =0.00%)

-                    Thread (reserved=2126472KB, committed=222584KB)
                            (thread #2059)
                            (stack: reserved=2116644KB, committed=212756KB)
                            (malloc=7415KB #10299)
                            (arena=2413KB #4116)

-                      Code (reserved=249957KB, committed=31621KB)
                            (malloc=2269KB #9949)
                            (mmap: reserved=247688KB, committed=29352KB)

-                        GC (reserved=951KB, committed=923KB)
                            (malloc=519KB #1742)
                            (mmap: reserved=432KB, committed=404KB)

-                  Compiler (reserved=1913KB, committed=1913KB)
                            (malloc=1783KB #1343)
                            (arena=131KB #5)

-                  Internal (reserved=7798KB, committed=7798KB)
                            (malloc=7758KB #28415)
                            (mmap: reserved=40KB, committed=40KB)

-                     Other (reserved=32304KB, committed=32304KB)
                            (malloc=32304KB #3030)

-                    Symbol (reserved=20616KB, committed=20616KB)
                            (malloc=17475KB #212850)
                            (arena=3141KB #1)

-    Native Memory Tracking (reserved=5417KB, committed=5417KB)
                            (malloc=347KB #4494)
                            (tracking overhead=5070KB)

-               Arena Chunk (reserved=241KB, committed=241KB)
                            (malloc=241KB)

-                   Logging (reserved=4KB, committed=4KB)
                            (malloc=4KB #184)

-                 Arguments (reserved=17KB, committed=17KB)
                            (malloc=17KB #469)

-                    Module (reserved=286KB, committed=286KB)
                            (malloc=286KB #2704)

等等,什么?2.1 GB 为线程保留?和222 MB正在使用,这是什么?我现在不知道,我只是看到了它...

我需要时间来试图理解为什么会发生这种情况


答案 1

我终于找到了我的问题,我想分享它,以便其他人可以从中受益。

正如我在上次编辑时发现的那样,我有一个线程问题,随着时间的推移导致所有内存消耗,特别是我们使用来自第三方库的异步方法,而没有正确处理这些资源(确保这些调用在这种情况下正确结束)。

我之所以能够检测到这个问题,是因为我从一开始就在我的 kubernete 部署中使用了内存限制(这在生产环境中是一个很好的做法),然后我使用工具非常密切地监视我的应用内存消耗,最重要的是,这方面给了我很多细节的标志。jstat, jcmd, visualvm, kill -3-XX:NativeMemoryTracking=summary


答案 2

推荐