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