QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

使用 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:431926
用户头像

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

关注

评论

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

加密货币可能是人类历史上最大的/富国银行报告:加密货币投资像19世纪50年代的早期淘金热财富转移

CECBC

数字货币

LeetCode题解:515. 在每个树行中找最大值,BFS,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

天下武功,唯”拆“不破| 技术人应知的创新思维模型 (4)

Alan

思维模型 技术人应知的创新思维模型 MECE 组合创新 28天写作

架构词典:工程

lidaobing

架构 工程能力

挖矿矿池系统开发详情丨挖矿矿池源码案例

系统开发咨询1357O98O718

挖矿矿池系统开发案例 旷工系统开发功能

TensorFlow2 Fashion-MNIST图像分类(二)

书豪

docker与podman的故事:一个方兴未艾,一个异军突起

晓川

甲方日常 66

句子

工作 随笔杂谈 日常

大促中为什么需要可视化监控大屏?

京东科技开发者

大数据 监控 数据可视化

Redis Sentinel-深入浅出原理和实战

Linux服务器开发

redis 中间件 底层应用开发 web服务器 Linux服务器开发

20分钟带你掌握JavaScript Promise和 Async/Await

葡萄城技术团队

Java

海量数据架构下如何保证Mycat的高可用?

冰河

分布式事务 分布式数据库 分布式存储 mycat 数据库集群

刚入职,就被各种 Code Review,真的有必要吗?

xcbeyond

方法论 研发管理 编程习惯

架构师训练营第八周作业

李日盛

算法

DolphinDB与Pandas对于大文本文件处理的性能对比

DolphinDB

数据库 pandas tsdb 数据库选择 DolphinDB

本文帮你在Unix下玩转C语言

MySQL从删库到跑路

unix C语言

合伙开公司、借款变工资 | 法庭上的CTO(7)

赵新龙

CTO 法庭上的CTO

架构师训练营第 1 期第12周作业

业哥

生产环境全链路压测建设历程之五 针对稳定性矛盾, 从目标、流程、组织体系发力

数列科技杨德华

修一座安全的广厦,庇护赛博世界的流浪者

脑极体

滴滴开源小桔棱镜:一款专注移动端操作行为的利器

滴滴技术

开源 滴滴 移动端

DeFi借贷质押系统APP开发|DeFi借贷质押软件开发

系统开发

DeFi流动性挖矿系统开发详解方案

系统开发咨询1357O98O718

defi流动性挖矿系统开发

揭开IP地址的神秘身份!!!

德胜网络-阳

SDK开发质量保障经验总结

张明云

接口 程序设计 接口测试 sdk SDK测试

架构师训练营W08作业

Geek_f06ede

TensorFlow2 Fashion-MNIST图像分类(一)

书豪

tensorflow 学习

CTO与COO联手接了公司的外包项目 | 法庭上的CTO(6)

赵新龙

CTO 法庭上的CTO

观点|发展区块链金融,长三角如何建设“四梁八柱”

CECBC

区块链

cartographer环境建立以及建图测试(详细级)

良知犹存

cartographer slam

架构师训练营第三周作业

Geek_xq

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