What's?
Spring BootでDockerイメージを作成する時は、Cloud Native Buildpacksを使うのが標準だと思いますが。
Jibを使って作ろうとした時に、依存関係にSpring Boot Devtoolsを含めているとちょっと困ったことになったのでその話をメモしておきます。
起こったことと解決方法
なにに困ったかというと、Spring Boot Devtoolsを依存関係に含めている状態でJibでDockerイメージを作って実行すると、Dockerコンテナの実行時にSpring Boot Devtoolsが有効になってしまうということです。
Spring Boot Devtools自体は、パッケージングしての実行時には無効になると書かれています。
Developer tools are automatically disabled when running a fully packaged application. If your application is launched from java -jar or if it is started from a special classloader, then it is considered a “production application”.
Developing with Spring Boot / Developer Tools
そもそも、プロダクション環境ではDevtoolは有効にするべきではないともされています。
This must not be done in a production environment where running devtools is a security risk.
ここで、JibのドキュメントにSpring Bootに特化した記述があるので見てみます。
Frequently Asked Questions (FAQ) / Jib CLI / How does the jar command support Spring Boot JARs?
推奨されているのは「Exploded Mode」というモードで、java -cp /app org.springframework.boot.loader.JarLauncher
という形式で実行する、と書いています。
※実際に動かすと、ちょっと違った感じでしたが…
すると、そのままだとSpring Boot Devtoolsが有効になってしまうという話です。
これを無効にするには、Dockerイメージを作成する際にSpring Boot Devtoolsのスコープをprovided
にしてパッケージングの際に含まれないようにすれば良さそうです。
もしくは、実行時に-Dspring.devtools.restart.enabled=false
と指定するかですね。
できればDockerイメージ作成の際に外しておいた方が無難かな、と思います。
というわけで、実際の動作を確認してみましょう。
環境
今回の環境は、こちらです。
$ java --version
openjdk 11.0.15 2022-04-19
OpenJDK Runtime Environment (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)
$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 11.0.15, vendor: Private Build, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-120-generic", arch: "amd64", family: "unix"
$ docker version
Client: Docker Engine - Community
Version: 20.10.17
API version: 1.41
Go version: go1.17.11
Git commit: 100c701
Built: Mon Jun 6 23:02:57 2022
OS/Arch: linux/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.17
API version: 1.41 (minimum version 1.12)
Go version: go1.17.11
Git commit: a89b842
Built: Mon Jun 6 23:01:03 2022
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.6
GitCommit: 10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1
runc:
Version: 1.1.2
GitCommit: v1.1.2-0-ga916309
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Spring Bootプロジェクトを作成して、Jibを加える
まずはSpring Bootプロジェクトを作成します。依存関係には、web
とdevtools
を含めておきました。
$ curl -s https://start.spring.io/starter.tgz \
-d bootVersion=2.7.0 \
-d javaVersion=11 \
-d name=devtools-with-jib \
-d groupId=com.example \
-d artifactId=devtools-with-jib \
-d version=0.0.1-SNAPSHOT \
-d packageName=com.example.spring \
-d dependencies=web,devtools \
-d baseDir=devtools-with-jib | tar zxvf -
プロジェクト内に移動。
$ cd devtools-with-jib
依存関係等はこちらです。
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
ここに、JibのMavenプラグインを追加します。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.2.1</version>
</plugin>
</plugins>
</build>
あとは、ソースコードを作成しましょう。
いったん、生成されたソースコードは削除しておきます。
$ rm src/main/java/com/example/spring/DevtoolsWithJibApplication.java src/test/java/com/example/spring/DevtoolsWithJibApplicationTests.java
簡単なソースコードを作成。
package com.example.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class App {
public static void main(String... args) {
SpringApplication.run(App.class, args);
}
@GetMapping
public String hello() {
return "Hello World";
}
}
確認してみる
では、Dockerイメージを作成してみます。
$ mvn package jib:dockerBuild
## 以下でも同じ
$ mvn compile jib:dockerBuild
実行。
$ docker container run -it --rm -p 8080:8080 --name app devtools-with-jib:0.0.1-SNAPSHOT
ログを見るとわかりますが、Spring Boot Devtoolsが有効になっています。
2022-06-22 15:25:21.171 INFO 1 --- [ restartedMain] com.example.spring.App : Starting App using Java 11.0.15 on 173c614374c4 with PID 1 (/app/classes started by root in /)
2022-06-22 15:25:21.177 INFO 1 --- [ restartedMain] com.example.spring.App : No active profile set, falling back to 1 default profile: "default"
2022-06-22 15:25:21.563 INFO 1 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2022-06-22 15:25:21.569 INFO 1 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2022-06-22 15:25:26.686 INFO 1 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
スレッド名も、restartedMain
ですからね。
コンテナ内の実行コマンドは、こんな感じです。
$ docker container exec -it app sh -c 'ps -ef | grep java | grep -v grep'
root 1 0 62 15:25 pts/0 00:00:39 java -cp @/app/jib-classpath-file com.example.spring.App
もちろん、ふつうに動いてはいますが。
$ curl localhost:8080
Hello World
というわけで、Spring Boot Devtoolsが含まれているのはちょっと嫌なので、これをなんとかしましょう。
Spring Boot Devtoolsのスコープを変更する
まずはJibのissueにあったとおり、Spring Boot Devtoolsのスコープを変更してみましょう。
provided
にすれば、パッケージングの際に含まれなくなることを利用したものです。
スコープをプロパティとして定義して
<properties>
<java.version>11</java.version>
<devtools.dependency.scope>runtime</devtools.dependency.scope>
</properties>
Spring Boot Devtoolsのscope
として設定。デフォルトはruntime
とします。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>${devtools.dependency.scope}</scope>
<optional>true</optional>
</dependency>
あとは、こんな感じでパッケージング時にスコープを指定。
$ mvn package jib:dockerBuild -Ddevtools.dependency.scope=provided
これで、Spring Boot Devtoolsが有効にならなくなりました。
2022-06-22 15:29:28.859 INFO 1 --- [ main] com.example.spring.App : Starting App using Java 11.0.15 on a1826ae55975 with PID 1 (/app/classes started by root in /)
2022-06-22 15:29:28.862 INFO 1 --- [ main] com.example.spring.App : No active profile set, falling back to 1 default profile: "default"
2022-06-22 15:29:30.187 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
spring.devtools.restart.enabled
プロパティをfalse
にする
もうひとつの方法は、spring.devtools.restart.enabled
プロパティをfalse
にします。
今回は、JibのMavenプラグインのjvmFlags
として指定。
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<container>
<jvmFlags>
<jvmFlag>-Dspring.devtools.restart.enabled=false</jvmFlag>
</jvmFlags>
</container>
</configuration>
</plugin>
コンテナイメージの作成。
$ mvn package jib:dockerBuild
こちらでも、先ほどと同様の結果になります。
2022-06-22 15:32:11.576 INFO 1 --- [ main] com.example.spring.App : Starting App using Java 11.0.15 on dfa3860ded97 with PID 1 (/app/classes started by root in /)
2022-06-22 15:32:11.578 INFO 1 --- [ main] com.example.spring.App : No active profile set, falling back to 1 default profile: "default"
2022-06-22 15:32:11.658 INFO 1 --- [ main] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2022-06-22 15:32:12.823 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)