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

ネイティブ・イメージは、ユーザーが自己完結型のバンドルからネイティブ実行可能ファイルをビルドできるようにする機能を提供します。通常のnative-imageビルドとは対照的に、この操作モードは入力として1つの*.nibファイルのみを取ります。このファイルには、ネイティブ実行可能ファイル(またはネイティブ共有ライブラリ)のビルドに必要なものがすべて含まれています。これは、多くの入力ファイル(JARファイル、構成ファイル、自動生成ファイル、ダウンロードしたファイル)で構成される大規模なアプリケーションを、すべてのファイルがまだ使用可能かどうかを気にせずに後で再ビルドする必要がある場合に便利です。複雑なビルドでは、後でアクセスできるかどうか保証されていない多くのライブラリをダウンロードすることがよくあります。ネイティブ・イメージ・バンドルを使用することは、ビルドに必要なすべての入力を単一のファイルにカプセル化する安全なソリューションです。

ノート: この機能は試験段階です。

目次

バンドルの作成

バンドルを作成するには、特定のnative-imageコマンドライン呼出しのその他の引数とともに--bundle-createオプションを渡します。これにより、native-imageは実際のイメージに加えて*.nibファイルを作成します。

オプションの説明は次のとおりです:

--bundle-create[=new-bundle.nib]
                      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 combined with --dry-run to only perform
                      the bundle operations without any actual image building.

たとえば、MicronautアプリケーションがMavenでビルドされている場合、--bundle-createオプションが使用されていることを確認します。そのためには、pom.xmlのプラグイン・セクションに次のものを追加する必要があります:

<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 package -Dpackaging=native-imageを実行すると、次のビルド・アーティファクトが得られます:

Finished generating 'micronautguide' in 2m 0s.

Native Image Bundles: Bundle build output written to /home/testuser/micronaut-data-jdbc-repository-maven-java/target/micronautguide.output
Native Image Bundles: Bundle written to /home/testuser/micronaut-data-jdbc-repository-maven-java/target/micronautguide.nib

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:08 min
[INFO] Finished at: 2023-03-27T15:09:36+02:00
[INFO] ------------------------------------------------------------------------

この出力は、ネイティブ実行可能ファイルmicronautguideおよびバンドルmicronautguide.nibを作成したことを示しています。バンドル・ファイルはtarget/ディレクトリに作成されます。後でネイティブ実行可能ファイルを再ビルドする必要がある場合に見つけられる安全な場所にコピーする必要があります。

バンドル・ファイルには、すべての入力ファイルと実行可能ファイル自体が含まれているため(実行可能ファイルはバンドル内で圧縮されています)、明らかに大きくなる可能性があります。バンドル内にイメージを含めると、バンドルから再ビルドされたネイティブ実行可能ファイルを元の実行可能ファイルと比較できます。micronaut-data-jdbc-repositoryの例の場合、バンドルは60.7MB(実行可能ファイルは103.4MB)です。バンドル内の内容を確認するには、jar tf *.nibを実行します:

$ jar tf micronautguide.nib
META-INF/MANIFEST.MF
META-INF/nibundle.properties
output/default/micronautguide
input/classes/cp/micronaut-core-3.8.7.jar
input/classes/cp/netty-buffer-4.1.87.Final.jar
input/classes/cp/jackson-databind-2.14.1.jar
input/classes/cp/micronaut-context-3.8.7.jar
input/classes/cp/reactive-streams-1.0.4.jar
...
input/classes/cp/netty-handler-4.1.87.Final.jar
input/classes/cp/micronaut-jdbc-4.7.2.jar
input/classes/cp/jackson-core-2.14.0.jar
input/classes/cp/micronaut-runtime-3.8.7.jar
input/classes/cp/micronautguide-0.1.jar
input/stage/build.json
input/stage/environment.json
input/stage/path_substitutions.json
input/stage/path_canonicalizations.json

ご覧のとおり、バンドルは特定のレイアウトを持つJARファイルに過ぎません。これについては次に詳しく説明します。

バンドルの隣には、出力ディレクトリtarget/micronautguide.outputもあります。これには、ネイティブ実行可能ファイルと、ビルドの一部として作成されたその他すべてのファイルが含まれています。追加の出力を生成するオプション(たとえば、デバッグ情報を生成する-g--diagnostics-mode)を指定しなかったため、実行可能ファイルのみがあります:

$ tree target/micronautguide.output
target/micronautguide.output
├── default
│   └── micronautguide
└── other

-bundle-createと-dry-runの組合せ

--bundle-createオプションの説明で述べたように、native-imageでバンドルを作成しながら、実際にはイメージ・ビルドを実行しないことも可能です。これは、バンドルをより強力なマシンに移動し、そこでイメージをビルドする場合に便利です。前述のnative-maven-plugin構成を変更して、引数<buildArg>--dry-run</buildArg>も含まれるようにします。./mvnw package -Dpackaging=native-imageの実行には数秒しかかからず、作成されるバンドルは大幅に小さくなります:

Native Image Bundles: Bundle written to /home/testuser/micronaut-data-jdbc-repository-maven-java/target/micronautguide.nib

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.267 s
[INFO] Finished at: 2023-03-27T16:33:21+02:00
[INFO] ------------------------------------------------------------------------

micronautguide.nibのファイル・サイズはわずか20MBで、実行可能ファイルは含まれていません:

$ jar tf micronautguide.nib
META-INF/MANIFEST.MF
META-INF/nibundle.properties
input/classes/cp/micronaut-core-3.8.7.jar
...

今回は、Maven出力に次のメッセージが表示されないことに注意してください:

Native Image Bundles: Bundle build output written to /home/testuser/micronaut-data-jdbc-repository-maven-java/target/micronautguide.output

実行可能ファイルは作成されないため、使用可能なバンドル・ビルド出力はありません。

バンドルを使用したビルド

ネイティブ実行可能ファイルが本番で使用され、実行時に予期しない例外がスローされる場合があると仮定します。実行可能ファイルの作成に使用されたバンドルがまだ存在するため、デバッグ・サポートを使用してその実行可能ファイルのバリアントをビルドするのは簡単です。次のように--bundle-apply=micronautguide.nibを使用します:

$ native-image --bundle-apply=micronautguide.nib -g

Native Image Bundles: Loaded Bundle from /home/testuser/micronautguide.nib
Native Image Bundles: Bundle created at 'Tuesday, March 28, 2023, 11:12:04 AM Central European Summer Time'
Native Image Bundles: Using version: '20.0.1+8' (vendor 'Oracle Corporation') on platform: 'linux-amd64'
Warning: Native Image Bundles are an experimental feature.
========================================================================================================================
GraalVM Native Image: Generating 'micronautguide' (executable)...
========================================================================================================================
...
Finished generating 'micronautguide' in 2m 16s.

Native Image Bundles: Bundle build output written to /home/testuser/micronautguide.output

このコマンドを実行すると、--bundle-applyの後に渡された追加のオプション-gを使用して実行可能ファイルが再ビルドされます。このビルドの出力は、micronautguide.outputディレクトリにあります:

micronautguide.output
micronautguide.output/other
micronautguide.output/default
micronautguide.output/default/micronautguide.debug
micronautguide.output/default/micronautguide
micronautguide.output/default/sources
micronautguide.output/default/sources/javax
micronautguide.output/default/sources/javax/smartcardio
micronautguide.output/default/sources/javax/smartcardio/TerminalFactory.java
...
micronautguide.output/default/sources/java/lang/Object.java

デバッグ情報が有効な状態で、バンドルからアプリケーションを正常に再ビルドしました。

--bundle-applyの完全なオプションのヘルプには、後で詳しく説明するより高度なユース・ケースが示されます:

--bundle-apply=some-bundle.nib
                      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 haven 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.

環境変数の取得

バンドル・サポートが追加される前は、すべての環境変数が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最適化バージョンをビルドする新しいバンドルを作成する場合です。

すでにmicronaut-data-jdbc-repositoryの例をmicronautguide.nibという名前のバンドルにビルドしているとします。そのバンドルのPGO最適化バリアントを生成するには、まず実行時にPGOプロファイリング情報を生成するネイティブ実行可能ファイルのバリアントを作成します(後で使用します):

$ native-image --bundle-apply=micronautguide.nib --pgo-instrument

次に、生成された実行可能ファイルを実行して、プロファイル情報を収集します:

$ /home/testuser/micronautguide.output/default/micronautguide

このウォークスルーに基づいて、実行中のネイティブ実行可能ファイルを使用して新しいデータベース・エントリを追加し、後でデータベース内の情報を問い合せて、実際のプロファイリング情報を取得します。完了したら、Ctrl+C (SIGTERM)を使用してMicronautアプリケーションを停止します。現在の作業ディレクトリを調べると、新しいファイルを確認できます:

$ ls -lh  *.iprof
-rw------- 1 testuser testuser 19M Mar 28 14:52 default.iprof

default.iprofファイルには、--pgo-instrumentを使用してビルドされた実行可能ファイルからMicronautアプリケーションを実行したために作成されたプロファイリング情報が含まれています。これで、既存のバンドルから新しい最適化されたバンドルを作成できます:

native-image --bundle-apply=micronautguide.nib --bundle-create=micronautguide-pgo-optimized.nib --dry-run --pgo

次に、micronautguide-pgo-optimized.nibmicronautguide.nibとどのように異なるかを確認します:

$ ls -lh *.nib
-rw-r--r-- 1 testuser testuser  20M Mar 28 11:12 micronautguide.nib
-rw-r--r-- 1 testuser testuser  23M Mar 28 15:02 micronautguide-pgo-optimized.nib

新しいバンドルが元のバンドルより3MB大きいことがわかります。この理由は、ご推測のとおり、バンドルにdefault.iprofファイルが含まれるようになったためです。ツールを使用してディレクトリを比較すると、差異を詳細に調べることができます:

目視によるバンドルの比較

ご覧のとおり、micronautguide-pgo-optimized.nibはディレクトリinput/auxiliarydefault.iprofを含み、他のファイルにも変更があります。META-INF/nibundle.propertiesinput/stage/path_substitutions.jsonおよびinput/stage/path_canonicalizations.jsonの内容については、後で説明します。ここでは、build.jsonの差分を確認します:

@@ -4,5 +4,6 @@
   "--no-fallback",
   "-H:Name=micronautguide",
   "-H:Class=example.micronaut.Application",
-  "--no-fallback"
+  "--no-fallback",
+  "--pgo"
 ]

予想どおり、新しいバンドルには、最適化されたバンドルをビルドするためにnative-imageに渡した--pgoオプションが含まれています。この新しいバンドルからネイティブ実行可能ファイルをビルドすると、PGO最適化実行可能ファイルがすぐに生成されます(ビルド出力のPGO: onを参照):

$ native-image --bundle-apply=micronautguide-pgo-optimized.nib
...
[1/8] Initializing...                                                                                    (3.9s @ 0.27GB)
 Java version: 20.0.1+8, vendor version: GraalVM EE 20.0.1+8.1
 Graal compiler: optimization level: '2', target machine: 'x86-64-v3', PGO: on
 C compiler: gcc (redhat, x86_64, 13.0.1)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 6 user-specific feature(s)
...

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

バンドル・ファイルは、明確に定義された内部レイアウトを持つ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
├── 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)
│       ├── 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
└── 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の将来のバージョンでは、バンドル・ファイルの内部構造が変更または拡張される可能性があります。バージョン管理によって、下位互換を念頭にバンドル形式を進化させることが可能になります。

input

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

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

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

output

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