Java 服务 Docker 容器化最佳实践
2022/7/31 1:25:18
本文主要是介绍Java 服务 Docker 容器化最佳实践,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
转载自:https://mp.weixin.qq.com/s/d2PFISYUy6X6ZAOGu0-Kig
1. 概述
当我们在容器中运行 Java 应用程序时,可能希望对其进行调整参数以充分利用资源。
在本教程中,我们将了解如何在运行 Java 进程的容器中设置 JVM 参数。本文将重点关注常见的 -Xmx
和-Xms
标志。
另外,我们还将研究使用某些 Java 版本运行的程序容器化的常见问题,以及如何在常见的容器化 Java 应用程序时设置自定义标志。
2. Java 容器中的默认堆设置
过去,JVM 不知道分配给容器的内存和 CPU。
Java 10 引入了一个新设置:+UseContainerSupport(默认启用)来修复 这个问题,并在 8u191 中将修复反向移植到 Java 8 。
现在 JVM 可以根据分配给容器的内存计算其内存。
2.1 自动内存计算
当不设置-Xmx
和-Xms
参数时,JVM 会根据系统规格来调整堆大小。
看看堆大小:
$ java -XX:+PrintFlagsFinal -version | grep -Ei "maxheapsize|maxram"
输出结果如下:
openjdk version "15" 2020-09-15 OpenJDK Runtime Environment AdoptOpenJDK (build 15+36) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15+36, mixed mode, sharing) size_t MaxHeapSize = 4253024256 {product} {ergonomic} uint64_t MaxRAM = 137438953472 {pd product} {default} uintx MaxRAMFraction = 4 {product} {default} double MaxRAMPercentage = 25.000000 {product} {default} size_t SoftMaxHeapSize = 4253024256 {manageable} {ergonomic}
我们看到 JVM 将其堆大小设置为可用 RAM 的大约 25%。在这个例子中,在一个 16GB 的系统上分配了 4GB
出于测试目的,创建一个文件,名为PrintXmxXms.java,内容是以 MB 为单位打印堆大小,代码内容如下:
import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; public class PrintXmxXms { public static void main(String[] args) { int mb = 1024 * 1024; MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); long xmx = memoryBean.getHeapMemoryUsage().getMax() / mb; long xms = memoryBean.getHeapMemoryUsage().getInit() / mb; System.out.println("Initial Memory (xms) : " + xms + "mb"); System.out.println("Max Memory (xmx) : " + xmx + "mb"); } }
假设已经安装了 JDK,可以编译程序并运行:
$ javac ./PrintXmxXms.java $ java -cp . PrintXmxXms
在 16Gb RAM 的主机上,输出结果为:
INFO: Initial Memory (xms) : 254mb INFO: Max Memory (xmx) : 4056mb
下面,在容器中尝试一下。
2.2. JDK 8u191 之前的版本
在包含 PrintXmxXms.java 文件的文件夹中添加以下 Dockerfile:
FROM openjdk:8u92-jdk-alpine COPY *.java /src/ RUN mkdir /app \ && ls /src \ && javac /src/PrintXmxXms.java -d /app CMD ["sh", "-c", \ "java -version \ && java -cp /app PrintXmxXms"]
这里使用的容器使用旧版本的 Java 8,它早于更新版本中可用的容器支持。构建镜像:
$ sudo docker build -t oldjava .
Dockerfile 中的 CMD 行是运行容器时默认执行的进程。由于没有提供-Xmx
或-Xms
JVM 标志,内存设置将是默认设置。
运行容器:
$ sudo docker run --rm -ti oldjava openjdk version "1.8.0_92-internal" OpenJDK Runtime Environment (build 1.8.0_92-...) OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode) Initial Memory (xms) : 198mb Max Memory (xmx) : 2814mb
现在使用--memory=1g
命令行标志将容器内存限制为 1GB:
$ sudo docker run --rm -ti --memory=1g oldjava openjdk version "1.8.0_92-internal" OpenJDK Runtime Environment (build 1.8.0_92-...) OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode) Initial Memory (xms) : 198mb Max Memory (xmx) : 2814mb
输出完全相同。这证明旧的 JVM 没有遵守容器内存限制。
2.3. JDK 8u130 之后的版本
使用相同的测试程序,更改 Dockerfile 的第一行来使用 JVM 8 的新版本:
FROM openjdk:8-jdk-alpine
然后再次做测试:
$ sudo docker build -t newjava . $ sudo docker run --rm -ti newjava openjdk version "1.8.0_212" OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0) OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode) Initial Memory (xms) : 198mb Max Memory (xmx) : 2814mb
如上输出,使用整个 docker 主机内存来计算 JVM 堆大小。但是,如果为容器分配 1GB 的 RAM:
$ sudo docker run --rm -ti --memory=1g newjava openjdk version "1.8.0_212" OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0) OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode) Initial Memory (xms) : 16mb Max Memory (xmx) : 247mb
这一次,JVM 根据容器可用的 1GB RAM 计算堆大小。
现在我们了解了 JVM 如何计算其默认值以及为什么需要一个最新的 JVM 来获得正确的默认值。
常用的基础镜像中内存设置
3.1 OpenJDK
与其直接在容器命令上硬编码 JVM 标志,不如使用环境变量。例如在Dockerfile 中使用 JAVA_OPTS 变量,可以在启动容器时对其进行修改:
FROM openjdk:8u92-jdk-alpine COPY *.java /src/ RUN mkdir /app && ls /src && javac /src/PrintXmxXms.java -d /app ENV JAVA_OPTS="" CMD ["sh", "-c", "java -version && java $JAVA_OPTS -cp /app PrintXmxXms"]
构建镜像:
$ sudo docker build -t openjdk-java .
通过指定JAVA_OPTS环境变量在运行时选择内存设置:
$ sudo docker run --rm -ti -e JAVA_OPTS="-Xms50M -Xmx50M" openjdk-java openjdk version "1.8.0_92-internal" OpenJDK Runtime Environment (build 1.8.0_92-internal-alpine-r1-b14) OpenJDK 64-Bit Server VM (build 25.92-b14, mixed mode) Initial Memory (xms) : 50mb Max Memory (xmx) : 48mb
注意:-Xmx 参数和 JVM 报告的 Max memory 之间存在细微差别。这是因为 Xmx 设置了内存分配池的最大大小,其中包括堆、垃圾收集器的幸存者空间和其他池。
3.2 Tomcat 9
Tomcat 9 容器有自己的启动脚本,因此要设置 JVM 参数,需要使用这些脚本。
bin/catalina.sh 脚本要求在环境变量 CATALINA_OPTS 中设置内存参数。
首先需要 创建一个 war 包部署到 Tomcat。
然后,我们使用下面的Dockerfile 对其进行容器化,并在其中声明CATALINA_OPTS环境变量:
FROM tomcat:9.0 COPY ./target/*.war /usr/local/tomcat/webapps/ROOT.war ENV CATALINA_OPTS="-Xms1G -Xmx1G"
然后构建容器镜像并运行它:
$ sudo docker build -t tomcat . $ sudo docker run --name tomcat -d -p 8080:8080 -e CATALINA_OPTS="-Xms512M -Xmx512M" tomcat
注意:运行时,将新值传递给 CATALINA_OPTS。如果不提供这个值,会使用 Dockerfile 的第 3 行给出的默认值。
可以检查应用的运行时参数并验证选项-Xmx
和-Xms
是否存在:
$ sudo docker exec -ti tomcat jps -lv 1 org.apache.catalina.startup.Bootstrap <other options...> -Xms512M -Xmx512M
4. 使用构建插件
Maven 和 Gradle 提供的插件允许我们在没有Dockerfile的情况下创建容器镜像。生成的镜像通常可以在运行时通过环境变量进行参数化。
下面看几个例子。
4.1 使用 Spring Boot
从 Spring Boot 2.3 开始,Spring Boot Maven和 Gradle插件可以在没有 Dockerfile的情况下高效构建容器。
使用 Maven 时,将它们添加到 spring-boot-maven-plugin 中的 <configuration>
块中:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <groupId>com.baeldung.docker</groupId> <artifactId>heapsizing-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <!-- dependencies... --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <name>heapsizing-demo</name> </image> <!-- for more options, check: https://docs.spring.io/spring-boot/docs/2.4.2/maven-plugin/reference/htmlsingle/#build-image --> </configuration> </plugin> </plugins> </build> </project>
要构建项目,运行下面的命令:
$ ./mvnw clean spring-boot:build-image
这将产生一个名为 <artifact-id>:<version>
的镜像。在这个例子中产生的镜像名为:demo-app:0.0.1-SNAPSHOT。Spring Boot 底层使用 Cloud Native Buildpacks 作为容器化技术。
该插件对 JVM 的内存设置进行硬编码。但是,我们仍然可以通过设置环境变量JAVA_OPTS 或 JAVA_TOOL_OPTIONS 来覆盖:
$ sudo docker run --rm -ti -p 8080:8080 -e JAVA_TOOL_OPTIONS="-Xms20M -Xmx20M" --memory=1024M heapsizing-demo:0.0.1-SNAPSHOT
输出将与此类似:
Setting Active Processor Count to 8 Calculated JVM Memory Configuration: [...] [...] Picked up JAVA_TOOL_OPTIONS: -Xms20M -Xmx20M [...]
4.2 使用谷歌 JIB
就像 Spring Boot maven 插件一样,Google JIB 无需 Dockerfile 即可高效创建 Docker 镜像。Maven 和 Gradle 插件以类似的方式配置。Google JIB 还使用环境变量 JAVA_TOOL_OPTIONS 作为 JVM 参数的覆盖机制。
我们可以在任何能够生成可执行 jar 文件的 Java 框架中使用 Google JIB Maven 插件。例如,可以在 Spring Boot 应用程序中使用它来代替spring-boot-maven插件来生成容器镜像:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <!-- dependencies, ... --> <build> <plugins> <!-- [ other plugins ] --> <plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>2.7.1</version> <configuration> <to> <image>heapsizing-demo-jib</image> </to> </configuration> </plugin> </plugins> </build> </project>
镜像是使用 mvn jib:dockerBuild
命令构建的:
$ mvn clean install && mvn jib:dockerBuild
尝试运行:
$ sudo docker run --rm -ti -p 8080:8080 -e JAVA_TOOL_OPTIONS="-Xms50M -Xmx50M" heapsizing-demo-jib Picked up JAVA_TOOL_OPTIONS: -Xms50M -Xmx50M [...] 2021-01-25 17:46:44.070 INFO 1 --- [ main] c.baeldung.docker.XmxXmsDemoApplication : Started XmxXmsDemoApplication in 1.666 seconds (JVM running for 2.104) 2021-01-25 17:46:44.075 INFO 1 --- [ main] c.baeldung.docker.XmxXmsDemoApplication : Initial Memory (xms) : 50mb 2021-01-25 17:46:44.075 INFO 1 --- [ main] c.baeldung.docker.XmxXmsDemoApplication : Max Memory (xmx) : 50mb
5. 结论
在本文中,我们介绍了需要使用最新的 JVM 来获取在容器中默认内存设置。
然后,研究了在自定义容器映像中设置 -Xms
和 -Xmx
的最佳实践, 以及如何使用现有 Java 应用程序容器在其中设置 JVM 选项。
最后,我们看到了如何利用构建工具来管理 Java 应用程序的容器化。
这篇关于Java 服务 Docker 容器化最佳实践的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-15鸿蒙生态设备数量超8亿台
- 2024-05-13TiDB + ES:转转业财系统亿级数据存储优化实践
- 2024-05-09“2024鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)”实战课程已上线
- 2024-05-09聊聊如何通过arthas-tunnel-server来远程管理所有需要arthas监控的应用
- 2024-05-09log4j2这么配就对了
- 2024-05-09nginx修改Content-Type
- 2024-05-09Redis多数据源,看这篇就够了
- 2024-05-09Google Chrome驱动程序 124.0.6367.62(正式版本)去哪下载?
- 2024-05-09有没有大佬知道这种数据应该怎么抓取呀?
- 2024-05-09这种运行结果里的10.100000001,怎么能最快改成10.1?