QuarkusのSpring Integrationとネイティブ実行可能ファイル作成とコンテナイメージ作成を実験

QuarkusプロジェクトにSpring Boot アプリの実装を取り込んだデモプロジェクトでいろいろ実験してみた。

ソースコード一式のzipアーカイブ

QuarkusのSpringインテグレーション機能

Quarkus - Quarkus Extension for Spring DI API

この辺を注意するといいのかもしれません。

インテグレーション機能と考えるより、移行機能と考えるのがいいかもしれない。

  • エントリポイントは不要
    • @SpringBootApplicationのクラスは削除する
  • アプリケーションコンテキストの代替品はありません
    • おそらくCDIのコンテキストへ読み替えて実現しているため
    • したがって Spring Test で実装したテストコードは移行できない……(ちゃんと調べてないから分からないけど)
  • Spring Frameworkのライブラリに対応するQuarkusプラグインが、それぞれのアノテーションをQuarkusのコンテキストへ変換している

プロジェクトの生成

Mavenコマンドで生成します。

mvn io.quarkus:quarkus-maven-plugin:1.12.2.Final:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=spring-data-jpa-web-security-demo \
    -DclassName="com.example.demo.GreetingController" \
    -Dpath="/greeting" \
    -Dextensions="resteasy,spring-web,spring-data-jpa,spring-security,quarkus-elytron-security-properties-file,resteasy-jackson,quarkus-jdbc-h2"

ネイティブ実行可能ファイルの作成

Quarkus - Building a Native Executable

nativeプロファイルでpackageフェーズを実行するとネイティブの実行可能ファイルを作成します。

実行可能ファイルはtarget/spring-data-jpa-web-security-demo-1.0.0-SNAPSHOT-runnerに作成します。

AOTコンパイルに時間がかかるのは変わらないのですが、フレームワーク自体がSpring Bootに比べて小さいため、ビルドマシンに要求するリソースは比較的低くなります。

./mvnw package -Pnative

コンテナイメージの作成

Quarkus - Container Images

コンテナイメージの作成方法が2種類あります。

  • Javaアプリケーションとしてコンテナイメージを作成
  • ネイティブ実行可能ファイルからコンテナイメージを作成

Javaアプリケーションとしてコンテナイメージを作成

Jibでコンテナイメージを作成するときはプロファイルにbuild-java-jibを指定します。

mvn package -Pbuild-java-jib

src/main/docker/Dockerfile.jvmに従ってコンテナイメージを作成するときはプロファイルにbuild-java-dockerを指定します。

mvn package -Pbuild-java-docker

作成したイメージの大きさはこれぐらいになります。ベースイメージが UBI になるため、比較的大きめ。

$ docker images com.example/spring-data-jpa-web-security-demo
REPOSITORY                                      TAG                 IMAGE ID       CREATED          SIZE
com.example/spring-data-jpa-web-security-demo   java-docker         93303798e24f   14 minutes ago   406MB
com.example/spring-data-jpa-web-security-demo   java-jib            bb8fd47095f3   15 minutes ago   225MB

ネイティブ実行可能ファイルからコンテナイメージを作成

Jibでコンテナイメージを作成するときはプロファイルにnative,build-navive-jibを指定します。

mvn package -Pnative,build-navive-jib

src/main/docker/Dockerfile.nativeに従ってコンテナイメージを作成するときはプロファイルにnative,build-navive-dockerを指定します。

mvn package -Pnative,build-navive-docker

src/main/docker/Dockerfile.native-distrolessに従ってコンテナイメージを作成するときはプロファイルにnative,build-navive-distrolessを指定します。

mvn package -Pnative,build-navive-distroless

作成したイメージの大きさはこれぐらいになります。 QuarkusのJibプラグインはベースイメージとしてregistry.access.redhat.com/ubi8/ubi-minimal(103MB)を利用するため、Dockerfileでdistrolessを使う場合に比べると少し大きくなってしまうのが分かります。

$ docker images com.example/spring-data-jpa-web-security-demo
REPOSITORY                                      TAG                 IMAGE ID       CREATED          SIZE
com.example/spring-data-jpa-web-security-demo   native-distroless   618ac1277742   7 seconds ago    87.3MB
com.example/spring-data-jpa-web-security-demo   native-docker       eae04a98e2d0   4 minutes ago    171MB
com.example/spring-data-jpa-web-security-demo   native-jib          232110e84e33   9 minutes ago    171MB

動作確認してみる。だいたいよさそうだ。

$ docker run --rm -i -p 8080:8080 com.example/spring-data-jpa-web-security-demo:java-jib
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-03-15 08:18:09,374 INFO  [io.agr.pool] (main) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary
2021-03-15 08:18:10,221 INFO  [io.quarkus] (main) spring-data-jpa-web-security-demo 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.12.2.Final) started in 2.235s. Listening on: http://0.0.0.0:8080
2021-03-15 08:18:10,222 INFO  [io.quarkus] (main) Profile prod activated.
2021-03-15 08:18:10,222 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-h2, mutiny, narayana-jta, resteasy, resteasy-jackson, security, security-properties-file, smallrye-context-propagation, spring-data-jpa, spring-di, spring-security, spring-web]

---

$ curl --user test:test -s localhost:8080/books; echo
[]

$ curl --user test:test --request POST --header 'Content-Type: application/json' -d '{"title":"test 001"}' -s localhost:8080/book; echo
{"id":1,"title":"test 001"}

$ curl --user test:test --request POST --header 'Content-Type: application/json' -d '{"title":"test 002"}' -s localhost:8080/book; echo
{"id":2,"title":"test 002"}

$ curl --user test:test -s localhost:8080/books; echo
[{"id":1,"title":"test 001"},{"id":2,"title":"test 002"}]

$ curl --user test:test --request PUT --header 'Content-Type: application/json' -d '{"title":"test 001u1"}' -s localhost:8080/book?id=1; echo
{"id":1,"title":"test 001u1"}

$ curl --user test:test --request DELETE -s localhost:8080/book?id=2; echo
2

$ curl --user test:test -s localhost:8080/books; echo
[{"id":1,"title":"test 001u1"}]

$ curl -s localhost:8080/books; echo