これは、なにをしたくて書いたもの?
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が良いような気はしますが、どうでしょうね。
とりあえず、覚えておきましょう。