首页 > 解决方案 > AWS JVM CPU 限制上的 Docker

问题描述

我在 AWS Fargate 服务的 docker 容器中启动我们的 Spring Boot 应用程序,因此一旦 CPU 消耗达到 100% 以上,容器就会停止 Docker OOM-killer 并出现错误

原因:OutOfMemoryError:容器因内存使用而被杀死

在指标上,我们可以看到 CPU 变得超过 100%。经过一段时间的分析,我们似乎发现了消耗 CPU 的代码,但我的问题是,CPU 怎么能超过 100%?

是不是说 JVM 只使用了 100%?

我记得我们在内存消耗方面遇到了类似的问题。看了很多关于cgroups的文章,发现解决方法是指定

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

因此,当您使用选项 -m=512 启动 docker 时,堆大小将是 mac 大小的 1/4。堆大小也可以通过选项进行调整

-XX:MaxRAMFraction=2

这将为堆分配 1/2 的 docker 内存。我应该对 CPU 使用类似的东西吗?我读了文章https://blogs.oracle.com/java-platform-group/java-se-support-for-docker-cpu-and-memory-limits,但它告诉

从 Java SE 8u131 和 JDK 9 开始,JVM 透明地了解 Docker CPU 限制。这意味着如果 -XX:ParalllelGCThreads 或 -XX:CICompilerCount 未指定为命令行选项,JVM 将应用 Docker CPU 限制作为 JVM 在系统上看到的 CPU 数量。然后,JVM 将调整 GC 线程和 JIT 编译器线程的数量,就像它在裸机系统上运行一样,CPU 数量设置为 Docker CPU 限制。

docker命令用于启动

docker run -d .... -e JAVA_OPTS='-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:+PrintFlagsFinal -XshowSettings:vm' -m=512 -c=256 ...

使用Java版本

openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-1~deb9u1-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

启动期间有关应用程序的一些附加信息

VM settings:
    Max. Heap Size (Estimated): 123.75M
    Ergonomics Machine Class: client
    Using VM: OpenJDK 64-Bit Server VM

ParallelGCThreads                         = 0   
CICompilerCount                          := 2
CICompilerCountPerCPU                     = true

标签: javaamazon-web-servicesdockeraws-fargate

解决方案


我找到了我的问题的答案。识别要使用的处理器数量的行为已在https://bugs.openjdk.java.net/browse/JDK-8146115中修复

CPU 数量


使用 number_of_cpus() 和 cpu_sets() 的组合来确定进程可用的处理器数量并适当调整 JVM os::active_processor_count。number_of_cpus() 将根据 cpu_quota() 和 cpu_period() 使用以下公式计算:number_of_cpus() = cpu_quota() / cpu_period()。如果容器已设置 cpu_shares,则 number_of_cpus() 将根据 cpu_shares()/1024 计算。1024 是基于云的容器管理软件中计算相对 CPU 使用率的默认和标准单位。

还添加一个新的 VM 标志 (-XX:ActiveProcessorCount=xx),允许覆盖 CPU 的数量。即使未启用 UseContainerSupport,也会使用此标志。

因此,在 AWS 上,您通常会在任务定义级别设置cpu_shares。在 jvm fix 之前,它计算不正确。

在 java8 版本 < 191 上:cpu_shares()/1024 = 256/1024 = 被识别为 2

在 java8 版本 > 191 上迁移后:cpu_shares()/1024 = 256/1024 = 被识别为 1

要测试的代码

val version = System.getProperty("java.version")
val runtime = Runtime.getRuntime()
val processors = runtime.availableProcessors()
logger.info("========================== JVM Info ==========================")
logger.info("Java version is: {}", version)
logger.info("Available processors: {}", processors)

样本输出

"Java version is: 1.8.0_212"
"Available processors: 1"

我希望它会对某人有所帮助,因为我在任何地方都找不到答案(spring-issues-tracker、AWS 支持等)


推荐阅读