防止Docker容器CPU重置?

时间:2018-10-31 20:20:31

标签: java spring docker tomcat

这是一个棘手的问题,有点难以解释,但是我会试一试,看看那里是否有人有类似的问题+解决方案。

快速背景
在Docker容器中的Tomcat上运行大型Java Spring App。其他容器很简单,一个用于JMS队列,另一个用于Mysql。我在Windows上运行,并给了Docker尽可能多的CPU(以及内存)。我已经为Catalina设置了JAVA_OPTS,以在docker-compose中最大化内存以及内存限制,但是问题似乎与CPU有关。

当应用程序空闲时,它通常位于103%的CPU(8核,最大800%)上。我们使用一个过程(使用线程池)来运行一些工作人员,以出去并运行一些代码。在我的本地主机(两者之间没有docker)上,它运行非常快且运行良好,并以良好的速度吐出日志。

问题: 在Docker上观看docker stats -a时,我看到该过程开始时CPU开始加速运行。同时,在日志中,随着CPU的增长,一切都按预期运行。它似乎接近700%,然后有点消亡,但事实并非如此。当达到此阈值时,我看到CPU急剧下降至<5%,并保持了一段时间。此时日志停止打印,因此我认为没有任何反应。最终,它会重新启动并返回〜120%,并继续其过程,就像什么也没发生,有时会回升至〜400%。

我正在尝试什么

我一直在尝试内存设置失败,但似乎更像是CPU问题。我知道Docker中的Java有点奇怪,但是我已经在我强大的dev框上给了我所有的空间,该框在本地运行,没有任何障碍。我发现奇怪的是CPU峰值然后死亡,但是容器本身并没有死亡或重置。有没有人看到过类似的问题或知道一些方法来进一步解决Docker的CPU问题?

谢谢。

1 个答案:

答案 0 :(得分:1)

JVM容器中的资源分配存在一个问题,因为它是指整个系统矩阵而不是容器矩阵。在JAVA 7和8中,JVM人体工程学应用了系统(实例)矩阵,例如核心和内存数量,而不是docker分配的资源(核心和内存)。结果,JVM如下基于核心计数和内存初始化了许多参数。

JVM内存占用

-烫发/元​​空间

-JIT字节码

-堆大小(实例内存的JVM人机工程学¼)

CPU

-不。 JIT编译器线程

-不。垃圾回收线程

-不。普通fork-join池中的线程

因此,由于CPU占用率较高,容器趋于变得无响应,或者由于OOM杀死而终止了容器。原因是容器CGGroups和Namespaces被JVM忽略,以限制内存和CPU周期。因此,JVM倾向于获取更多实例资源,而不是限制docker分配资源的单独分配。E

示例

假设两个容器在具有8GB内存的4核实例上运行。当涉及到docker初始化点时,假定docker具有1GB内存和2048个CPU周期作为硬限制。在这里,每个容器都有4个核心,而JVM根据其状态分别分配内存,JIT编译器和GC线程。但是,JVM将看到该实例(4)上的内核总数,并使用该值初始化我们之前看到的默认线程数。因此,两个容器的JVM矩阵将如下所述。

-4 * 2个Jit编译器线程

-4 * 2个垃圾回收线程

-2 GB堆大小* 2(实例完整内存的¼,而不是docker分配的内存)

在内存方面

根据以上示例,随着JVM看到2GB的最大堆大小,JVM将逐渐增加堆使用率,这是实例内存(8GB)的四分之一。一旦容器的内存使用量达到1GB的Hard限制,该容器将被OOM杀死。

关于CPU

根据以上示例,一个JVM已使用4个垃圾回收线程和4个JIT编译器进行了初始化。但是,泊坞窗仅分配2048个CPU周期。因此,这会导致CPU使用率升高,上下文切换和容器响应更多,最终由于CPU使用率过高而终止该容器。

解决方案 基本上,有两个过程,即CGGroups和Namespaces,它们在OS级别上处理这种情况。但是,JAVA 7和8不接受CGgroup和命名空间,但是jdk_1.8.131之后的发行版能够通过JVM参数(-XX:+ UseCGroupMemoryLimitForHeap,-XX:+ UnlockExperimentalVMOptions)启用CGroup限制。但是,它只是为内存问题提供解决方案,而与CPU集问题无关。

使用OpenJDK 9,JVM将自动检测CPUset。特别是在编排中,它还能够使用JVM标志(XX:ParallelGCThreads,XX:ConcGCThreads)根据容器上CPU周期计数手动覆盖CPU设置线程计数的默认参数。