写点什么

使用 GraalVM 为 Java 应用程序构建最少的 Docker 镜像

  • 2019-09-27
  • 本文字数:4232 字

    阅读完需:约 14 分钟

使用 GraalVM 为 Java 应用程序构建最少的 Docker 镜像

优化 Docker 镜像的大小有诸多好处。其中一个好处是能加快部署速度。如果您的应用程序需要快速向外扩展以响应意外的突发流量,那么这一点将非常重要。在这篇博文中,我将向您展示通过一种有趣的方法来优化 Java 应用程序的 Docker 镜像,这也有助于缩短启动时间。所举的例子基于我几个月前发布的另一篇博文 Reactive Microservices Architecture on AWS。

Java 应用程序的工作原理是怎样的?

Java 应用程序使用 Java 11 实现,并使用 Vert.x 3.6 作为主框架。Vert.x 是具有反应性和非阻塞特性的事件驱动型 Polyglot 框架,其作用是实施微服务。它在 Java 虚拟机 (JVM) 上运行,使用低级 I/O 库 Netty。该应用程序包含五个不同的 verticle,涵盖业务逻辑的不同方面。


为了构建此应用程序,我使用了采用不同配置文件的 Maven。第一个配置文件(默认配置文件)使用“标准”构建以创建 Uber JAR(一个包含所有依赖项的独立应用程序)。第二个配置文件使用 GraalVM 编译本地镜像。标准构建使用 jlink 来构建一个具有有限模块集的自定义 Java 运行时。(使用命令行工具 jlink 可关联一系列模块及其可传递依赖项,以创建运行时镜像。)

使用 jlink 构建自定义 JDK 分配

我们需要关注 JDK 9 中包含的 Java 平台模块功能 (JPMS),也称为 Project Jigsaw。该功能是为构建仅包含必要依赖项的模块化 Java 运行时而开发的。对于此应用程序,您只需要数量有限的一组模块(可在构建过程中指定)。在构建前的准备工作中,请下载 Amazon Corretto 11 并解压,然后删除 JDK 附带的 src.zip 文件等不必要的文件。为帮助读者更好地理解相关内容,以下部分使用了多阶段构建,并单独介绍了构建的不同部分。

第 1 步:构建自定义运行时模块

构建过程的第一步是构建自定义运行时(只包含运行应用程序所必需的几个模块),然后将结果写入 /opt/minimal:


FROM debian:9-slim AS builderLABEL maintainer="Sascha Möllering <smoell@amazon.de>"
# First step: build java runtime moduleRUN set -ex && \ apt-get update && apt-get install -y wget unzip && \ wget https://d3pxv6yz143wms.cloudfront.net/11.0.3.7.1/amazon-corretto-11.0.3.7.1-linux-x64.tar.gz -nv && \ mkdir -p /opt/jdk && \ tar zxvf amazon-corretto-11.0.3.7.1-linux-x64.tar.gz -C /opt/jdk --strip-components=1 && \ rm amazon-corretto-11.0.3.7.1-linux-x64.tar.gz && \ rm /opt/jdk/lib/src.zip
RUN /opt/jdk/bin/jlink \ --module-path /opt/jdk/jmods \ --verbose \ --add-modules java.base,java.logging,java.naming,java.net.http,java.se,java.security.jgss,java.security.sasl,jdk.aot,jdk.attach,jdk.compiler,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.internal.ed,jdk.internal.le,jdk.internal.opt,jdk.naming.dns,jdk.net,jdk.security.auth,jdk.security.jgss,jdk.unsupported,jdk.zipfs \ --output /opt/jdk-minimal \ --compress 2 \ --no-header-files
复制代码

第 2 步:将自定义运行时复制到目标镜像

接下来,将新创建的自定义运行时从构建映像复制到实际目标镜像。在这一步,您要再次使用 debian:9-slim 作为基本镜像。复制最小运行时后,将 Java 应用程序复制到 /opt/,添加 Docker 运行状况检查,然后启动 Java 进程:


FROM debian:9-slimLABEL maintainer="Sascha Möllering <smoell@amazon.de>"
COPY --from=builder /opt/jdk-minimal /opt/jdk-minimal
ENV JAVA_HOME=/opt/jdk-minimalENV PATH="$PATH:$JAVA_HOME/bin"
RUN mkdir /opt/app && apt-get update && apt-get install curl -yCOPY target/reactive-vertx-1.5-fat.jar /opt/app
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ CMD curl -f http://localhost:8080/health/check || exit 1
EXPOSE 8080
CMD ["java", "-server", "-XX:+DoEscapeAnalysis", "-XX:+UseStringDeduplication", \ "-XX:+UseCompressedOops", "-XX:+UseG1GC", \ "-jar", "opt/app/reactive-vertx-1.5-fat.jar"]
复制代码

使用 GraalVM 将 Java 编译为本机

GraalVM 是 Oracle 提供的一种开源、高性能 Polyglot 虚拟机。使用它可以提前编译本机镜像,以提高启动性能,并减少基于 JVM 的应用程序的内存消耗和文件大小。允许预先编译的框架称为 SubstrateVM。


在以下部分,您可以看到 pom.xml 文件的相关片段。创建一个名为 native-image-fargate 的附加 Maven 配置文件,该文件使用 native-image-maven 插件在“包装”阶段将源代码编译为本机镜像:


<profile>    <id>native-image-fargate</id>    <build>        <plugins>            <plugin>                <groupId>com.oracle.substratevm</groupId>                <artifactId>native-image-maven-plugin</artifactId>                <version>${graal.version}</version>                <executions>                    <execution>                        <goals>                            <goal>native-image</goal>                        </goals>                        <phase>package</phase>                    </execution>                </executions>                <configuration>                    <imageName>${project.artifactId}</imageName>                    <mainClass>${vertx.verticle}</mainClass>                    <buildArgs>--enable-all-security-services -H:+ReportUnsupportedElementsAtRuntime --allow-incomplete-classpath</buildArgs>                </configuration>            </plugin>        </plugins>    </build></profile>
复制代码

Docker 多阶段构建

您的目标是定义一个包含尽可能少相关项的可重现构建环境。为此,要创建一个使用 Docker 多阶段构建的自包含构建过程。


对于多阶段构建而言,一个值得关注的方面在于,您可以在 Dockerfile 中使用多个 FROM 语句。每条 FROM 指令可以使用不同的基本镜像,并开始构建的新阶段。您可以选择必需的文件并将其从一个阶段复制到另一个阶段,这非常有用,因为这样可以限制必须复制的文件数。使用此功能可以在一个阶段构建应用程序,并将已编译的构件和其他文件复制到目标镜像。


在以下部分,您可以看到构建的两个不同阶段。您的 Dockerfile(称为 Dockerfile-native)分为两部分:构建器镜像和目标镜像。


第一个代码示例显示了基于 graalvm-ce 的生成器镜像。在构建过程中,必须安装 Maven、设置一些环境变量,并将必要的文件复制到 Docker 镜像中。对于构建,您需要源代码和 pom.xml 文件。成功将文件复制到 Docker 镜像后,将使用配置文件 native-image-fargate 启动应用程序到可执行二进制文件的构建。当然,也可以使用 Maven 基本镜像并安装 GraalVM(整个构建过程会稍有不同)。


FROM oracle/graalvm-ce:1.0.0-rc16 AS build-aot
RUN yum update -yRUN yum install wget -yRUN wget https://www-eu.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz -P /tmpRUN tar xf /tmp/apache-maven-3.6.1-bin.tar.gz -C /optRUN ln -s /opt/apache-maven-3.6.1 /opt/mavenRUN ln -s /opt/graalvm-ce-1.0.0-rc16 /opt/graalvm
ENV JAVA_HOME=/opt/graalvmENV M2_HOME=/opt/mavenENV MAVEN_HOME=/opt/mavenENV PATH=${M2_HOME}/bin:${PATH}ENV PATH=${JAVA_HOME}/bin:${PATH}
COPY ./pom.xml ./pom.xmlCOPY src ./src/
ENV MAVEN_OPTS='-Xmx6g'RUN mvn -Dmaven.test.skip=true -Pnative-image-fargate clean package
复制代码


现在,开始执行多阶段构建过程中第二部的操作:创建实际目标镜像。此镜像基于 debian:9-slim,并将两个环境变量设置为 TLS 特定设置(因为应用程序使用 TLS 与 Amazon Kinesis 数据流通信)。


FROM debian:9-slimLABEL maintainer="Sascha Möllering <smoell@amazon.de>"
ENV javax.net.ssl.trustStore /cacertsENV javax.net.ssl.trustAnchors /cacerts
RUN apt-get update && apt-get install -y curl
COPY --from=build-aot target/reactive-vertx /usr/bin/reactive-vertxCOPY --from=build-aot /opt/graalvm/jre/lib/amd64/libsunec.so /libsunec.soCOPY --from=build-aot /opt/graalvm/jre/lib/security/cacerts /cacerts
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ CMD curl -f http://localhost:8080/health/check || exit 1
EXPOSE 8080
CMD [ "/usr/bin/reactive-vertx" ]
复制代码


构建目标镜像非常简单。运行以下命令:


docker build . -t <your_docker_repo>/reactive-vertx-native -f Dockerfile-native
复制代码


要使用 Uber JAR 构建标准 Docker 镜像,请运行以下命令:


docker build . -t <your_docker_repo>/reactive-vertx -f Dockerfile
复制代码


成功完成两个构建后,运行命令 Docker 镜像将显示以下结果:


REPOSITORY                     TAG                 IMAGE ID            CREATED             SIZEsmoell/reactive-vertx          latest              391f944bb553        19 minutes ago      181MB<none>                         <none>              389ee5ec6a8c        19 minutes ago      411MBsmoell/reactive-vertx-native   latest              ecd72b58a3d2        25 minutes ago      133MB<none>                         <none>              d93993d1d5ab        26 minutes ago      2.89GBdebian                         9-slim              92d2f0789514        4 days ago          55.3MBoracle/graalvm-ce              1.0.0-rc16          131b80926177        2 weeks ago         1.72G
复制代码


您可以看到在构建中使用的不同基本镜像(oracle/graalvm-ce:1.0.0-rc16 和 debian:9-slim)、构建期间使用的临时映像(没有合适的名称)以及目标镜像 smoell/reactive-vertx 和 smoell/reactive-vertx-native。

小结

在本博文中,我介绍了如何通过基于 Docker 多阶段构建的自包含应用程序,使用 GraalVM 将 Java 应用程序编译为本机镜像。我还演示了如何使用 jlink 为较小的目标镜像创建自定义 JDK 分配。我们希望这些内容能为您带来一些灵感,帮助您优化现有 Java 应用程序以减少启动时间和内存消耗。


本文转载自 AWS 技术博客。


原文链接:


https://amazonaws-china.com/cn/blogs/china/using-graalvm-build-minimal-docker-images-java-applications/


2019-09-27 14:431985
用户头像

发布了 1906 篇内容, 共 146.3 次阅读, 收获喜欢 81 次。

关注

评论

发布
暂无评论
发现更多内容

在线985,211高校查询

入门小站

工具

[微服务] You built it.You fix it.

baiyutang

微服务 9月日更

Linux之常见的存储架构

在即

9月日更

p3c 插件,是怎么检查出你那屎山的代码?

小傅哥

小傅哥 代码规范 p3m pmd 开发手册

MPU:鸿蒙轻内核的任务栈的溢出检察官

华为云开发者联盟

鸿蒙 内核 任务栈 MPU 内存保护单元

maven如何忽略指定的远程仓库

小江

maven nexus 迁移 java;

百度信息流和搜索业务中的KV存储实践

百度Geek说

后端 搜索

Redis核心原理与实践--散列类型与字典结构实现原理

binecy

数据结构 源码阅读 Redis 6.0

2B 销售系统设计需要考虑的 3 个层面

boshi

团队管理 销售管理

低代码平台的功能及其用处

低代码小观

程序员 低代码 开发工具 低代码开发平台 无代码

OceanBase 源码解读(五):租户的一生

OceanBase 数据库

数据开发 oceanbase OceanBase 开源 OceanBase 社区版 OceanBase 数据库大赛

考试试卷存储设计

guangbao

数据安全与隐私系列08:大数据与电影《少数派报告》

Databri_AI

人工智能

MLOps生产中的机器学习:为什么你应该关心数据和概念漂移 易筋 ARTS 打卡 Week 67

John(易筋)

ARTS 打卡计划

自研ISP芯片背后:手机厂商的目光在影像之外

脑极体

大厂面试喜欢考算法,该怎么破?

博文视点Broadview

一文带你了解经典的Java垃圾回收机制

华为云开发者联盟

Java JVM 对象 垃圾回收机制 垃圾收集器

iPhone13全线机型上线WeTest云手机平台

WeTest

密码学系列之:1Password的加密基础PBKDF2

程序那些事

算法 加密解密 密码学 程序那些事

卷王本卷

FunTester

内卷 FunTester

两个剪辑透明化融合视频特效处理

老猿Python

Python 音视频 视频剪辑 视频特效 引航计划

回帖抽大奖——5分钟极速体验AI技术能力

百度大脑

人工智能

JavaScript “上层”语言

Augus

JavaScript 9月日更

深入理解Netty-从偶现宕机看Netty流量控制

vivo互联网技术

Java、 框架 netty

如何做到监控告警的管理?

睿象云

运维 告警 监控告警 运维平台 告警管理

带你读论文丨基于视觉匹配的自适应文本识别

华为云开发者联盟

损失函数 视觉 文本识别 文档识别 视觉匹配

什么是低代码自动化以及它如何使你受益?

低代码小观

程序员 自动化 工具 低代码 低代码开发平台

吃串串,数签签,这个AI神器一秒搞定

百度大脑

人工智能 EasyDL

天壤完成新一轮战略融资 加速构建数字化转型通用智能平台

InfoQ 天津

华为云顾炯炯:云原生应用传送网络AND的实现架构与核心技术分享

华为云开发者联盟

网络 华为云 应用传送网络 ADN 东数西算

linux之秘钥登录

入门小站

Linux

使用 GraalVM 为 Java 应用程序构建最少的 Docker 镜像_语言 & 开发_亚马逊云科技 (Amazon Web Services)_InfoQ精选文章