"コンテナファースト" Java フレームワーク Quarks を試してみた
Quarksとは
今更ぼくが紹介するまでもないと思うんですが、Redhat さん主導で開発されている新気鋭の Java フレームワークで、コンテナ環境での実行をメインターゲットにしている点が特徴ですかね。 会社で話題になっていたので試してみました。
ちょっとにわかには信じがたい性能のふれこみですね!
TL;DR
- Native 版実行コンテナを作って起動するまで
# 雛形作成 ❯ mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=getting-started \ -DclassName="org.acme.getting.started.GreetingResource" \ -Dpath="/hello" ❯ cd getting-started # install Extension ❯ ./mvnw quarkus:add-extension -Dextensions="container-image-docker" # build ❯ ./mvnw package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true # go! ❯ docker run -i --rm -p 8080:8080 ikuo.suyama/getting-started:1.0.0-SNAPSHOT
- エコシステム含め開発体験◎、実戦投入に向けてより詳細に使い込んでみたい
開発
とりあえずチュートリアルをやってみます。ドキュメントは多くがすでに日本語化されていて気合を感じますね。 ちょっと分かりづらい気もする、、とくに初学者にはつらそうです(けどおそらくそこはターゲットではなさそう
セットアップ
❯ mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=getting-started \ -DclassName="org.acme.getting.started.GreetingResource" \ -Dpath="/hello"
mvn コマンド一発で雛形と Hello world, test 一式が生成されます。 .gitignore も含まれていて、IntelliJ の設定ファイルもちゃんとかいてある!
ああちゃんと使う人のことが考えられているなぁと感じます。 細かいけどこういう配慮があるプロダクトは信頼できますよね。
起動
開発モードで起動
❯ cd getting-started
❯ ./mvnw compile quarkus:dev
: istening for transport dt_socket at address: 5005 __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-03-13 22:48:41,751 INFO [io.quarkus] (Quarkus Main Thread) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 1.098s. Listening on: http://localhost:8080 2021-03-13 22:48:41,754 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated. 2021-03-13 22:48:41,754 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy]
curlしてみます。いいですね!
❯ curl localhost:8080/hello Hello RESTEasy⏎
もちろんホットリロード対応で、変更を加えるとアプリケーションが高速にリロードされます。
コードはこんなノリです
@Path("/hello") public class GreetingResource { @Inject GreetingService service; @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "Hello RESTEasy"; } @GET @Produces(MediaType.TEXT_PLAIN) @Path("/greeting/{name}") public String greeting(@PathParam("name") String name) { return service.greeting(name); } }
サンプルはCRUDEすら無いシンプル(すぎる)アプリケーションだけど、 まんま JAX-RS だし Java で Backend やってる人はだいたいふんいきでわかるんじゃないでしょうか。
Native Build
目玉のネイティブ実行ファイルを作成してみます。
ここをなぞるだけです。 ビルド時に、native プロパティを指定すると GraalVM なネイティブ実行ファイルが作成できます。簡単。
❯ ./mvnw package -Pnative
めんどうくさそうなところは飛ばして、とりあえず実行すると当然死にます。
[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:1.12.2.Final:build (default) on project getting-started: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors [ERROR] [error]: Build step io.quarkus.deployment.pkg.steps.NativeImageBuildStep#build threw an exception: java.lang.RuntimeException: Cannot find the `native-image` in the GRAALVM_HOME, JAVA_HOME and System PATH. Install it using `gu install native-image` [ERROR] at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.getNativeImageBuildRunner(NativeImageBuildStep.java:306) [ERROR] at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:102) [ERROR] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [ERROR] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) [ERROR] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [ERROR] at java.base/java.lang.reflect.Method.invoke(Method.java:566) [ERROR] at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:920) [ERROR] at io.quarkus.builder.BuildContext.run(BuildContext.java:277) [ERROR] at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415) [ERROR] at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452) [ERROR] at java.base/java.lang.Thread.run(Thread.java:829) [ERROR] at org.jboss.threads.JBossThread.run(JBossThread.java:501) [ERROR] -> [Help 1] :
ふむ。GraalVM をインストールしてないからですね。それはそうだ。ドキュメント読みましょう。でもインストールするのは面倒くさいです。
どうやら GraalVM をインストールしなくても、ビルド用イメージを引っ張ってきてコンテナ内でビルドしてくれるオプションがあるみたいです。すばらしい。 もちろんMacOS用の実行ファイルにはならないですけど。
❯ ./mvnw package -Pnative -Dquarkus.native.container-build=true
今度は違うエラーで死。OOMだそう。
[INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 31.431 s [INFO] Finished at: 2021-03-13T23:19:52+09:00 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:1.12.2.Final:build (default) on project getting-started: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors [ERROR] [error]: Build step io.quarkus.deployment.pkg.steps.NativeImageBuildStep#build threw an exception: java.lang.RuntimeException: Failed to build native image [ERROR] at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:282) [ERROR] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [ERROR] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) [ERROR] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [ERROR] at java.base/java.lang.reflect.Method.invoke(Method.java:566) [ERROR] at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:920) [ERROR] at io.quarkus.builder.BuildContext.run(BuildContext.java:277) [ERROR] at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2415) [ERROR] at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452) [ERROR] at java.base/java.lang.Thread.run(Thread.java:829) [ERROR] at org.jboss.threads.JBossThread.run(JBossThread.java:501) [ERROR] Caused by: java.lang.RuntimeException: Image generation failed. Exit code was 137 which indicates an out of memory error. Consider increasing the Xmx value for native image generation by setting the "quarkus.native.native-image-xmx" property [ERROR] at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.imageGenerationFailed(NativeImageBuildStep.java:439) [ERROR] at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:254) [ERROR] ... 10 more [ERROR] -> [Help 1] :
quarkus.native.native-image-xmx
を指定するといいよ、とあるけど、追加しても同様。
いま Docker コンテナ内でビルドしてるはずなので、 Docker 側の設定の問題じゃないかと踏んで確認してみたら、案の定何故か Docker のメモリ制限が 2G とかになっていました。
アップグレードされた際にリセットされてしまったのかな。とにかく適当に大きめに設定します。
: [INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 -J-Duser.language=ja -J-Duser.country=JP -J-Dfile.encoding=UTF-8 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy\$BySpaceAndTime -H:+JNI -H:+AllowFoldMethods -jar getting-started-1.0.0-SNAPSHOT-runner.jar -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:EnableURLProtocols=http --no-server -H:-UseServiceLoaderFeature -H:+StackTrace getting-started-1.0.0-SNAPSHOT-runner [getting-started-1.0.0-SNAPSHOT-runner:26] classlist: 4,692.18 ms, 0.96 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (cap): 538.10 ms, 0.96 GB [getting-started-1.0.0-SNAPSHOT-runner:26] setup: 2,192.05 ms, 0.96 GB 14:22:21,626 INFO [org.jbo.threads] JBoss Threads version 3.2.0.Final [getting-started-1.0.0-SNAPSHOT-runner:26] (clinit): 528.50 ms, 1.95 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (typeflow): 9,915.57 ms, 1.95 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (objects): 9,587.44 ms, 1.95 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (features): 519.44 ms, 1.95 GB [getting-started-1.0.0-SNAPSHOT-runner:26] analysis: 21,527.90 ms, 1.95 GB [getting-started-1.0.0-SNAPSHOT-runner:26] universe: 1,069.14 ms, 2.67 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (parse): 2,067.55 ms, 2.67 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (inline): 3,448.59 ms, 3.37 GB [getting-started-1.0.0-SNAPSHOT-runner:26] (compile): 13,496.82 ms, 4.52 GB [getting-started-1.0.0-SNAPSHOT-runner:26] compile: 20,469.25 ms, 4.52 GB [getting-started-1.0.0-SNAPSHOT-runner:26] image: 2,609.89 ms, 4.52 GB [getting-started-1.0.0-SNAPSHOT-runner:26] write: 1,690.58 ms, 4.52 GB [getting-started-1.0.0-SNAPSHOT-runner:26] [total]: 54,532.52 ms, 4.52 GB [WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] objcopy executable not found in PATH. Debug symbols will not be separated from executable. [WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] That will result in a larger native image with debug symbols embedded in it. [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 60944ms [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:07 min [INFO] Finished at: 2021-03-13T23:23:01+09:00
はいはい、今度は成功しました。メモリは5Gくらい食ったみたいですね。
いつもどおり target/
下に実行ファイルができています。
❯ ll target total 57632 drwxr-xr-x 14 ikuo.suyama staff 448B 3 13 23:38 ./ drwxr-xr-x 14 ikuo.suyama staff 448B 3 13 23:42 ../ -rwxr-xr-x 1 ikuo.suyama staff 28M 3 13 23:23 getting-started-1.0.0-SNAPSHOT-runner*
... 28MB??
いやいやご冗談でしょう。。Javaですよ?いくら Hello World だけとはいえ。 linux 用にビルドしたものなので当然Macでは動きませんけど。。
Image作成
せっかくなので Docker Image も作成してみましょう。 package に -Dquarkus.container-image.build=true
オプションが用意されていてスムーズです。
Document が少し分かりづらいけど、今は3通り Image ビルドする方法が用意されています。
どれかのエクステンションは追加しないと Image ビルドできないみたいですね。 こんなWARNが出ていることに気づかず、Image が生成されないなーとしばらく悩みました。
2021-03-13 23:33:07,289 WARN [io.qua.config] (main) Unrecognized configuration key "quarkus.container-image.build" was provided; it will be ignored; verify that the dependency extension for this configuration is set or you did not make a typo
とりあえずDockerでビルドすることにして、quarkus-container-image-docker
エクステンションを追加します。
❯ ./mvnw quarkus:add-extension -Dextensions="container-image-docker"
Native 版
ビルドするコマンドは以下。
❯ ./mvnw package -Pnative -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true
できました!
❯ docker images | grep getting-started ikuo.suyama/getting-started 1.0.0-SNAPSHOT 4e99006a3194 25 seconds ago 132MB
132MB! いやー小さいですね。Golang とかに比べたらまだ大きいのかも知れませんけど... ちなみに BaseImage には Redhat さんの ubi ってやつが使われてるみたいです。38MBくらい。
起動してみましょう。
※ グループ名は自分のものに置き換えてください。ビルド時のオプションで指定もできます。
❯ docker run -i --rm -p 8080:8080 ikuo.suyama/getting-started:1.0.0-SNAPSHOT __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-03-13 15:20:51,266 INFO [io.quarkus] (main) getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 1.12.2.Final) started in 0.017s. Listening on: http://0.0.0.0:8080 2021-03-13 15:20:51,266 INFO [io.quarkus] (main) Profile prod activated. 2021-03-13 15:20:51,266 INFO [io.quarkus] (main) Installed features: [cdi, resteasy]
早つ。1s かからない。いいかんじですね!
jar 版との比較
せっかくなので jar 版も作って、サイズなど比較してみましょう。
-Pnative
せずにビルドします。GraalVMがいらないので -Dquarkus.native.container-build=true
も不要です。
区別するために Image の名前を適当に変えておきます。こういうオプションがちゃんと用意されているところにも好感が持てます。
❯ ./mvnw package -Dquarkus.container-image.name=quarks-getting-started-jar
❯ d images | grep getting-started ikuo.suyama/getting-started 1.0.0-SNAPSHOT 4e99006a3194 9 minutes ago 132MB ikuo.suyama/quarks-getting-started-jar 1.0.0-SNAPSHOT c0281da22b67 46 minutes ago 380MB
やはりjarだとサイズがおおきいです。約2.8倍!いや、ネイティブ版が小さすぎるんですけど。 起動してみます。
❯ docker run -i --rm -p 8080:8080 ikuo.suyama/quarks-getting-started-jar:1.0.0-SNAPSHOT exec java -Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -XX:+ExitOnOutOfMemoryError -cp . -jar /deployments/quarkus-run.jar __ ____ __ _____ ___ __ ____ ______ --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \ --\___\_\____/_/ |_/_/|_/_/|_|\____/___/ 2021-03-13 15:27:38,805 INFO [io.quarkus] (main) getting-started 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 0.673s. Listening on: http://0.0.0.0:8080 2021-03-13 15:27:38,830 INFO [io.quarkus] (main) Profile prod activated. 2021-03-13 15:27:38,830 INFO [io.quarkus] (main) Installed features: [cdi, resteasy] ^C2021-03-13 15:27:45,104 INFO [io.quarkus] (Shutdown thread) getting-started stopped in 0.076s
java経由で起動するのでネイティブ版よりワンテンポ遅く感じます。
まとめ
ネイティブ実行ファイルの作成、コンテナイメージの作成まで非常にスムーズに実施できました。
おそらく本格的なアプリケーションを開発し始めるとそれなりに詰まるポイントがあるんでしょうけど、それでもかなり可能性を感じますね。
特にやはりGraalVMによるネイティブ実行ファイルのイメージサイズの小ささや実行速度はかなり魅力的に感じます。つぎはk8sとも組み合わせてみようかな。
実戦投入に向けて、よりディープに使い込んでみたいなと思えるプロダクトでした!