如何在Docker里跑Java

86次阅读
没有评论

共计 3470 个字符,预计需要花费 9 分钟才能阅读完成。

本篇内容介绍了“如何在 Docker 里跑 Java”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

背景:众所周知,当我们执行没有任何调优参数(如“java-jar mypplication-fat.jar”)的 Java 应用程序时,JVM 会自动调整几个参数,以便在执行环境中具有最佳性能。

但是许多开发者发现,如果让 JVM ergonomics (即 JVM 人体工程学,用于自动选择和行为调整)对垃圾收集器、堆大小和运行编译器使用默认设置值,运行在 Linux 容器(docker,rkt,runC,lxcfs 等)中的 Java 进程会与我们的预期表现严重不符。

懒人超精简阅读版:

a.JVM 做不了内存限制,一旦超出资源限制,容器就会出错

b. 即使你多给些内存资源,也没什么卵用,只会错上加错

c. 解决方案:用 Dockfile 中的环境变量来定义 JVM 的额外参数

d. 更进一步:使用由 Fabric8 社区提供的基础 Docker 镜像来定义 Java 应用程序, 将始终根据容器调整堆大小

详细全文:

我们往往把容器当虚拟机,让它定义一些虚拟 CPU 和虚拟内存。其实容器更像是一种隔离机制:它可以让一个进程中的资源(CPU,内存,文件系统,网络等)与另一个进程中的资源完全隔离。Linux 内核中的 cgroups 功能用于实现这种隔离。

然而,一些从执行环境收集信息的应用程序已经在 cgroups 存在之前就被执行了。“top”,“free”,“ps”,甚至 JVM 等工具都没有针对在容器内执行高度受限的 Linux 进程进行优化。

1. 存在的问题

为了演示,我用“docker-machine create -d virtualbox –virtualbox-memory‘1024’docker1024”在 1GB RAM  的虚拟机中创建了 docker daemon。接下来,在一个虚拟内存为 100MB  的容器里面跑三个不同的 Linux distribution,执行“free -h”命令,结果是:它们都显示了 995MB  的总内存。

即使在 Kubernetes / OpenShift 集群中,结果也类似。

我在一个 15GB  内存的集群中跑一个 Kubernetes Pod,并将 Pod 的内存限制为 512M(通过“kubectl run mycentos –image=centos -it –limits=’memory=512Mi”命令实现),最后显示的总内存却是 14GB。

如果想知道为什么会发生这种情况,建议您阅读博客“Memoryinside Linux containers – Or why don’t free and top work in a Linux container?”(https://fabiokung.com/2014/03/13/memory-inside-linux-containers/)

docker switches(-m,-memory 和 -memory-swap)和 kubernetes switch(–limits)在进程超过限制的情况下,会指示 Linux 内核杀死该进程;但 JVM 是完全不知道限制,所以在进程超过限制的时候,糟糕的事情就发生了!

为了模拟在超过指定的内存限制后被杀死的进程,我们可以通过“docker run -it –name mywildfly -m=50m jboss/wildfly”命令在 50MB 内存限制的容器中跑 WildFly 应用 server,用“dockerstats”命令来检查容器限制。

但是在几秒钟之后,Wildfly 的容器执行将被中断并显示:*** JBossAS process (55) received KILL signal ***

“docker inspect mywildfly -f‘{{json.State}}”命令显示由于 OOM(内存不足),该容器已被杀死。注意容器“state”中的 OOMKilled = true。

2.JAVA 的应用程序是如何被影响的?

在 docker daemon 里用 Dockerfile 中定义的参数 -XX:+ PrintFlagsFinal 和 -XX:+ PrintGCDetails 起一个 java 应用。

其中 machine:1GB RAM 容器内存:限制为 150M(对于这个 Spring Boot 应用,似乎够用)

这些参数允许我们读取初始 JVM 人机工程学参数,并了解有关垃圾收集(GC)执行的详细信息。

动手试一下:

我已经在“/ api / memory /”上准备了一个端点,它使用 String 对象加载 JVM 内存来模拟消耗大量内存的操作。我们来调用一次:

此端点将回复“分配超过 80%(219.8 MiB)的最大允许 JVM 内存大小(241.7 MiB)”

在这里我们可以提至少两个问题:

为什么 JVM 最大允许内存 241.7 MiB?

如果这个容器将内存限制为 150MB,那为什么它允许 Java 分配近 220MB?

首先,我们需要回顾一下 JVM 人机工程学页面上关于“最大堆大小”的内容: 是物理内存的 1 /4。由于 JVM 不知道它在一个容器内执行,所以允许最大堆大小将接近 260MB。鉴于我们在容器初始化期间添加了 -XX:+ PrintFlagsFinal 标志,我们可以检查这个值:

其次,我们需要了解,当我们在 docker 命令行中使用参数“-m 150M”时,docker daemon 将在 RAM 中限制 150M,在 Swap 中限制为 150M。因此,该过程可以分配 300M。这就解释了为什么我们的进程没有被杀死。

docker 命令行中的内存限制(-memory)和 swap(-memory-swap)之间的更多组合可以在这里 (https://docs.docker.com/engine/reference/run/#example-run-htop-inside-a-container) 找到。

3. 提供更多内存是否靠谱?

不了解问题的开发者往往认为环境不能为执行 JVM 提供足够的内存。所以通常的解决办法是提供更多内存,这实际上会使事情变得更糟。

我们假设将 daemon 从 1GB 更改为 8GB(使用“docker-machinecreate -d virtualbox –virtualbox-memory‘8192’docker8192”创建),并将容器内存从 150M 更改为 800M:

请注意这次,“curl http://`docker-machine ipdocker8192`:8080/api/memory”命令甚至没有执行完,因为在 8GB 环境中计算的 JVM 的 MaxHeapSize 为 2092957696 字节(〜2GB)。检查“docker logs mycontainer|grep -i MaxHeapSize”

该应用将尝试分配超过 1.6GB 的内存,这超出了此容器的限制(RAM 中的 800MB + Swap 中的 800MB),并且该进程将被杀掉。

很显然,用增加内存且让 JVM 自定义参数的方式在容器里跑 Java,不是什么好主意。  在容器内部运行 Java  应用程序时,我们应该根据应用程序需求和容器限制设置最大堆大小(-Xmx 参数)。

4. 解决方案

Dockerfile  的一个细微变化允许用户指定一个环境变量来定义 JVM  的额外参数。  检查以下行:

现在我们可以使用 JAVA_OPTIONS 环境变量来通知 JVM 堆的大小。对于这个应用程序,300M 就够了。稍后可以检查日志并获取 314572800 字节(300MBi)的值

对于 docker,您可以使用“-e”switch 指定环境变量。

如何在 Docker 里跑 Java

在 Kubernetes 中,您可以使用 switch“-env = [key = value]”设置环境变量:

如何在 Docker 里跑 Java

再进一步

如果可以根据容器限制自动计算堆的值,该怎么做?

使用由 Fabric8 社区提供的基础 Docker 镜像,就可以搞定。这个镜像 fabric8 / java-jboss-openjdk8-jdk 使用一个脚本来计算容器限制,并使用 50%的可用内存作为上限。请注意,这个 50%的内存比可以被复写。您还可以使用此镜像来启用 / 禁用调试,诊断等。

如何在 Docker 里跑 Java

下面一起看看 Dockerfile 是如何作用于这个 Spring Boot 应用程序:

搞定!现在,无论容器内存限制是多少,我们的 Java  应用程序将始终根据容器调整堆大小,而不是根据 daemon  调整堆大小。

如何在 Docker 里跑 Java

“如何在 Docker 里跑 Java”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计3470字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)