写点什么

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

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

关注

评论

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

聊聊Vuex原理

yyds2026

Vue

解读Vue3模板编译优化

yyds2026

Vue

企业如何低成本快速搭建团队知识库,实现企业知识管理?

Baklib

我对软件工程的理解

老张

软件工程 质量保障

StoneDB 团队成员与 MySQL 之父 Monty 会面,共话未来数据库形态

StoneDB

MySQL 国产数据库 HTAP StoneDB 10月月更

快手 RocketMQ 高性能实践

阿里巴巴云原生

阿里云 RocketMQ 云原生

【直播回顾】OpenHarmony知识赋能第八期:手把手教你实现涂鸦小游戏

OpenHarmony开发者

OpenHarmony

量化自动套利分红机器人系统开发(成熟技术)

开发微hkkf5566

MobPush Android常见问题

MobTech袤博科技

android

Apache RocketMQ 5.0 在Stream场景的存储增强

阿里巴巴云原生

阿里云 云原生 Apache RocketMQ

Apache SeaTunnel(Incubating) 2.2.0-beta 版本发布!API 重构,连接器与引擎解偶

Apache SeaTunnel

数据同步 Seatunnel 版本发布 数据集成平台 新版本/特性发布

展示出你的创意,知识库搭建也可以这样玩!

Baklib

vue的几个提效技巧

yyds2026

Vue

RocketMQ Streams在云安全及 IoT 场景下的大规模最佳实践

阿里巴巴云原生

阿里云 云原生 Apache RocketMQ

如何搭建知识库网页?

Baklib

强引用、软引用、弱引用、幻象引用的区别

zarmnosaj

10月月更

Flowable 设置任务处理人的四种方式

江南一点雨

Java springboot flowable JavaEE

5分钟带您了解DSL、以太网线缆和光纤之间的区别,值得收藏!

wljslmz

dsl 光纤 以太网 10月月更

全彩LED显示屏在单位形象的作用

Dylan

LED显示屏 全彩LED显示屏 led显示屏厂家

如何制作企业在线产品手册?这里有一些简单的方法!

Baklib

软件测试 | 测试开发 | 测试过程中遇到的那些奇葩bug

测吧(北京)科技有限公司

测试

顶级理解!阿里这份Github星标63.7K的Redis高级笔记简直不要太细

程序知音

Java 数据库 redis 架构 后端技术

我奋斗了 18 年才和你坐在一起喝咖啡

宇宙之一粟

Containerization and Its Benefits - Defining and Exploring

Mahipal_Nehra

container Docker Swarm tools APP开发 web 容器

epoll的实现原理

C++后台开发

数据结构 后台开发 linux开发 epoll C++开发

深度探讨react-hooks实现原理

xiaofeng

React

企业级MQTT物联网接入平台EMQX正式上线VMware Marketplace

EMQ映云科技

物联网 IoT emqx 10月月更 VMware Marketplace

详解webpack构建优化

Geek_02d948

webpack

KunlunBase功能体验范例

KunlunBase昆仑数据库

MySQL 数据库

NFTScan 是什么?

NFT Research

区块链 NFT 多链 数据基础设施

放弃 SpringCloud Gateway!Apache APISIX 在「还呗」业务中的技术实践

API7.ai 技术团队

spring-cloud SpringCloud Gateway APISIX 网关 开源、

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