EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Javaのバッチ処理をネイティブモジュールにする

昨年インフラの刷新ではフロントエンドのサーバ群をDockerコンテナ化したのと合わせて、Javaを利用するバッチ処理もDockerコンテナ化しました。 これにより、Dockerコンテナの実行さえできればどのサーバでもバッチが実行できるようになりました。

以前に比べればバッチを実行するサーバの構築が簡単になりましたが、もっと簡単にJavaバッチ処理を実行出来ないか調べてみました。 その中で、micronautというJava言語向けのフルスタックフレームワークとGraalVMを利用することで、Javaの処理をネイティブモジュールに出来ることがわかりました。 今回はコレに挑戦してみたいと思います。

micronautの準備

SDKをダウンロードして実行してみます。まずは以下からダウンロードします。

https://micronaut.io/download.html

次に、実行するためパスを通しました。今回はPowerShellを利用しました。

Set-Item Env:Path "$Env:Path;C:\Users\xxxx\libs\micronaut-1.1.0\bin"

パージョンを確認してみます

mn -V
| Micronaut Version: 1.1.0
| JVM Version: 1.8.0_162

CLIアプリケーションを作成

以下を参考にCLIアプリケーションを作ります。

https://docs.micronaut.io/latest/guide/index.html#commandLineApps

micronaut-cli-sampleという名前でプロジェクトを作成します。 mavenでビルドを行いたいので、"--build maven"を追加しています。 また、ネイティブモジュールを作成するため、"--features graal-native-image"を追加しています。

mn create-cli-app micronaut-cli-sample --build maven --features graal-native-image

サンプルはPicocliを利用していますが、GraalVMではリフレクションに制限があるため利用しませんでした。 リフレクションを利用する場合に定義ファイルを利用する方法が以下に書かれています。

https://picocli.info/picocli-on-graalvm.html

以下のようなmainメソッドを実装した簡単な実装を用意しました。

package micronaut.cli.sample;

public class MicronautCliSampleCommand {

    public static void main(String[] args) throws Exception {
        System.out.println("Hello micronaut");
    }

}

まずjarをビルドしてみます。

mvn package

実行しています。

java -jar target/micronaut-cli-sample-0.1.jar -v
Hello micronaut

正常に実行出来ました。当たり前ですが、実行にはjavaが必要です。

バイナリ出力に挑戦

javaコマンドで実行を確認出来たので、javaに依存しないバイナリの出力に挑戦したいと思います。 以下を参考に作業を進めます。

https://docs.micronaut.io/latest/guide/index.html#graal

GraalVMを利用するにあたり、今回はDockerを利用してビルドします。 プロジェクトの直下のDockerfileを利用する設定になっていますが、この記述を参考に行かのうようにしてビルドしてみます。

docker build . -t micronaut-cli-sample

しかし、以下のエラーが発生してしまいました。

Error: Main entry point class 'target/micronaut-cli-sample-0.1-shaded.jar' not found.
Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Error: Image build request failed with exit status 1

クラスが正しく認識されていないようなので、クラスを指定するためDockerfileの4行目を以下のように変更してみましたが、エラーは変わりません。

RUN native-image --no-server -cp target/micronaut-cli-sample-*.jar -H:Class=micronaut.cli.sample.MicronautCliSampleCommand

"--verbose"オプションを付けて実行してみましたが、反映されていないようです。 そこで、エラーログをもとにDockerの4行目を以下のように書き換えて実行することで、やっとビルドが開始されました。

RUN native-image --no-server -cp target/micronaut-cli-sample-0.1.jar -H:Class=micronaut.cli.sample.MicronautCliSampleCommand

ネット上でビルドに時間がかかるという書き込みを見ていたので覚悟は下のですが、60分近くかかった挙げ句、メモリ不足で失敗してしまいました。 Docker Toolbox経由で実行したのですが、VMに割り当てた1GBのメモリでは足りないようです。4GBへ増やしたところ5分程度でビルドが完了しました。

ビルドが成功したので早速実行したみたところ、以下のエラーが発生しました。

docker run --network host micronaut-cli-sample
Exception in thread "main" com.oracle.svm.core.jdk.UnsupportedFeatureError: Accessing an URL protocol that was not enabled. The URL protocol http is supported but not enabled by default. It must be enabled by adding the --enable-url-protocols=http option to the native-image command.

Dockerfileの4行目に"--enable-url-protocols=http"オプションを追加してビルドを行います。

RUN native-image --no-server -cp target/micronaut-cli-sample-0.1.jar -H:Class=micronaut.cli.sample.MicronautCliSampleCommand --enable-url-protocols=http

実行すると、javaで実行したときど同じ結果が出力されました。

docker run --network host micronaut-cli-sample
Hello micronaut

まとめ

以上でJavaバッチ処理をネイティブモジュールにすることができました。 リフレクション以外もいくつか制約があるようですが、あらたに何か作る際は候補にしても面白いかもしれません。