The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

构建一个原生可执行文件

本指南包括:

  • 将应用程序编译为原生可执行文件

  • 将原生可执行文件打包到一个容器中

  • 调试原生可执行文件

本指南将 入门指南 中开发的应用程序作为输入。

GraalVM

构建一个原生可执行文件需要使用GraalVM的发行版。有三个发行版:Oracle GraalVM社区版(CE)、Oracle GraalVM企业版(EE)和Mandrel。Oracle和Mandrel发行版之间的区别如下:

  • Mandrel是Oracle GraalVM CE的一个下游发行版。Mandrel的主要目标是提供一种方法来构建专门为支持Quarkus而设计的原生可执行文件。

  • Mandrel版本的代码库来自于上游的Oracle GraalVM CE代码库,只做了一些小的改动,但也有一些重要的对于Quarkus本地应用程序来说是没有必要的排除项。它们支持与Oracle GraalVM CE相同的构建原生可执行文件的能力,在功能上没有重大变化。值得注意的是,它们不包括对多语言编程的支持。之所以排除这些功能,是为了给大多数Quarkus用户提供更好的支持水平。与Oracle GraalVM CE/EE相比,这些不包括的内容也意味着Mandrel发布的软件包大小大大减小。

  • Mandrel的构建方式与Oracle GraalVM CE略有不同,它使用标准的OpenJDK项目。这意味着,Oracle往OpenJDK增加了一些小增强功能,并用于构建Oracle自己的GraalVM,但Mandrel不能从中获益。这些增强功能被省略了,因为它上游的标准OpenJDK并不管理这些特性,也无法提供保障。这一点在涉及到一致性和安全性时尤其重要。

  • Mandrel被推荐用于构建针对Linux容器化环境的原生可执行文件。这意味着,我们鼓励Mandrel用户使用容器来构建他们的原生可执行文件。如果你要为macOS构建本地可执行文件,你应该考虑使用Oracle GraalVM,因为Mandrel目前不针对这个平台。直接在裸机Linux或Windows上构建原生可执行文件是可能的,详情可参见 Mandrel READMEMandrel发行版

准备工作

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • 安装JDK 11以上版本并正确配置了 JAVA_HOME

  • Apache Maven 3.8.6

  • A working container runtime (Docker or Podman)

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 可以安装Mandrel或者GraalVM,并正确配置

  • 一个 C语言工作开发环境

  • 入门指南 中开发的应用程序代码。

支持在C语言中进行原生编译

拥有一个C语言工作开发者环境意味着什么?

  • 在Linux上,你将需要GCC,以及glibc和zlib头文件。常见发行版的例子:

    # dnf (rpm-based)
    sudo dnf install gcc glibc-devel zlib-devel libstdc++-static
    # Debian-based distributions:
    sudo apt-get install build-essential libz-dev zlib1g-dev
  • XCode在macOS上提供了必要的依赖性:

    xcode-select --install
  • 在Windows上,你将需要安装 Visual Studio 2017 Visual C++构建工具

配置GraalVM

如果你无法安装GraalVM,你可以使用多阶段Docker构建在嵌入GraalVM的Docker容器内运行Maven。本指南最后有关于如何操作的解释。

版本 22.3 是必需的。使用社区版就可以了。

  1. 如果你还没有安装 GraalVM。那么你有几个选择:

  2. 配置运行环境。将 GRAALVM_HOME 环境变量设置为GraalVM的安装目录,例如:

    export GRAALVM_HOME=$HOME/Development/graalvm/

    在macOS上(Mandrel不支持),将该变量指向 Home 子目录:

    export GRAALVM_HOME=$HOME/Development/graalvm/Contents/Home/

    在Windows上,您将需要通过控制面板来设置环境变量。

    通过scoop安装将为你做到这一点。

  3. (仅适用于Oracle GraalVM CE/EE)使用 gu install 安装 native-image 工具:

    ${GRAALVM_HOME}/bin/gu install native-image

    GraalVM以前的一些版本默认包括 native-image 工具。现在已经不是这样了,它必须在安装完GraalVM本身后作为第二步来安装。注意:一个已知的问题 在MacOS Catalina上使用GraalVM

  4. (可选)将 JAVA_HOME 环境变量设置为GraalVM的安装目录。

    export JAVA_HOME=${GRAALVM_HOME}
  5. (可选)将GraalVM bin 目录添加到路径中

    export PATH=${GRAALVM_HOME}/bin:$PATH
在MacOS Catalina上使用GraalVM的问题

GraalVM的二进制文件(尚未)对macOS Catalina进行认证,正如这个 GralVM问题 中所报告的那样。这意味着您在使用 gu 时可能会看到以下错误:

“gu” cannot be opened because the developer cannot be verified

其中一种解决方法是,使用以下命令递归删除GraalVM安装目录上的 com.apple.quarantine 扩展属性:

xattr -r -d com.apple.quarantine ${GRAALVM_HOME}/../..

解决方案

我们建议您按照下面几节的说明,一步一步地打包应用。不过您还可以直接进入完成的例子。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或者下载 存档

该解决方案位于 getting-started 目录中。

制作一个原生可执行文件

我们应用程序的原生可执行文件将包含应用程序代码、所需的库、Java API和一个缩小版的虚拟机。较小的虚拟机基础提高了应用程序的启动时间和最小的磁盘占用。

创建原生可执行文件

如果你已经从前面的教程中生成了应用程序,你可以在 pom.xml ,找到以下 profile

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
        </properties>
    </profile>
</profiles>

你可以使用 <quarkus.native.additional-build-args> 属性为 native-image 命令提供自定义选项。多个选项可以用逗号隔开。

另一种做法是在你的 application.properties ,填写 quarkus.native.additional-build-args 配置属性。

你可以在下面的 配置原生可执行文件 部分找到关于如何配置原生镜像构建过程的更多信息。

我们使用profile是因为,你很快就会看到,打包原生可执行文件需要 若干 分钟。你可以在命令行中把 -Dquarkus.package.type=native 作为一个属性,但是最好是使用一个profile,因为这可以使原生镜像测试也被运行。

使用以下方法创建一个原生可执行文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native
在Windows上打包的问题

在打包之前,必须首先初始化 Microsoft Native Tools for Visual Studio。你可以通过启动与Visual Studio Build Tools 一起安装的 x64 Native Tools Command Prompt 来做到这一点。在 x64 Native Tools Command Prompt ,你可以导航到你的项目文件夹并运行 mvnw package -Pnative

另一个解决方案是写一个脚本来完成:

cmd /c 'call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && mvn package -Pnative'

除了常规文件外,该构建还产生了 target/getting-started-1.0.0-SNAPSHOT-runner 。你可以用以下方式运行: ./target/getting-started-1.0.0-SNAPSHOT-runner

Java预览功能

依赖于预览功能的Java代码需要特别注意。为了产生一个原生可执行文件,这意味着需要将 --enable-preview 标志传递给底层的原生镜像调用。你可以这样做,用 -J 作为标志的前缀,并将其作为额外的原生构建参数传递:-Dquarkus.native.additional-build-args=-J—​enable-preview

构建纯静态原生可执行文件

纯静态原生可执行文件支持是试验性的。

On Linux it’s possible to package a native executable that doesn’t depend on any system shared library. There are some system requirements to be fulfilled and additional build arguments to be used along with the native-image invocation, a minimum is -Dquarkus.native.additional-build-args="--static","--libc=musl".

编译纯静态的二进制文件是通过静态链接 musl 而不是 glibc ,如果没有严格的测试,不应该在生产中使用。

测试原生可执行文件

制作一个原生可执行文件可能会导致一些问题,因此,建议针对以原生可执行文件运行的应用程序运行一些测试。具体原因在 测试指南 中有详细解释。

要看到 GreetingResourceIT 面向原生可执行文件运行,使用 ./mvnw verify -Pnative

$ ./mvnw verify -Pnative
...
[getting-started-1.0.0-SNAPSHOT-runner:18820]     universe:     587.26 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]      (parse):   2,247.59 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]     (inline):   1,985.70 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]    (compile):  14,922.77 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]      compile:  20,361.28 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]        image:   2,228.30 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]        write:     364.35 ms
[getting-started-1.0.0-SNAPSHOT-runner:18820]      [total]:  52,777.76 ms
[INFO]
[INFO] --- maven-failsafe-plugin:2.22.1:integration-test (default) @ getting-started ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.quickstart.GreetingResourceIT
Executing [/data/home/gsmet/git/quarkus-quickstarts/getting-started/target/getting-started-1.0.0-SNAPSHOT-runner, -Dquarkus.http.port=8081, -Dtest.url=http://localhost:8081, -Dquarkus.log.file.path=build/quarkus.log]
2019-04-15 11:33:20,348 INFO  [io.quarkus] (main) Quarkus 999-SNAPSHOT started in 0.002s. Listening on: http://[::]:8081
2019-04-15 11:33:20,348 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.387 s - in org.acme.quickstart.GreetingResourceIT
...

默认情况下,Quarkus会等待60秒的时间来启动原生镜像,如果超时则本地测试自动失败。这个持续时间可以使用 quarkus.test.wait-time 系统属性来改变。例如,要增加持续时间到300秒,使用: ./mvnw verify -Pnative -Dquarkus.test.wait-time=300

这个过程以前是用 @NativeImageTest 注释来完成的。 @NativeImageTest 正考虑被废弃掉,请采用 @QuarkusIntegrationTest ,它提供了 @NativeImageTest 的测试能力的超集。关于 @QuarkusIntegrationTest 的更多信息可以在 测试指南 中找到。

配置文件

集成测试默认使用 prod 的配置(profile) 构建运行 原生可执行文件。

可以在可执行文件 运行 测试期间覆盖配置文件的 quarkus.test.native-image-profile 属性。可以添加到 application.properties ,或将其附加到命令行,如: ./mvnw verify -Pnative -Dquarkus.test.native-image-profile=test。您的 %test. 前缀属性将在测试运行时使用。

可以使用 quarkus-profile=test 属性覆盖可执行文件 builtruns 的配置文件。例如 ./mvnw clean verify -Pnative -Dquarkus-profile=test。如果要处理特定于测试的资源,例如将测试数据导入数据库,那么这可能会很方便。

quarkus.native.resources.includes=version.txt
%test.quarkus.native.resources.includes=version.txt,import-dev.sql
%test.quarkus.hibernate-orm.database.generation=drop-and-create
%test.quarkus.hibernate-orm.sql-load-script=import-dev.sql

在前面提到的 application.properties 例子中。在JVM模式测试运行期间和原生模式测试运行期间,Hibernate ORM管理的数据库将填充测试数据。生产可执行文件将只包含 version.txt 资源,没有多余的测试数据。

使用 -Dquarkus-profile=test 构建的可执行文件不适合生产部署。它包含您的测试资源文件和设置。一旦测试完成,就必须使用默认的 prod 配置文件再次构建可执行文件。

Java预览功能

Java预览功能

依赖于预览功能的Java代码需要特别注意。为了测试一个原生可执行文件,这意味着需要将 --enable-preview 标志传递给Surefire插件。将 <argLine>--enable-preview</argLine> 添加到其 configuration 片段是一种可行方法。

作为原生可执行文件运行时排除测试

When running tests this way, the only things that actually run natively are your application endpoints, which you can only test via HTTP calls. Your test code does not actually run natively, so if you are testing code that does not call your HTTP endpoints, it’s probably not a good idea to run them as part of native tests.

如果你像我们上面建议的那样,在JVM和原生执行之间共享测试类,你可以用 @DisabledOnNativeImage 注解标记某些测试,被标注的测试只在JVM上运行。

使用 @DisabledOnIntegrationTest 还将禁用所有集成测试实例中的测试,包括在JVM模式、容器镜像和原生镜像中测试应用程序。

测试一个现有的原生可执行文件

也可以针对已经构建的原生可执行文件重新运行测试。要做到这一点,运行 ./mvnw test-compile failsafe:integration-test 。这将查找现有的原生可执行文件,并使用failsafe插件对其运行测试。

如果进程由于某种原因找不到原生镜像,或者你想测试一个已经不在目标目录中的原生镜像,你可以用 -Dnative.image.path= 系统属性指定可执行文件。

在没有安装GraalVM的情况下创建一个Linux可执行文件

在进一步行动之前,请确保有一个工作的容器运行环境(Docker或podman)。如果你在Windows上使用Docker,你应该在Docker Desktop文件共享设置中共享你的项目的驱动器,并重新启动Docker Desktop。

很多时候,人们只需要为他们的Quarkus应用程序创建一个原生Linux可执行文件(例如,为了在容器化环境中运行),并希望避免安装适当的GraalVM版本来完成这项麻烦任务(例如,在CI环境中,通常的做法是尽可能少地安装软件)。

为此,Quarkus提供了一个非常方便的方法,通过利用容器运行时(如Docker或podman)来创建一个原生Linux可执行文件。完成这项任务的最简单方法是执行:

CLI
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
Maven
./mvnw install -Dnative -DskipTests -Dquarkus.native.container-build=true
Gradle
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true

默认情况下,Quarkus会自动检测容器的运行时。如果你想显式指定择容器的运行时,你可以通过以下方式实现:

对于Docker:

CLI
quarkus build --native -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=docker
Maven
./mvnw install -Dnative -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=docker
Gradle
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=docker

对于podman:

CLI
quarkus build --native -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=podman
Maven
./mvnw install -Dnative -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=podman
Gradle
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true -Dquarkus.native.container-runtime=podman

这些是标准的Quarkus配置属性,所以如果你总是想在容器中构建,建议你把这些添加到你的 application.properties ,以避免每次都指定它们。

如果你在试图使用容器构建创建原生可执行文件时,尽管你的JAR已经成功构建,仍看到应用程序JAR出现以下无效路径错误,您很可能为容器运行时使用了一个远程守护进程。

Error: Invalid Path entry getting-started-1.0.0-SNAPSHOT-runner.jar
Caused by: java.nio.file.NoSuchFileException: /project/getting-started-1.0.0-SNAPSHOT-runner.jar

在这种情况下,使用参数 -Dquarkus.native.remote-container-build=true ,而不是 -Dquarkus.native.container-build=true

原因是通过 -Dquarkus.native.container-build=true 调用的本地构建驱动程序使用卷挂载来使 JAR 在构建容器中可用,但卷挂载对远程守护程序不起作用。远程容器构建驱动程序复制必要的文件,而不是挂载它们。请注意,即使远程驱动程序也能与本地守护进程一起工作,但在本地情况下,本地驱动程序应该是首选,因为挂载通常比复制性能更高。

使用Mandrel构建需要额外传递一个自定义的构建器镜像参数:

CLI
quarkus build --native -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17
Maven
./mvnw install -Dnative -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17
Gradle
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17

Please note that the above command points to a floating tag. It is highly recommended to use the floating tag, so that your builder image remains up-to-date and secure. If you absolutely must, you may hard-code to a specific tag (see here for available tags), but be aware that you won’t get security updates that way and it’s unsupported.

创建一个容器

使用容器-镜像扩展

到目前为止,从你的Quarkus应用程序中创建一个容器镜像的最简单方法是利用容器镜像扩展。

如果这些扩展之一是存在的,那么为原生可执行文件创建一个容器镜像基本上就是执行一个命令的问题:

./mvnw package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true
  • quarkus.native.container-build=true 允许在不安装GralVM的情况下创建一个Linux可执行文件(只有在你没有安装GralVM或者你的本地操作系统不是Linux的情况下才需要)

If you’re running a remote Docker daemon, you need to replace quarkus.native.container-build=true with quarkus.native.remote-container-build=true.

  • quarkus.container-image.build=true 指示Quarkus使用最终的应用程序工件(在本例中是原生可执行文件)创建一个容器镜像

更多细节见 容器镜像指南

手动使用微基础镜像

你可以使用Quarkus Maven插件生成的JAR在容器中运行该应用程序。然而,在本节中我们将重点讨论使用生成的原生可执行文件创建一个容器镜像。

容器化过程

使用本地GraalVM安装时,原生可执行文件的目标是您的本地操作系统(Linux、macOS、Windows等)。然而,由于容器可能不使用与操作系统相同的 可执行文件 格式,我们将指示Maven构建通过利用容器运行时( 如 this section 所述)来生成可执行文件:

产生的可执行文件将是一个64位的Linux可执行文件,所以如果您的操作系统不支持,它可能不能运行。但由于我们会把它复制到容器中,所以这不是一个问题。自动生成的项目在 src/main/docker 目录中提供了一个 Dockerfile.native-micro ,内容如下:

FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
什么是Quarkus微镜像?

Quarkus 微镜像是一个小型的容器镜像,为运行你的原生应用程序提供了正确的依赖性集合。它是基于 UBI Micro 的。这个基础镜像已经被定制为在容器中完美运行。

你可以在这里阅读更多关于UBI镜像的内容:

UBI镜像可以不受任何限制地使用。

此页 解释了当你的应用有特殊要求时,如何扩展 quarkus-micro 镜像。

然后,如果你没有删除生成的原生可执行文件,可以用以下方法构建docker镜像:

docker build -f src/main/docker/Dockerfile.native-micro -t quarkus-quickstart/getting-started .

最后,用以下方式运行:

docker run -i --rm -p 8080:8080 quarkus-quickstart/getting-started

手工使用最小基础镜像

生成的项目也在 src/main/docker 目录中提供了一个 Dockerfile.native ,内容如下:

FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

UBI最小镜像比上面提到的微镜像要大。它包含更多的实用程序,如 microdnf 软件包管理器。

使用多阶段的Docker构建

上一节向你展示了如何使用Maven或Gradle构建原生可执行文件,但这需要你先创建原生可执行文件。此外,这个原生可执行文件必须是Linux 64位可执行文件。

你可能想直接在一个容器中构建原生可执行文件,而不需要一个包含构建工具的最终容器。这种方法可以通过多阶段的Docker构建来实现:

  1. 第一阶段使用Maven或Gradle构建原生可执行文件

  2. 第二阶段是复制产生了原生可执行文件的最小镜像

Before building a container image from the Dockerfiles shown below, you need to update the default .dockerignore file, as it filters everything except the target directory. In order to build inside a container, you need to copy the src directory. Thus, edit your .dockerignore and remove the * line.

这样的多阶段构建可以通过以下方式实现:

用Maven构建的Docker文件例子:

## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17 AS build
COPY --chown=quarkus:quarkus mvnw /code/mvnw
COPY --chown=quarkus:quarkus .mvn /code/.mvn
COPY --chown=quarkus:quarkus pom.xml /code/
USER quarkus
WORKDIR /code
RUN ./mvnw -B org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline
COPY src /code/src
RUN ./mvnw package -Pnative

## Stage 2 : create the docker final image
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
COPY --from=build /code/target/*-runner /work/application

# set up permissions for user `1001`
RUN chmod 775 /work /work/application \
  && chown -R 1001 /work \
  && chmod -R "g+rwX" /work \
  && chown -R 1001:root /work

EXPOSE 8080
USER 1001

CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
这种多阶段Docker构建从主机上复制Maven包装器。Maven包装器(或Gradle包装器)是提供特定版本Maven/Gradle的一种便捷方式。它避免了用Maven和Gradle创建一个基础镜像。要在项目中配置Maven包装器,请使用: mvn -N org.apache.maven.plugins:maven-wrapper-plugin:3.1.0:wrapper

将此文件保存在 src/main/docker/Dockerfile.multistage ,因为它不包括在开始快速入门中。

用Gradle构建的Docker文件例子:

## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17 AS build
USER root
RUN microdnf install findutils
COPY --chown=quarkus:quarkus gradlew /code/gradlew
COPY --chown=quarkus:quarkus gradle /code/gradle
COPY --chown=quarkus:quarkus build.gradle /code/
COPY --chown=quarkus:quarkus settings.gradle /code/
COPY --chown=quarkus:quarkus gradle.properties /code/
USER quarkus
WORKDIR /code
COPY src /code/src
RUN ./gradlew build -Dquarkus.package.type=native

## Stage 2 : create the docker final image
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
COPY --from=build /code/build/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

如果在项目中使用Gradle,你可以使用这个Docker文件例子。将其保存在 src/main/docker/Dockerfile.multistage

docker build -f src/main/docker/Dockerfile.multistage -t quarkus-quickstart/getting-started .

最后,用以下方式运行:

docker run -i --rm -p 8080:8080 quarkus-quickstart/getting-started

如果你的原生可执行文件需要SSL支持,你可以轻松地在Docker镜像中包含必要的库。

To use Mandrel instead of GraalVM CE, update the FROM clause to: FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17 AS build.

Starting with 22.3, Mandrel does not provide a -java11 variant anymore. Use the -java17 image instead.

使用无发行版基础镜像

无发行版支持是试验性的。

如果你正在寻找小型的容器镜像,https://github.com/GoogleContainerTools/distroless[无发行] 的方法可以减少基础层的大小。 distroless 背后的想法是使用一个单一和最小的基础镜像包含所有的需求,有时甚至是应用程序本身。

Quarkus提供了一个无发行版的基础镜像,可以用于你的 Dockerfile 。你只需要复制你的应用程序,就可以了:

FROM quay.io/quarkus/quarkus-distroless-image:2.0
COPY target/*-runner /application

EXPOSE 8080
USER nonroot

CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

Quarkus provides the quay.io/quarkus/quarkus-distroless-image:2.0 image. It contains the required packages to run a native executable and is only 9Mb. Just add your application on top of this image, and you will get a tiny container image.

在没有经过严格测试的情况下,不应该在生产中使用无发行版镜像。

Build a container image from scratch

Scratch镜像的支持是试验性的。

Building fully statically linked binaries enables the usage of a scratch image containing solely the resulting native executable.

以下是基于 scratch 镜像,使用Dockerfile多阶段构建镜像的例子:

## Stage 1 : build with maven builder image with native capabilities
FROM quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17 AS build
USER root
RUN microdnf install make gcc
COPY --chown=quarkus:quarkus mvnw /code/mvnw
COPY --chown=quarkus:quarkus .mvn /code/.mvn
COPY --chown=quarkus:quarkus pom.xml /code/
RUN mkdir /musl && \
    curl -L -o musl.tar.gz https://more.musl.cc/11.2.1/x86_64-linux-musl/x86_64-linux-musl-native.tgz && \
    tar -xvzf musl.tar.gz -C /musl --strip-components 1 && \
    curl -L -o zlib.tar.gz https://www.zlib.net/zlib-1.2.13.tar.gz && \
    mkdir zlib && tar -xvzf zlib.tar.gz -C zlib --strip-components 1 && \
    cd zlib && ./configure --static --prefix=/musl && \
    make && make install && \
    cd .. && rm -rf zlib && rm -f zlib.tar.gz && rm -f musl.tar.gz
ENV PATH="/musl/bin:${PATH}"
USER quarkus
WORKDIR /code
RUN ./mvnw -B org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline
COPY src /code/src
RUN ./mvnw package -Pnative -DskipTests -Dquarkus.native.additional-build-args="--static","--libc=musl"

## Stage 2 : create the final image
FROM scratch
COPY --from=build /code/target/*-runner /application
EXPOSE 8080
ENTRYPOINT [ "/application" ]

在没有经过严格测试的情况下,不应该在生产中使用Scratch镜像。

The versions of musl and zlib may need to be updated to meet the native-image executable requirements (and UPX if you use native image compression).

Compress native images

Quarkus可以使用UPX压缩产生的原生可执行文件。更多细节见 UPX压缩文档

Java和原生镜像分开编译

在某些情况下,你可能想在独立的步骤中构建本地可执行文件。例如,在CI/CD pipeline中,你可能希望在独立步骤中生成用于原生可执行文件构建的源文件,另一个步骤才是使用这些源文件来构建原生可执行文件。对于这种情况,你可以设置 quarkus.package.type=native-sources 。这个属性会像原生编译一样( -Pnative )执行java编译,但不会触发调用GraalVM的 native-image

$ ./mvnw clean package -Dquarkus.package.type=native-sources

编译完成后,你可以在 target/native-sources 找到构建工件:

$ cd target/native-sources
$ ls
native-image.args  getting-started-1.0.0-SNAPSHOT-runner.jar lib

从上面的输出可以看出,除了生成的jar文件和相关的lib目录外,还创建了一个名为 native-image.args 的文本文件。这个文件包含了所有的参数(包括要编译的JAR的名字),以及传递给GraalVM的 native-image 命令。如果你已经安装了GraalVM,你可以通过执行以下命令开始本地编译:

$ cd target/native-source
$ native-image $(cat native-image.args)
...
$ ls
native-image.args
getting-started-1.0.0-SNAPSHOT-runner
getting-started-1.0.0-SNAPSHOT-runner.build_artifacts.txt
getting-started-1.0.0-SNAPSHOT-runner.jar

Gradle的过程是类似的。

在一个容器中运行构建过程也是可能的:

cd target/native-image
docker run \
  -it \
  --rm \
  --v $(pwd):/work (1)
  -w /work (2)
  --entrypoint bin/sh \
  quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17 \ (3)
  -c "native-image $(cat native-image.args) -J-Xmx4g" (4)
1 将主机的目录 target/native-image 挂载到容器的 /work 。因此,生成的二进制文件也将被写入这个目录。
2 将工作目录切换到 /work ,我们已经在<1>中挂载了这个目录。
3 Use the quay.io/quarkus/ubi-quarkus-graalvmce-builder-image:22.3-java17 docker image introduced in Using a multi-stage Docker build to build the native image.
4 以文件 native-image.args 的内容为参数调用 native-image 。我们还提供了一个额外的参数,将进程的最大内存限制在4G字节(这可能取决于正在构建的项目和构建它的机器)。

如果你是在Windows机器上运行,请记住,二进制文件是在Linux docker容器中创建的。因此,二进制文件在Windows主机上是无法执行的。

以下是对CI/CD管道的各个步骤的高度概述:

  1. 将执行 ./mvnw …​ 命令的步骤的输出(即目录 target/native-image )注册为构建工件,

  2. 在执行 native-image …​ 命令的步骤中需要这个工件,并且

  3. 将执行 native-image …​ 命令的步骤的输出(即匹配 target/*runner 的文件)注册为构建工件。

执行步骤 1 的环境只需要安装Java和Maven(或Gradle),而执行步骤 3 的环境只需要安装GralVM(包括 native-image 功能)。

根据CI/CD管道的最终期望输出,生成的二进制文件可能被用来创建一个容器镜像。

调试原生可执行文件

从Oracle GraalVM 20.2或Mandrel 20.1开始,可以为Linux环境生成原生可执行文件的调试符号(Windows支持仍在开发中,不支持macOS)。这些符号可用于用工具调试原生可执行文件,如 gdb

要生成调试符号,在生成原生可执行文件时添加 -Dquarkus.native.debug.enabled=true 标志。你将在原生可执行文件旁边的 .debug 文件中找到原生可执行文件的调试符号。

.debug 文件的生成取决于 objcopy 。在常见的Linux发行版上,你将需要安装 binutils 包:

# dnf (rpm-based)
sudo dnf install binutils
# Debian-based distributions
sudo apt-get install binutils

当被嵌入到可执行文件中的 objcopy 调试符号不可用时。

除了调试符号外,设置 -Dquarkus.native.debug.enabled=true 标志会生成一个源文件缓存,用于生成原生可执行文件时解决的任何JDK运行时类、GraalVM类和应用程序类。这个源码缓存对原生调试工具很有用,可以在符号和匹配的源代码之间建立联系。在调试本原生执行文件时,它提供了一种方便的方法,使调试器/IDE仅能获得必要的源文件。

第三方jar依赖的源,包括Quarkus源代码,默认情况下不会被添加到源缓存中。要包括这些,请确保你先调用 mvn dependency:sources 。这一步是必须的,以便拉出这些依赖的源代码,并将其包含在源代码缓存中。

源缓存位于 target/sources 文件夹中。

如果从与 target 不同的目录下运行 gdb ,那么可以通过运行源代码加载:

directory path/to/target

gdb 提示中。

或这样运行 gdb 命令:

gdb -ex 'directory path/to/target' path/to/target/{project.name}-{project.version}-runner

比如:

gdb -ex 'directory ./target' ./target/getting-started-1.0.0-SNAPSHOT-runner

关于调试原生镜像的更详细指南,请参考 原生参考指南

配置原生可执行文件

有很多不同的配置选项可以影响原生可执行文件的生成方式。这些都是在 application.properties ,与其他配置属性相同。

这些属性显示如下:

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

类型

默认

Comma-separated, additional arguments to pass to the build process. If an argument includes the , symbol, it needs to be escaped, e.g. \\,

Environment variable: QUARKUS_NATIVE_ADDITIONAL_BUILD_ARGS

list of string

If the HTTP url handler should be enabled, allowing you to do URL.openConnection() for HTTP URLs

Environment variable: QUARKUS_NATIVE_ENABLE_HTTP_URL_HANDLER

boolean

true

If the HTTPS url handler should be enabled, allowing you to do URL.openConnection() for HTTPS URLs

Environment variable: QUARKUS_NATIVE_ENABLE_HTTPS_URL_HANDLER

boolean

false

The default value for java.awt.headless JVM option. Switching this option affects linking of awt libraries.

Environment variable: QUARKUS_NATIVE_HEADLESS

boolean

true

Defines the file encoding as in -Dfile.encoding=…​. Native image runtime uses the host’s (i.e. build time) value of file.encoding system property. We intentionally default this to UTF-8 to avoid platform specific defaults to be picked up which can then result in inconsistent behavior in the generated native executable.

Environment variable: QUARKUS_NATIVE_FILE_ENCODING

string

UTF-8

If all character sets should be added to the native image. This increases image size

Environment variable: QUARKUS_NATIVE_ADD_ALL_CHARSETS

boolean

false

The location of the Graal distribution

Environment variable: QUARKUS_NATIVE_GRAALVM_HOME

string

${GRAALVM_HOME:}

The location of the JDK

Environment variable: QUARKUS_NATIVE_JAVA_HOME

File

${java.home}

The maximum Java heap to be used during the native image generation

Environment variable: QUARKUS_NATIVE_NATIVE_IMAGE_XMX

string

If the native image build should wait for a debugger to be attached before running. This is an advanced option and is generally only intended for those familiar with GraalVM internals

Environment variable: QUARKUS_NATIVE_DEBUG_BUILD_PROCESS

boolean

false

If the debug port should be published when building with docker and debug-build-process is true

Environment variable: QUARKUS_NATIVE_PUBLISH_DEBUG_BUILD_PROCESS_PORT

boolean

true

If isolates should be enabled

Environment variable: QUARKUS_NATIVE_ENABLE_ISOLATES

boolean

true

If a JVM based 'fallback image' should be created if native image fails. This is not recommended, as this is functionally the same as just running the application in a JVM

Environment variable: QUARKUS_NATIVE_ENABLE_FALLBACK_IMAGES

boolean

false

If all META-INF/services entries should be automatically registered

Environment variable: QUARKUS_NATIVE_AUTO_SERVICE_LOADER_REGISTRATION

boolean

false

If the bytecode of all proxies should be dumped for inspection

Environment variable: QUARKUS_NATIVE_DUMP_PROXIES

boolean

false

If this build should be done using a container runtime. Unless container-runtime is also set, docker will be used by default. If docker is not available or is an alias to podman, podman will be used instead as the default.

Environment variable: QUARKUS_NATIVE_CONTAINER_BUILD

boolean

If this build is done using a remote docker daemon.

Environment variable: QUARKUS_NATIVE_REMOTE_CONTAINER_BUILD

boolean

false

The docker image to use to do the image build. It can be one of graalvm, mandrel, or the full image path, e.g. quay.io/quarkus/ubi-quarkus-mandrel-builder-image:22.3-java17.

Environment variable: QUARKUS_NATIVE_BUILDER_IMAGE

string

${platform.quarkus.native.builder-image}

The container runtime (e.g. docker) that is used to do an image based build. If this is set then a container build is always done.

Environment variable: QUARKUS_NATIVE_CONTAINER_RUNTIME

docker, podman

Options to pass to the container runtime

Environment variable: QUARKUS_NATIVE_CONTAINER_RUNTIME_OPTIONS

list of string

Enable monitoring options that allow the VM to be inspected at run time.

Environment variable: QUARKUS_NATIVE_MONITORING

list of MonitoringOption

If full stack traces are enabled in the resulting image

Environment variable: QUARKUS_NATIVE_FULL_STACK_TRACES

boolean

true

If the reports on call paths and included packages/classes/methods should be generated

Environment variable: QUARKUS_NATIVE_ENABLE_REPORTS

boolean

false

If exceptions should be reported with a full stack trace

Environment variable: QUARKUS_NATIVE_REPORT_EXCEPTION_STACK_TRACES

boolean

true

If errors should be reported at runtime. This is a more relaxed setting, however it is not recommended as it means your application may fail at runtime if an unsupported feature is used by accident.

Environment variable: QUARKUS_NATIVE_REPORT_ERRORS_AT_RUNTIME

boolean

false

Don’t build a native image if it already exists. This is useful if you have already built an image and you want to use Quarkus to deploy it somewhere. Note that this is not able to detect if the existing image is outdated, if you have modified source or config and want a new image you must not use this flag.

Environment variable: QUARKUS_NATIVE_REUSE_EXISTING

boolean

false

A comma separated list of globs to match resource paths that should be added to the native image. Use slash (/) as a path separator on all platforms. Globs must not start with slash. By default, no resources are included. Example: Given that you have src/main/resources/ignored.png and src/main/resources/foo/selected.png in your source tree and one of your dependency JARs contains bar/some.txt file, with the following configuration quarkus.native.resources.includes = foo/**,bar/**/*.txt the files src/main/resources/foo/selected.png and bar/some.txt will be included in the native image, while src/main/resources/ignored.png will not be included. Supported glob features Feature Description * Matches a (possibly empty) sequence of characters that does not contain slash (/) ** Matches a (possibly empty) sequence of characters that may contain slash (/) ? Matches one character, but not slash [abc] Matches one character given in the bracket, but not slash [a-z] Matches one character from the range given in the bracket, but not slash [!abc] Matches one character not named in the bracket; does not match slash [a-z] Matches one character outside the range given in the bracket; does not match slash {one,two,three} Matches any of the alternating tokens separated by comma; the tokens may contain wildcards, nested alternations and ranges \ The escape character Note that there are three levels of escaping when passing this option via application.properties: . application.properties parser - MicroProfile Config list converter that splits the comma separated list - Glob parser All three levels use backslash (\) as the escaping character. So you need to use an appropriate number of backslashes depending on which level you want to escape. Note that Quarkus extensions typically include the resources they require by themselves. This option is useful in situations when the built-in functionality is not sufficient.

Environment variable: QUARKUS_NATIVE_RESOURCES_INCLUDES

list of string

A comma separated list of globs to match resource paths that should not be added to the native image. Use slash (/) as a path separator on all platforms. Globs must not start with slash. Please refer to includes for details about the glob syntax. By default, no resources are excluded. Example: Given that you have src/main/resources/red.png and src/main/resources/foo/green.png in your source tree and one of your dependency JARs contains bar/blue.png file, with the following configuration quarkus.native.resources.includes = **/*.png quarkus.native.resources.excludes = foo/**,**/green.png the resource red.png will be available in the native image while the resources foo/green.png and bar/blue.png will not be available in the native image.

Environment variable: QUARKUS_NATIVE_RESOURCES_EXCLUDES

list of string

If debug is enabled and debug symbols are generated. The symbols will be generated in a separate .debug file.

Environment variable: QUARKUS_NATIVE_DEBUG_ENABLED

boolean

false

Generate the report files for GraalVM Dashboard.

Environment variable: QUARKUS_NATIVE_ENABLE_DASHBOARD_DUMP

boolean

false

The compression level in [1, 10]. 10 means best Higher compression level requires more time to compress the executable.

Environment variable: QUARKUS_NATIVE_COMPRESSION_LEVEL

int

Allows passing extra arguments to the UPX command line (like --brute). The arguments are comma-separated. The exhaustive list of parameters can be found in https://github.com/upx/upx/blob/devel/doc/upx.pod.

Environment variable: QUARKUS_NATIVE_COMPRESSION_ADDITIONAL_ARGS

list of string

下一步做什么?

本指南介绍了如何为应用程序创建原生(二进制)可执行文件。它提供了一个具备快速启动时间和消耗更少内存的应用程序。然而,还有更多。

我们建议继续阅读 部署到Kubernetes和OpenShift