原生镜像捆绑包
原生镜像提供一个功能,使用户能够从自包含的捆绑包中构建原生可执行文件。与常规的native-image
构建不同,此操作模式仅将单个*.nib文件作为输入。该文件包含构建原生可执行文件(或原生共享库)所需的一切。当大型应用程序由许多输入文件(JAR 文件、配置文件、自动生成的文件、下载的文件)组成,并且需要在稍后时间重新构建时,无需担心所有文件是否仍然可用,此方法非常有用。通常,复杂的构建会涉及下载许多库,而这些库无法保证以后仍然可以访问。使用原生镜像捆绑包是将构建所需的所有输入封装到单个文件中的安全解决方案。
注意:此功能处于试验阶段。
目录 #
创建捆绑包 #
要创建捆绑包,请在特定native-image
命令行调用中传递--bundle-create
选项以及其他参数。这将导致native-image
除了实际镜像外,还会创建一个*.nib文件。
以下是选项说明
--bundle-create[=new-bundle.nib][,dry-run][,container[=<container-tool>][,dockerfile=<Dockerfile>]]
in addition to image building, create a Native Image bundle file (*.nib
file) that allows rebuilding of that image again at a later point. If a
bundle-file gets passed, the bundle will be created with the given
name. Otherwise, the bundle-file name is derived from the image name.
Note both bundle options can be extended with ",dry-run" and ",container"
* 'dry-run': only perform the bundle operations without any actual image building.
* 'container': sets up a container image for image building and performs image building
from inside that container. Requires podman or rootless docker to be installed.
If available, 'podman' is preferred and rootless 'docker' is the fallback. Specifying
one or the other as '=<container-tool>' forces the use of a specific tool.
* 'dockerfile=<Dockerfile>': Use a user provided 'Dockerfile' instead of the default based on
Oracle Linux 8 base images for GraalVM (see https://github.com/graalvm/container)
使用 Maven 创建捆绑包 #
假设一个 Java 应用程序是用 Maven 构建的,请在用于原生镜像构建配置的 Maven 插件中将--bundle-create
作为构建参数传递
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<buildArgs combine.children="append">
<buildArg>--bundle-create</buildArg>
</buildArgs>
</configuration>
</plugin>
然后,运行 Maven 包命令
./mvnw -Pnative native:compile
注意:用于 Micronaut 项目使用 Maven 创建原生可执行文件的命令是:
./mvnw package -Dpackaging=native-image
。
您将获得以下构建工件
Finished generating 'application' in 2m 0s.
Native Image Bundles: Bundle build output written to /application/target/application.output
Native Image Bundles: Bundle written to /application/target/application.nib
此输出表明您已创建一个原生可执行文件application
和一个捆绑包application.nib。捆绑包文件是在target/目录中创建的。它应该被复制到某个安全位置,以便在以后需要重新构建原生可执行文件时可以找到它。
使用 Gradle 创建捆绑包 #
假设一个 Java 应用程序是用 Gradle 构建的,请在用于原生镜像构建配置的 Gradle 插件中将--bundle-create
作为构建参数传递
graalvmNative {
binaries {
main {
buildArgs.add("--bundle-create")
}
}
}
然后,运行 Gradle 构建命令
./gradlew nativeCompile
您将获得以下构建工件
Finished generating 'application' in 2m 0s.
Native Image Bundles: Bundle build output written to /application/build/native/nativeCompile/application.output
Native Image Bundles: Bundle written to /application/build/native/nativeCompile/application.nib
此输出表明您已创建一个原生可执行文件application
和一个捆绑包application.nib。捆绑包文件是在build/native/nativeCompile/目录中创建的。
捆绑包文件和输出目录 #
显然,捆绑包文件可能很大,因为它包含所有输入文件以及可执行文件本身(可执行文件在捆绑包中被压缩)。将原生镜像包含在捆绑包中,可以将从捆绑包中重建的原生可执行文件与原始文件进行比较。
捆绑包只是一个具有特定布局的 JAR 文件。这将在下面详细说明。要查看捆绑包中的内容,请运行
jar tf application.nib
在捆绑包旁边,您还可以找到输出目录:application.output。它包含原生可执行文件以及作为构建的一部分创建的所有其他文件。由于您没有指定任何会产生额外输出的选项(例如,-g
用于生成调试信息或--diagnostics-mode
),因此那里只能找到可执行文件。
将–bundle-create与dry-run结合使用 #
如--bundle-create
选项说明中所述,也可以让native-image
构建捆绑包,但不实际创建镜像。如果用户希望将捆绑包移动到性能更强大的机器上并在那里构建镜像,这可能很有用。将 Maven / Gradle 原生镜像插件配置中的--bundle-create
参数修改为<buildArg>--bundle-create,dry-run</buildArg>
。然后构建项目只需要几秒钟,并且创建的捆绑包会小很多。例如,检查target/application.nib的内容,并注意可执行文件未包含在内
jar tf application.nib
META-INF/MANIFEST.MF
META-INF/nibundle.properties
...
注意,这次您不会在 Maven 输出中看到以下消息
Native Image Bundles: Bundle build output written to /application/target/application.output
由于未创建可执行文件,因此没有可用的捆绑包构建输出。
使用捆绑包构建 #
假设原生可执行文件在生产环境中使用,并且偶尔会在运行时抛出意外异常。由于您仍然拥有用于创建可执行文件的捆绑包,因此使用调试支持重新构建该可执行文件的变体非常简单。像这样使用--bundle-apply=application.nib
native-image --bundle-apply=application.nib -g
运行此命令后,可执行文件将使用已启用的调试信息从捆绑包中重新构建。
--bundle-apply
的完整选项帮助显示了一个更高级的用例,该用例将在后面详细讨论
--bundle-apply=some-bundle.nib[,dry-run][,container[=<container-tool>][,dockerfile=<Dockerfile>]]
an image will be built from the given bundle file with the exact same
arguments and files that have been passed to native-image originally
to create the bundle. Note that if an extra --bundle-create gets passed
after --bundle-apply, a new bundle will be written based on the given
bundle arguments plus any additional arguments that have been passed
afterwards. For example:
> native-image --bundle-apply=app.nib --bundle-create=app_dbg.nib -g
creates a new bundle app_dbg.nib based on the given app.nib bundle.
Both bundles are the same except the new one also uses the -g option.
在容器中构建 #
--bundle-create
和--bundle-apply
选项的另一个新增功能是在容器镜像中执行镜像构建。这确保在镜像构建过程中,native-image
无法访问未通过类路径或模块路径显式指定的任何资源。
将 Maven / Gradle 原生镜像插件配置中的--bundle-create
参数修改为<buildArg>--bundle-create,container<buildArg>
。这仍然会创建与之前相同的捆绑包。但是,将构建容器镜像,然后用于构建原生可执行文件。
如果容器镜像是新创建的,您还可以看到来自容器工具的构建输出。容器镜像的名称是所用 Dockerfile 的哈希值。如果容器镜像已存在,您将在构建输出中看到以下行 instead
Native Image Bundles: Reusing container image c253ca50f50b380da0e23b168349271976d57e4e.
要在容器中构建,您的系统上需要使用podman或rootless docker。
目前,仅支持在 Linux 上构建容器。使用任何其他操作系统原生镜像都不会创建和使用容器镜像。
用于运行镜像构建的容器工具可以使用<buildArg>--bundle-create,container=podman<buildArg>
或<buildArg>--bundle-create,container=docker<buildArg>
指定。如果未指定,native-image
将使用支持的工具之一。如果可用,则优先使用podman
,而rootless docker
是备用工具。
用于构建容器镜像的 Dockerfile 也可以使用--bundle-create,container,dockerfile=<path-to-dockerfile>
显式指定。如果未指定 Dockerfile,则将使用默认的 Dockerfile,该文件基于来自这里的 Oracle Linux 8 容器镜像。最终用于构建容器镜像的任何 Dockerfile 都将存储在捆绑包中。即使您不使用container
选项,native-image
也会创建一个 Dockerfile 并将其存储在捆绑包中。
除了在主机系统上创建容器镜像之外,在容器中构建不会创建任何额外的构建输出。但是,创建的捆绑包包含一些其他文件
jar tf application.nib
META-INF/MANIFEST.MF
META-INF/nibundle.properties
...
input/stage/path_substitutions.json
input/stage/path_canonicalizations.json
input/stage/build.json
input/stage/run.json
input/stage/environment.json
input/stage/Dockerfile
input/stage/container.json
捆绑包包含用于构建容器镜像的 Dockerfile,并将使用的容器工具、其版本和容器镜像的名称存储在container.json
中。例如
{
"containerTool":"podman",
"containerToolVersion":"podman version 3.4.4",
"containerImage":"c253ca50f50b380da0e23b168349271976d57e4e"
}
container
选项也可以与dry-run
结合使用,在这种情况下,native-image
既不会创建可执行文件,也不会创建容器镜像。它甚至不会检查所选容器工具是否可用。在这种情况下,container.json将被省略,或者,如果您显式指定了容器工具,则只包含containerTool字段,不包含任何其他信息。
容器化构建是粘性的,这意味着如果使用--bundle-create,container
创建了一个捆绑包,则该捆绑包将被标记为容器构建。如果您现在使用此捆绑包使用--bundle-apply
,它将自动再次在容器中构建。但是,这并不适用于执行捆绑包,默认情况下,捆绑的应用程序仍然在容器之外执行。
用于容器化构建的扩展命令行界面显示在上面--bundle-create
和--bundle-apply
的选项帮助文本中。
捕获环境变量 #
在添加捆绑包支持之前,所有环境变量都对native-image
构建器可见。这种方法不适用于捆绑包,并且对于没有捆绑包的镜像构建来说也是有问题的。假设您有一个环境变量,它从构建机器中保存敏感信息。由于原生镜像能够在构建时运行代码,该代码可以创建在运行时可用的数据,因此很容易构建一个镜像,您会不小心泄露此类变量的内容。
现在,将环境变量传递给native-image
需要显式参数。
假设用户希望在调用native-image
工具的环境中使用一个环境变量(例如,KEY_STORAGE_PATH
),在设置为在构建时初始化的类初始化器中。要允许在类初始化器(使用java.lang.System.getenv
)中访问该变量,请将选项-EKEY_STORAGE_PATH
传递给构建器。
要使环境变量在构建时可访问,请使用
-E<env-var-key>[=<env-var-value>]
allow native-image to access the given environment variable during
image build. If the optional <env-var-value> is not given, the value
of the environment variable will be taken from the environment
native-image was invoked from.
使用-E
可以与捆绑包按预期工作。使用-E
指定的任何环境变量都将在捆绑包中被捕获。对于可选的<env-var-value>
未给出的变量,捆绑包将捕获变量在创建捆绑包时所具有的值。选择前缀-E
是为了使选项看起来类似于相关的-D<java-system-property-key>=<java-system-property-value>
选项(它使 Java 系统属性在构建时可用)。
将–bundle-create和–bundle-apply结合使用 #
如使用捆绑包构建中已经提到的,可以基于现有捆绑包创建新的捆绑包。--bundle-apply
帮助消息中有一个简单的示例。如果使用现有捆绑包来创建新的捆绑包,该捆绑包构建了原始应用程序的 PGO 优化版本,那么就会出现一个更有趣的示例。
假设您已经将应用程序构建到名为application.nib的捆绑包中。要生成该捆绑包的 PGO 优化版本,首先构建原生可执行文件的变体,该变体在运行时生成 PGO 分析信息(您将在稍后使用它)
native-image --bundle-apply=application.nib --pgo-instrument
现在运行生成的执行文件,以便收集配置文件信息
./target/application
完成后,停止应用程序。
在当前工作目录中,您可以找到一个名为default.iprof的新文件。该文件包含由于您从使用--pgo-instrument
构建的可执行文件中运行应用程序而创建的性能分析信息。现在,您可以从现有捆绑包中创建一个新的优化捆绑包。
native-image --bundle-apply=application.nib --bundle-create=application-pgo-optimized.nib,dry-run --pgo
现在,看看application-pgo-optimized.nib与application.nib有什么不同。
$ ls -lh *.nib
-rw-r--r-- 1 testuser testuser 20M Mar 28 11:12 application.nib
-rw-r--r-- 1 testuser testuser 23M Mar 28 15:02 application-pgo-optimized.nib
新的捆绑包应该比原始捆绑包更大。原因,可以猜到,是因为现在捆绑包包含default.iprof文件。使用工具比较目录,您可以详细检查差异。
如您所见,application-pgo-optimized.nib在input/auxiliary目录中包含default.iprof,并且其他文件也发生了一些变化。META-INF/nibundle.properties、input/stage/path_substitutions.json和input/stage/path_canonicalizations.json的内容将在稍后解释。现在,查看build.json的差异。
@@ -4,5 +4,6 @@
"--no-fallback",
"-H:Name=application",
"-H:Class=example.com.Application",
- "--no-fallback"
+ "--no-fallback",
+ "--pgo"
正如预期的那样,新的捆绑包包含您传递给native-image
以构建优化捆绑包的--pgo
选项。从这个新的捆绑包构建一个本机可执行文件会直接生成一个 PGO 优化的可执行文件(在构建输出中看到PGO: on
)。
native-image --bundle-apply=application-pgo-optimized.nib
执行捆绑应用程序 #
如捆绑文件格式中所述,捆绑文件是一个包含用于启动捆绑应用程序的启动器的 JAR 文件。这意味着您可以将本机映像捆绑包与任何 JDK 一起使用,并像使用 JAR 文件一样使用<jdk>/bin/java -jar [bundle-file.nib]
来执行它。启动器使用存储在run.json中的命令行参数,并将input/classes/cp/和input/classes/p/中的所有 JAR 文件和目录分别添加到类路径和模块路径。
启动器还附带一个单独的命令行界面,如其帮助文本中所述。
This native image bundle can be used to launch the bundled application.
Usage: java -jar bundle-file [options] [bundle-application-options]
where options include:
--with-native-image-agent[,update-bundle[=<new-bundle-name>]]
runs the application with a native-image-agent attached
'update-bundle' adds the agents output to the bundle-files class path.
'=<new-bundle-name>' creates a new bundle with the agent output instead.
Note 'update-bundle' requires native-image to be installed
--container[=<container-tool>][,dockerfile=<Dockerfile>]
sets up a container image for execution and executes the bundled application
from inside that container. Requires podman or rootless docker to be installed.
If available, 'podman' is preferred and rootless 'docker' is the fallback. Specifying
one or the other as '=<container-tool>' forces the use of a specific tool.
'dockerfile=<Dockerfile>': Use a user provided 'Dockerfile' instead of the Dockerfile
bundled with the application
--verbose enable verbose output
--help print this help message
使用--with-native-image-agent
参数运行捆绑应用程序需要native-image-agent
库可用。native-image-agent
的输出被写入_,update-bundle
插入到捆绑包中,则启动器还需要native-image
。update-bundle
选项在使用native-image-agent
附加的捆绑应用程序执行后,执行命令native-image --bundle-apply=
container
选项实现了与容器化映像构建类似的行为。但是,唯一的例外是,在这种情况下,应用程序是在容器内部执行的,而不是native-image
。每个捆绑包都包含一个 Dockerfile,用于在容器中执行捆绑应用程序。但是,可以通过将,dockerfile=<path-to-dockerfile>
添加到--container
参数来覆盖此 Dockerfile。
捆绑包启动器只使用它知道的选项,所有其他参数都传递给捆绑应用程序。如果捆绑包启动器解析--
而没有指定选项,则启动器将停止解析参数。所有剩余的参数也传递给捆绑应用程序。
捆绑文件格式 #
捆绑文件是一个具有明确定义的内部布局的 JAR 文件。在捆绑包内部,您可以找到以下内部结构。
[bundle-file.nib]
├── META-INF
│ ├── MANIFEST.MF
│ └── nibundle.properties <- Contains build bundle version info:
│ * Bundle format version (BundleFileVersion{Major,Minor})
│ * Platform and architecture the bundle was created on
│ * GraalVM / Native-image version used for bundle creation
├── com.oracle.svm.driver.launcher <- launcher for executing the bundled application
├── input <- All information required to rebuild the image
│ ├── auxiliary <- Contains auxiliary files passed to native-image via arguments
│ │ (for example, external `config-*.json` files or PGO `*.iprof`-files)
│ ├── classes <- Contains all class-path and module-path entries passed to the builder
│ │ ├── cp
│ │ └── p
│ └── stage
│ ├── build.json <- Full native-image command line (minus --bundle options)
│ ├── container.json <- Containerization tool, tool version and container
│ │ image name (not available information is omitted)
│ ├── Dockerfile <- Dockerfile used for building the container image
│ ├── environment.json <- Environment variables used in the image build
│ ├── path_canonicalizations.json <- Record of path-canonicalizations that happened
│ │ during bundle creation for the input files
│ ├── path_substitutions.json <- Record of path-substitutions that happened
│ │ during bundle creation for the input files
│ └── run.json <- Full command line for executing the bundled application
│ (minus class path and module path)
└── output
├── default
│ ├── myimage <- Created image and other output created by the image builder
│ ├── myimage.debug
| └── sources
└── other <- Other output created by the builder (not relative to image location)
META-INF #
捆绑文件本身的布局是版本化的。META-INF/nibundle.properties中有两个属性声明给定捆绑文件基于的布局的哪个版本。捆绑包目前使用以下布局版本。
BundleFileVersionMajor=0
BundleFileVersionMinor=9
GraalVM 的未来版本可能会更改或扩展捆绑文件的内部结构。版本控制使我们能够在考虑向后兼容性的前提下发展捆绑包格式。
输入数据 #
此目录包含所有传递给native-image
构建器的输入数据。input/stage/build.json文件保存创建捆绑包时传递给native-image
的原始命令行。
在捆绑包构建中没有意义的重新应用的参数已被过滤掉。这些包括
--bundle-{create,apply}
--verbose
--dry-run
与构建相关的环境变量的状态被捕获在input/stage/environment.json中。对于创建捆绑包时看到的每个-E
参数,都会在文件中记录其键值对的快照。剩余的文件path_canonicalizations.json和path_substitutions.json包含基于原始命令行参数中指定的输入文件路径,由native-image
工具执行的文件路径转换的记录。
输出数据 #
如果在本机可执行文件构建捆绑包的过程中构建了本机可执行文件(例如,没有使用dry-run
选项),则捆绑包中也有一个output目录。它包含构建的可执行文件以及构建过程中生成的任何其他文件。大多数输出文件位于output/default目录中(可执行文件、其调试信息和调试源代码)。如果可执行文件没有在捆绑包模式下构建,则本来会写入任意绝对路径的构建器输出文件,可以在output/other中找到。