これは、なにをしたくて書いたもの?
Quarkusのドキュメントを見ていて、コンテナイメージを作成するExtensionができていることに気づいたので。
ちょっと試してみようかなと。
Quarkus Container Images extension
Quarkus 1.3.0.Finalで、Jib、Docker、S2Iを使ってコンテナイメージを作成するExtensionが追加されたようです。
ドキュメントはこちら。
最初に書きましたが、Jib、Docker、S2Iをサポートしていて、今回はJibとDockerを扱おうと思います。
Quarkusはプロジェクト作成時にDockerfileも作成していたはずなので、Docker Extensionは最初は位置づけが
よくわからなかったのですが、これらのExtensionは「コンテナイメージのビルドやPushを抽象化するもの」みたいですね。
環境
今回の環境は、こちら。
$ java --version openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04) OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.3 (ff8e977a158738155dc465c6a97ffaf31982d739) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-88-generic", arch: "amd64", family: "unix"
Docker Extensionのあり/なしの差を確認する
まずは、Docker Extensionを見てみましょう。
Build Container Images / Docker
比較のために、Docker extensionを使わないプロジェクトの作成を合わせて行います。
RESTEasy Reactive Extensionを使った、シンプルなプロジェクトを作成。プロジェクト名はcontainer-image-simplyと
しておきましょう。
$ mvn io.quarkus.platform:quarkus-maven-plugin:2.3.0.Final:create \
-DprojectGroupId=org.littlewings \
-DprojectArtifactId=container-image-simply \
-DprojectVersion=0.0.1-SNAPSHOT \
-Dextensions="resteasy-reactive"
Extensionと生成されるコードの種類が表示されます。
[INFO] selected extensions: - io.quarkus:quarkus-resteasy-reactive [INFO] applying codestarts... [INFO] 📚 java 🔨 maven 📦 quarkus 📝 config-properties 🔧 dockerfiles 🔧 maven-wrapper 🚀 resteasy-reactive-codestart [INFO] -----------
そのまま、Docker Extension(container-image-docker)を追加したプロジェクトも作成します。こちらのプロジェクト名は、
container-image-dockerとしておきます。
$ mvn io.quarkus.platform:quarkus-maven-plugin:2.3.0.Final:create \
-DprojectGroupId=org.littlewings \
-DprojectArtifactId=container-image-docker \
-DprojectVersion=0.0.1-SNAPSHOT \
-Dextensions="resteasy-reactive,container-image-docker"
先ほどとの差は、Extensionが追加されたくらいです。
[INFO] selected extensions: - io.quarkus:quarkus-container-image-docker - io.quarkus:quarkus-resteasy-reactive [INFO] applying codestarts... [INFO] 📚 java 🔨 maven 📦 quarkus 📝 config-properties 🔧 dockerfiles 🔧 maven-wrapper 🚀 resteasy-reactive-codestart [INFO]
それぞれ、treeで表示してみましょう。
$ tree container-image-simply
container-image-simply
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── docker
│ │ ├── Dockerfile.jvm
│ │ ├── Dockerfile.legacy-jar
│ │ ├── Dockerfile.native
│ │ └── Dockerfile.native-distroless
│ ├── java
│ │ └── org
│ │ └── littlewings
│ │ └── ReactiveGreetingResource.java
│ └── resources
│ ├── META-INF
│ │ └── resources
│ │ └── index.html
│ └── application.properties
└── test
└── java
└── org
└── littlewings
├── NativeReactiveGreetingResourceIT.java
└── ReactiveGreetingResourceTest.java
13 directories, 13 files
$ tree container-image-docker
container-image-docker
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── docker
│ │ ├── Dockerfile.jvm
│ │ ├── Dockerfile.legacy-jar
│ │ ├── Dockerfile.native
│ │ └── Dockerfile.native-distroless
│ ├── java
│ │ └── org
│ │ └── littlewings
│ │ └── ReactiveGreetingResource.java
│ └── resources
│ ├── META-INF
│ │ └── resources
│ │ └── index.html
│ └── application.properties
└── test
└── java
└── org
└── littlewings
├── NativeReactiveGreetingResourceIT.java
└── ReactiveGreetingResourceTest.java
13 directories, 13 files
差がわかりません…。
diffを見てみます。
$ diff -r container-image-simply container-image-docker diff -r container-image-simply/README.md container-image-docker/README.md 1c1 < # container-image-simply Project --- > # container-image-docker Project 46c46 < You can then execute your native executable with: `./target/container-image-simply-0.0.1-SNAPSHOT-runner` --- > You can then execute your native executable with: `./target/container-image-docker-0.0.1-SNAPSHOT-runner` diff -r container-image-simply/pom.xml container-image-docker/pom.xml 6c6 < <artifactId>container-image-simply</artifactId> --- > <artifactId>container-image-docker</artifactId> 31a32,35 > <dependency> > <groupId>io.quarkus</groupId> > <artifactId>quarkus-container-image-docker</artifactId> > </dependency> diff -r container-image-simply/src/main/docker/Dockerfile.jvm container-image-docker/src/main/docker/Dockerfile.jvm 10c10 < # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/container-image-simply-jvm . --- > # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/container-image-docker-jvm . 14c14 < # docker run -i --rm -p 8080:8080 quarkus/container-image-simply-jvm --- > # docker run -i --rm -p 8080:8080 quarkus/container-image-docker-jvm 21c21 < # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/container-image-simply-jvm --- > # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/container-image-docker-jvm diff -r container-image-simply/src/main/docker/Dockerfile.legacy-jar container-image-docker/src/main/docker/Dockerfile.legacy-jar 10c10 < # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/container-image-simply-legacy-jar . --- > # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/container-image-docker-legacy-jar . 14c14 < # docker run -i --rm -p 8080:8080 quarkus/container-image-simply-legacy-jar --- > # docker run -i --rm -p 8080:8080 quarkus/container-image-docker-legacy-jar 21c21 < # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/container-image-simply-legacy-jar --- > # docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/container-image-docker-legacy-jar diff -r container-image-simply/src/main/docker/Dockerfile.native container-image-docker/src/main/docker/Dockerfile.native 10c10 < # docker build -f src/main/docker/Dockerfile.native -t quarkus/container-image-simply . --- > # docker build -f src/main/docker/Dockerfile.native -t quarkus/container-image-docker . 14c14 < # docker run -i --rm -p 8080:8080 quarkus/container-image-simply --- > # docker run -i --rm -p 8080:8080 quarkus/container-image-docker diff -r container-image-simply/src/main/docker/Dockerfile.native-distroless container-image-docker/src/main/docker/Dockerfile.native-distroless 10c10 < # docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/container-image-simply . --- > # docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/container-image-docker . 14c14 < # docker run -i --rm -p 8080:8080 quarkus/container-image-simply --- > # docker run -i --rm -p 8080:8080 quarkus/container-image-docker diff -r container-image-simply/src/main/resources/META-INF/resources/index.html container-image-docker/src/main/resources/META-INF/resources/index.html 5c5 < <title>container-image-simply - 0.0.1-SNAPSHOT</title> --- > <title>container-image-docker - 0.0.1-SNAPSHOT</title> 147c147 < <li>ArtifactId: <code>container-image-simply</code></li> --- > <li>ArtifactId: <code>container-image-docker</code></li>
作成した時のプロジェクト名の差だけが出ていると思いきや、よく見るとpom.xmlには依存関係の差が出ています。
$ diff container-image-simply/pom.xml container-image-docker/pom.xml 6c6 < <artifactId>container-image-simply</artifactId> --- > <artifactId>container-image-docker</artifactId> 31a32,35 > <dependency> > <groupId>io.quarkus</groupId> > <artifactId>quarkus-container-image-docker</artifactId> > </dependency>
主な差はこれくらいみたいですね。
一気にまとめて書きますが、ふつうにmvn packageする分にはどちらのプロジェクトも同じ振る舞いになります。
$ cd container-image-simply # または $ cd container-image-docker $ mvn package # どちらのプロジェクトも起動は同じ $ java -jar target/quarkus-app/quarkus-run.jar
動作確認。
$ curl localhost:8080/hello Hello RESTEasy Reactive
差が出るのは、以下のようにビルドの設定をして実行した場合です。
mvn package時にコンテナイメージのビルドやPushが行われるようになります。
Build Container Images / Building
Build Container Images / Pushing
Docker Extensionを加えた方のプロジェクトで、quarkus.container-image.buildをtrueにしてmvn packageを
実行してみましょう。
$ cd container-image-docker $ mvn package -Dquarkus.container-image.build=true
すると、Quarkus Maven Pluginの挙動が代わり、Dockerイメージのビルドが始まります。
[INFO] --- quarkus-maven-plugin:2.3.0.Final:build (default) @ container-image-docker --- [INFO] [org.jboss.threads] JBoss Threads version 3.4.2.Final [INFO] [io.quarkus.container.image.docker.deployment.DockerProcessor] Building docker image for jar. [INFO] [io.quarkus.container.image.docker.deployment.DockerProcessor] Executing the following command to build docker image: 'docker build -f /path/to/container-image-docker/src/main/docker/Dockerfile.jvm -t username/container-image-docker:0.0.1-SNAPSHOT /path/to/container-image-docker'
ログからもわかりますが、docker buildがOSコマンド呼び出しで実行されます。Dockerイメージのビルド中に、
プロセスを見るとdocker buildが実行されているのが確認できますし。
今回は確認しませんが、quarkus.container-image.pushをtrueにするとDockerレジストリへのPushが行われるようです。
動作確認。
$ docker container run -it --rm -p 8080:8080 username/container-image-docker:0.0.1-SNAPSHOT $ curl localhost:8080/hello Hello RESTEasy Reactive
OKですね。
設定は、こちらを参照。Docker Extension固有のオプションと、コンテナイメージに関するExtension共通のものが
あります。
Build Container Images / Customizing / Docker Options / Configuration property
Build Container Images / Customizing / Container Image Options / Configuration property
コンテナイメージの名前とタグの名前は、デフォルトでは
OSユーザー名/Quarkusアプリメーション名:Quarkusアプリケーションバージョンとなるようです。
ログでも、こんな感じでしたね。
-t username/container-image-docker:0.0.1-SNAPSHOT
これを変更するには、quarkus.container-image.group、quarkus.container-image.nameなどを指定します。
たとえば、以下の例では
$ mvn package -Dquarkus.container-image.build=true -Dquarkus.container-image.group=my-user -Dquarkus.container-image.name=my-image -Dquarkus.container-image.tag=v1
このような指定でビルドされます。
-t my-user/my-image:v1
コンテナイメージ名やタグの他にも、Push先のDockerレジストリの設定なども行えるようです。
また、Dockerイメージ作成の際は以下のDockerfileが使われました。
src/main/docker/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/container-image-docker-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/container-image-docker-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/container-image-docker-jvm
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
&& microdnf update \
&& microdnf clean all \
&& mkdir /deployments \
&& chown 1001 /deployments \
&& chmod "g+rwX" /deployments \
&& chown 1001:root /deployments \
&& curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
&& chown 1001 /deployments/run-java.sh \
&& chmod 540 /deployments/run-java.sh \
&& echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security
# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 target/quarkus-app/*.jar /deployments/
COPY --chown=1001 target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 1001
ENTRYPOINT [ "/deployments/run-java.sh" ]
Docker Extensionのソースコードは、こちら。
実行時のExtensionはほぼなく、
あくまでビルド時が主体のExtensionのようです。
主となるDockerProcessorクラスでdockerコマンドを使っている感じですね。
で、どうしてDockerイメージのビルド時にDockerfile.jvmが選択されたのかは、こちらを参照。
ビルド方法に応じて、選択するDockerfileが切り替わるようです。
たとえば、以下のビルドコマンドだとDockerfile.legacy-jarファイルが使われます。
$ mvn package -Dquarkus.container-image.build=true -Dquarkus.package.type=legacy-jar
ネイティブイメージについても同様です。
もっとも、Dockerfile.jvmファイルとDockerfile.nativeファイルの2つについては、設定で指定できそうですけどね。
Build Container Images / Customizing / Docker Options / Configuration property
Docker Extensionについては、こんな感じでしょうか。
あと、そもそもDocker Extensionがない場合はどうやってDockerイメージをビルドするかというと、Dockerfileに
書いてありますね。以下のように、ふつうにdockerコマンドで対象のDockerfileを指定してビルドすることになります。
# 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/container-image-docker-jvm . # # Then run the container using: # # docker run -i --rm -p 8080:8080 quarkus/container-image-docker-jvm
なので、Docker Extensionを使うとdockerコマンドを直接使わなくても(裏で使われますが)Dockerイメージを
ビルド、Pushできるように抽象化できるというわけですね。
ただ、使われるDockerfile自体はDocker Extensionを使わない場合と同じなのですが。
カスタマイズは、Dockerfile自体を修正することになりますね。
Jib Extensionを使う
続いて、Jib Extensionを使ってみましょう。
Extensionにcontainer-image-jibを入れて、プロジェクトを作成。プロジェクト名は、container-image-jibとします。
$ mvn io.quarkus.platform:quarkus-maven-plugin:2.3.0.Final:create \
-DprojectGroupId=org.littlewings \
-DprojectArtifactId=container-image-jib \
-DprojectVersion=0.0.1-SNAPSHOT \
-Dextensions="resteasy-reactive,container-image-jib"
作成されるファイルは、ここまでのパターンと変わらないようです。
$ tree container-image-jib
container-image-jib
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── docker
│ │ ├── Dockerfile.jvm
│ │ ├── Dockerfile.legacy-jar
│ │ ├── Dockerfile.native
│ │ └── Dockerfile.native-distroless
│ ├── java
│ │ └── org
│ │ └── littlewings
│ │ └── ReactiveGreetingResource.java
│ └── resources
│ ├── META-INF
│ │ └── resources
│ │ └── index.html
│ └── application.properties
└── test
└── java
└── org
└── littlewings
├── NativeReactiveGreetingResourceIT.java
└── ReactiveGreetingResourceTest.java
13 directories, 13 files
プロジェクトへ移動。
$ cd container-image-jib
Maven依存関係にはquarkus-container-image-jibが追加されているところがポイントです。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-container-image-jib</artifactId> </dependency>
こちらの場合も、単純にmvn packageしただけではJARファイルができるだけです。
$ mvn package
なので、Docker Extensionの時と同じようにquarkus.container-image.buildをtrueとして実行すると、
Dockerイメージのビルドが始まります。
$ mvn package -Dquarkus.container-image.build=true
こんな感じですね。
[INFO] --- quarkus-maven-plugin:2.3.0.Final:build (default) @ container-image-jib --- [INFO] [org.jboss.threads] JBoss Threads version 3.4.2.Final [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Starting container image build [WARNING] [io.quarkus.container.image.jib.deployment.JibProcessor] Base image 'fabric8/java-alpine-openjdk11-jre' does not use a specific image digest - build may not be reproducible [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] The base image requires auth. Trying again for fabric8/java-alpine-openjdk11-jre... [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] LogEvent [level=INFO, message=Docker config auths section defines credentials for index.docker.io] [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] LogEvent [level=LIFECYCLE, message=Using credentials from Docker config ($HOME/.docker/config.json) for fabric8/java-alpine-openjdk11-jre] [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Using base image with digest: sha256:b459cc59d6c7ddc9fd52f981fc4c187f44a401f2433a1b4110810d2dd9e98a07 [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Container entrypoint set to [java, -Djava.util.logging.manager=org.jboss.logmanager.LogManager, -jar, quarkus-run.jar] [INFO] [io.quarkus.container.image.jib.deployment.JibProcessor] Created container image username/container-image-jib:0.0.1-SNAPSHOT (sha256:c33a38daaffbbd93fab7a8bcee9d26625f6d8842b6f3998b04fb95722f63debc)
このあたりから、これらのExtensionは有効にするとコンテナイメージのビルドやPushを行うことを抽象化したもので
あることがわかります。quarkus.container-image.buildやquarkus.container-image.pushを有効にした時に、
背後で動作するExtensionを追加するという感じですね。
動作確認は、省略します(Dockerイメージ名が異なるだけで、Docker Extensionの時と同じなので)。
作成されるDockerイメージの名前は、Docker Extensionの時と同じルールで決まります。
つまり、こちらの設定はコンテナイメージを作成するExtensionで共通のものということですね。
Build Container Images / Customizing / Container Image Options / Configuration property
こんな感じで、Dockerコンテナイメージ名やタグを指定できます。
$ mvn package -Dquarkus.container-image.build=true -Dquarkus.container-image.group=my-user -Dquarkus.container-image.name=my-image -Dquarkus.container-image.tag=v1
Jib Extensionに関する設定は、こちら。
Build Container Images / Customizing / Jib Options / Configuration property
たとえば、デフォルトのベースイメージはfabric8/java-alpine-openjdk11-jreですが、これを変更するには
quarkus.jib.base-jvm-imageを指定します。
※ネイティブイメージの場合は、quarkus.jib.base-native-imageです
今回は、ベースイメージをeclipse-temurin:11-jreに変更してビルドしてみます。
$ mvn package -Dquarkus.container-image.build=true -Dquarkus.jib.base-jvm-image=eclipse-temurin:11-jre
確認。
$ docker container run -it --rm -p 8080:8080 --name app --entrypoint java username/container-image-jib:0.0.1-SNAPSHOT --version openjdk 11.0.12 2021-07-20 OpenJDK Runtime Environment Temurin-11.0.12+7 (build 11.0.12+7) OpenJDK 64-Bit Server VM Temurin-11.0.12+7 (build 11.0.12+7, mixed mode)
ベースイメージがEclipse Temurinになっていることが確認できました。
その他、Dockerイメージにファイルを追加する方法や、ENTRYPOINTを変更する方法については以下に記載があります。
Build Container Images / Container Image extensions / Jib / Including extra files
Build Container Images / Container Image extensions / Jib / JVM Debugging
ソースコードについてはこちらですが、Docker Extensionと同様にdeploymentが主です。
https://github.com/quarkusio/quarkus/tree/2.3.0.Final/extensions/container-image/container-image-jib
完全に蛇足ですが。Jib Extensionの場合は、Dockerfileを使わないので極端な話Dockerfileを削除してもふつうに動きます。
$ rm -rf src/main/docker $ mvn package -Dquarkus.container-image.build=true
Jib Extensionについては、だいたいこんなところでしょうか。
オマケ
Jib ExtensioonやDocker Extensionのような、コンテナイメージに関するExtensionを複数追加している場合は、
quarkus.container-image.builderでどのExtensionを使うか指定するようです。
Build Container Images / Selecting among multiple extensions
docker、jib、s2iといった指定になります。
あとは、コンテナイメージに関するExtensionの共通部分のソースコードはこちらです。
https://github.com/quarkusio/quarkus/tree/2.3.0.Final/extensions/container-image/deployment
https://github.com/quarkusio/quarkus/tree/2.3.0.Final/extensions/container-image/runtime
主は、やっぱりdeploymentですけどね。
まとめ
Quarkusのコンテナイメージに関するExtensionのうち、Docker ExtensionとJib Extensionを試してみました。
簡単にコンテナイメージを作りたかったらJib Extensionが良いような気はしますが、どうでしょうね。
とりあえず、覚えておきましょう。