Quarkusアプリケーションをコンテナー化する方法
はじめに
Quarkus in Actionのサンプルアプリケーションを使ってQuarkusの機能を紹介するシリーズの3回目です。
第1回 SDKMANを使ってQuarkusを学習するための環境を整備する
第2回 GraalVMを使ってQuarkusアプリケーションからネイティブイメージを生成する
第3回目の今回はQuarkusアプリケーションをコンテナーで動かすための方法について説明します(Quarks in ActionのSection 2.6 あたりです)。
Quarkusは、最初からKubernetes上で動かすことが意図されているので、コンテナー化が簡単にできるようになっています。コンテナー内でネイティブイメージを直接実行すればJavaのランタイムを使わないのでアプリケーションを起動が早くなります。
インストール環境の前提
動作環境としてはLinuxを想定しています。Fedora 41とUbuntu 24.10で動作することを確認済みです。SDKMANで以下のJavaツールがインストール済みであることを前提にしています。
$ sdk current
Using:
java: 21.0.5-tem
maven: 3.9.9
quarkus: 3.17.7
これらのLinux環境でdockerやpodmanコマンドがインストール済みであれば、GraalVMが環境にインストールされていなくても、ネイティブイメージを生成することができます。
今回もコマンドの実行ログの一部を参考例として添付します。実行環境はFedora 41を使いました。実行環境の詳細は前回の記事を見てください。
サンプルアプリケーション
前回同様、Quarkus CLIを使ってREST API実装の雛形を生成し、これにもとづいてコンテナーイメージを生成していきます。
$ quarkus create app org.acme:quarkus-in-action --extension quarkus-rest
Quarkusが生成するJARの形式
QuarkusをビルドとするとJARまたはネイティブイメージを生成することができますので、アプリケーションをコンテナーで実行するときは、以下の2択になります。
コンテナー内で、Javaランタイムを使ってJARファイルを実行する。
コンテナー内で、ネイティブイメージを直接実行する。
3種類のJAR形式
Quarkusを使った場合、実はJARファイルの形式にはいくつかの種類が存在します。Quarkusは以下の3種類の形式のJARを生成することができます(デフォルトはfast-jar)。これらはビルド時の設定によって指定可能です。
fast-jar: Quarkus およびデフォルトの設定オプション用に最適化された JAR ファイル。その結果、起動時間がわずかに短縮され、メモリー使用量がわずかに減少します。
legacy-jar: 典型的な JAR ファイル。
uber-jar: 単一のスタンドアロン JAR ファイル。
legacy-jarやuber-jarはJavaでよく知られているJAR形式ですが、fast-jarは馴染みのない方が多いと思いますので少し補足します。
Fast-jar のディレクトリ構造
fast-jarは、以下のようにtargetの下のquarkus-appディレクトリに依存関係のあるJARファイルが展開されています。JARの中にさらにJARを含むような入れ子の構造を避けたり、起動時に必要なJARだけを読み込むようにClassloaderを工夫することで起動が早くなっているのです。
$ tree -L 2 target/quarkus-app/
target/quarkus-app/
├── app
│ └── quarkus-in-action-1.0.0-SNAPSHOT.jar
├── lib
│ ├── boot
│ └── main
├── quarkus
│ ├── generated-bytecode.jar
│ ├── quarkus-application.dat
│ └── transformed-bytecode.jar
├── quarkus-app-dependencies.txt
└── quarkus-run.jar
uber-jarのように単一のJARファイルに詰め込まれている訳ではないので、アプリケーションをデプロイするときにはこのquarkus-appディレクトリごと扱う必要があります。後述するように、コンテナーイメージを作るときには、これらのディレクトリをイメージにコピーします。
fast-jarを起動するときには、以下のようにquarkus-appの下のquarkus-run.jarが起点になります。
java -jar target/quarkus-app/quarkus-run.jar
Quarkusプロジェクトに含まれるDockerfileの種類
quarkus create appコマンドで生成したプロジェクトには、最初からDockerfileが含まれているので、すぐにコンテナーイメージを生成することができます。
プロジェクトには以下の4種類のDockerfileが含まれています。
$ tree quarkus-in-action/src/main/docker/
quarkus-in-action/src/main/docker/
├── Dockerfile.jvm
├── Dockerfile.legacy-jar
├── Dockerfile.native
└── Dockerfile.native-micro
0 directories, 4 files
Dockerfile.jvm: fast-jarを含むコンテナーイメージを生成
Dockerfile.legacy-jar: legacy-jarを含むコンテナーイメージを生成
Dockerfile.native: ネイティブイメージを含むコンテナーイメージを生成 (親イメージとしてubi8/ubi-minimal:8.10を指定)
Dockerfile.native-micro: ネイティブイメージを含むコンテナーイメージを生成 (親イメージとしてより軽量なquay.io/quarkus/quarkus-micro-image:2.0を指定)
Dockerfileの例 (fast-jarの場合)
コンテナーイメージのビルド方法や、コンテナーの実行方法はこれらのDockerfileのコメントとして書かれています。以下、サンプルとしてDockerfile.jvmのコマンド部分のみを抜き出してみます。
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-in-action-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/quarkus-in-action-jvm
このコマンドをそのまま実行すれば、コンテナーイメージを作成し、コンテナーを実行することができます。
次に、Dockerfile.jvmの本体を見てみましょう。親イメージとして、ubi8/openjdk-21が指定されています(つまりJDKがイメージに含まれています)。また、fast-jarをイメージ内に配置するため、COPY命令によってtarget/quarkus-app配下のファイルやディレクトリがまるごとイメージ内にコピーされていることがわかります。
FROM registry.access.redhat.com/ubi8/openjdk-21:1.20
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
Dockerfileを使ったコンテナーイメージのビルド
Dockerfile.jvmの場合
Dockerfile.jvmを使ってfast-jarを含むコンテナーイメージをビルドし、実行してみます。上のコメントでは./mvnw packageを実行していましたが、ここでは代わりにquakus buildコマンドを使ってみます。FedoraなどRed Hat系のOS上で動かす場合は、dockerコマンドではなく、podmanを使います。
$ quarkus build
$ podman build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-in-action-jvm .
podman buildの結果としてコンテナーイメージが生成されていることを確認します。quarkus/quarkus-in-action-jvmのサイズは427MBです。
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/quarkus/quarkus-in-action-jvm latest f31f61651207 18 seconds ago 427 MB
registry.access.redhat.com/ubi8/openjdk-21 1.20 e50fb251f7bd 2 months ago 410 MB
quarkus/quarkus-in-action-jvmのイメージを実行します。以下の例では、起動に0.347秒かかっています。
$ podman run -i --rm -p 8080:8080 quarkus/quarkus-in-action-jvm
<略>
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2025-01-30 14:05:21,243 INFO [io.quarkus] (main) quarkus-in-action 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.18.1) started in 0.347s. Listening on: http://0.0.0.0:8080
2025-01-30 14:05:21,245 INFO [io.quarkus] (main) Profile prod activated.
2025-01-30 14:05:21,245 INFO [io.quarkus] (main) Installed features: [cdi, rest, smallrye-context-propagation, vertx]
Dockerfile.native-microの場合
次に比較のため、Dockerfile.native-microを使って、ネイティブイメージを含む軽量なコンテナーイメージをビルドして、実行してみます。
$ quarkus build --native
$ podman build -f src/main/docker/Dockerfile.native-micro -t quarkus/quarkus-in-action-micro .
podman build --nativeの結果としてコンテナーイメージが生成されていることを確認します。quarkus/quarkus-in-action-microのサイズは81MBです。JDKを含むイメージよりだいぶ小さくなりました。
$ podman image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/quarkus/quarkus-in-action-micro latest 36f0e4620a91 43 seconds ago 81 MB
localhost/quarkus/quarkus-in-action-jvm latest f31f61651207 14 minutes ago 427 MB
quay.io/quarkus/quarkus-micro-image 2.0 f80f4bcc92c3 4 weeks ago 30.4 MB
quay.io/quarkus/ubi-quarkus-mandrel-builder-image jdk-21 2bf317620adf 4 weeks ago 1.15 GB
registry.access.redhat.com/ubi8/openjdk-21 1.20 e50fb251f7bd 2 months ago 410 MB
quarkus/quarkus-in-action-microのイメージを実行します。以下の例では、起動に0.009秒かかっています。Javaを使わずに起動しているので早いですね。
$ podman run -i --rm -p 8080:8080 quarkus/quarkus-in-action-micro
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2025-01-30 14:19:15,496 INFO [io.quarkus] (main) quarkus-in-action 1.0.0-SNAPSHOT native (powered by Quarkus 3.18.1) started in 0.009s. Listening on: http://0.0.0.0:8080
2025-01-30 14:19:15,496 INFO [io.quarkus] (main) Profile prod activated.
2025-01-30 14:19:15,496 INFO [io.quarkus] (main) Installed features: [cdi, rest, smallrye-context-propagation, vertx]
まとめ
この記事ではQuarkusアプリケーションをコンテナー化する方法について紹介しました。
quarkus create appコマンドを使うといくつかの用途の異なるDockerfileが用意されていました。これらのDockerfileを使えば、簡単にコンテナーイメージを作ることができました。
実は、quarkus image buildコマンドを使うとpodmanやdockerコマンドを使わずにコンテナーイメージを生成することができます。
# JVMを含むビルド
$ quarkus image build jib
# ネイティブイメージを含むビルド
$ quarkus image build jib --native
上のコマンドではJibというQuarkus Extensionを指定していますが、他にもDockerやPodman、BuildPack、OpenShiftといったExtensionを指定することでさまざまなコンテナーイメージのビルドが可能になります(OpenShiftを指定するとマニフェストファイルが自動生成されます)。
でも、今回説明したDockerfileの仕組みは理解しておいた方が良いです。quarkus image buildのオプションを設定することで、この記事で説明したDockerfileのパスを指定することができるからです(つまり、カスタマイズしたDockerfileも指定可能ということ)。
quarkus image buildコマンドについて説明を始めると長くなりそうです。Quarkus in Actionでは、このあたりの説明はChapter 11にありますので、また別の機会に説明しますね。