ネイティブ・イメージ・バンドル

ネイティブ・イメージは、ユーザーが自己完結型のバンドルからネイティブ実行可能ファイルをビルドできるようにする機能を提供します。通常のnative-imageビルドとは対照的に、この操作モードは入力として1つの*.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を使用したバンドルの作成

Mavenを使用してJavaアプリケーションがビルドされている場合は、ネイティブ・イメージ・ビルド構成用の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を使用したバンドルの作成

Gradleを使用してJavaアプリケーションがビルドされている場合は、ネイティブ・イメージ・ビルド構成用の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 args 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オプションに追加されたもう1つの内容は、コンテナ・イメージ内でのイメージ・ビルドの実行です。これにより、イメージ・ビルド中にnative-imageは、クラスパスまたはモジュール・パスを介して明示的に指定されていないリソースにアクセスできなくなります。

前述のMaven/Gradleネイティブ・イメージ・プラグイン構成の--bundle-create引数を、<buildArg>--bundle-create,container<buildArg>に変更します。これにより、以前と同じバンドルが作成されます。ただし、コンテナ・イメージがビルドされ、ネイティブ実行可能ファイルのビルドに使用されます。

コンテナ・イメージが新しく作成された場合は、コンテナ・ツールからのビルド出力も確認できます。コンテナ・イメージの名前は、使用されているDockerfileのハッシュです。コンテナ・イメージがすでに存在する場合は、かわりにビルド出力に次の行が表示されます。

Native Image Bundles: Reusing container image c253ca50f50b380da0e23b168349271976d57e4e.

コンテナでビルドするには、podmanまたはrootless dockerをシステムで使用可能にする必要があります。

コンテナ内でのビルドは現在、Linuxでのみサポートされています。他のOSネイティブ・イメージを使用しても、コンテナ・イメージは作成および使用されません。

イメージ・ビルドの実行に使用するコンテナ・ツールは、<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が指定されていない場合は、ここのGraalVMのOracle Linux 8コンテナ・イメージに基づくデフォルトのDockerfileが使用されます。コンテナ・イメージのビルドに最終的に使用される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.propertiesinput/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でネイティブ・イメージ・バンドルを使用し、<JDK>/bin/java -JAR [bundle-file.nib]でJARファイルとして実行できます。ランチャは、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 classpath.
                '=<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の出力は、_.output/launcher/META-INF/native-image/-agent_に書き込まれます。`update-bundle`を使用してネイティブ・イメージ・エージェント出力をバンドルに挿入する必要がある場合、ランチャには`native-image`も必要です。`update-bundle`オプションは、`native-image-agent`がアタッチされたバンドル・アプリケーションを実行した後に、`native-image --bundle-apply=.nib --bundle-create=.nib -cp .output/launcher`コマンドを実行します。

containerオプションは、コンテナ化されたイメージ・ビルドと同様の動作を実現します。ただし、唯一の例外は、この場合、アプリケーションはnative-imageではなくコンテナ内で実行されることです。すべてのバンドルには、コンテナでバンドルされたアプリケーションを実行するために使用されるDockerfileが含まれています。ただし、このDockerfileは、,dockerfile=<path-to-dockerfile>--container引数に追加することで上書きできます。

バンドル・ランチャは認識しているオプションのみを使用し、他のすべての引数はバンドルされたアプリケーションに渡されます。バンドル・ランチャで、オプションを指定せずに--を解析すると、ランチャでは引数の解析が停止します。残りのすべての引数は、バンドルされたアプリケーションにも渡されます。

バンドル・ファイルの形式

バンドル・ファイルは、明確に定義された内部レイアウトを持つ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
│   │                (e.g. 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 classpath 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には、特定のバンドル・ファイルの基になるレイアウトのバージョンを宣言する2つのプロパティがあります。バンドルでは現在次のレイアウト・バージョンが使用されています:

BundleFileVersionMajor=0
BundleFileVersionMinor=9

GraalVMの将来のバージョンでは、バンドル・ファイルの内部構造が変更または拡張される可能性があります。バージョン管理によって、下位互換を念頭にバンドル形式を進化させることが可能になります。

入力データ

このディレクトリには、native-imageビルダーに渡されるすべての入力データが含まれます。ファイルinput/stage/build.jsonは、バンドルが作成されたときにnative-imageに渡された元のコマンド行を保持します。

バンドル・ビルドで再適用しても意味がないパラメータは、すでに除外されています。次のものがあります:

ビルドに関連する環境変数の状態は、input/stage/environment.jsonに取得されます。バンドルの作成時に確認された-E引数ごとに、キーと値のペアのスナップショットがファイルに記録されます。残りのファイルpath_canonicalizations.jsonおよびpath_substitutions.jsonには、元のコマンド行引数で指定された入力ファイル・パスに基づいてnative-imageツールによって実行されたファイルパス変換のレコードが含まれています。

出力データ

バンドルのビルドの一部としてネイティブ実行可能ファイルがビルドされている場合(たとえば、dry-runオプションが使用されなかった場合)、バンドルにはoutputディレクトリもあります。これには、ビルドされた実行可能ファイルと、ビルドの一部として生成されたその他のファイルが含まれます。ほとんどの出力ファイルは、ディレクトリoutput/defaultにあります(実行可能ファイル、デバッグ情報、およびデバッグ・ソース)。実行可能ファイルがバンドル・モードでビルドされなかった場合に任意の絶対パスに書き込まれたビルダー出力ファイルは、output/otherにあります。