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

ネイティブイメージは、自己完結型のバンドルからネイティブ実行可能ファイルをビルドできる機能を提供します。通常の 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 オプションへのもう 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=<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.nibapplication.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.jsoninput/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 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の出力は、_.output/launcher/META-INF/native-image/-agent_に書き込まれます。ネイティブイメージエージェントの出力を`,update-bundle`でバンドルに挿入する必要がある場合、ランチャーは`native-image`も必要とします。`update-bundle`オプションは、`native-image --bundle-apply=`.nib --bundle-create=.nib -cp.output/launcher`コマンドを、native-image-agentがアタッチされたバンドルされたアプリケーションを実行した後で実行します。

containerオプションは、コンテナ化されたイメージビルドと同様の動作を実現します。ただし、唯一の例外は、この場合、アプリケーションはnative-imageではなくコンテナ内で実行されることです。すべてのバンドルには、コンテナ内でバンドルされたアプリケーションを実行するために使用されるDockerfileが含まれています。ただし、このDockerfileは、--container引数に,dockerfile=<path-to-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には、特定のバンドルファイルがどのバージョンのレイアウトに基づいているかを宣言する2つのプロパティがあります。現在、バンドルは次のレイアウトバージョンを使用しています。

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にあります。

お問い合わせ