SpringBoot 2.3.0 M1から入ったBuild Docker images with Cloud Native Buildpacksを試す


きっかけ

SpringBoot 2.3.0 M1がリリースされました。

いろいろな改善や、不要機能の削除などがされていますが、リリースノートを見ていて気になった、Build Docker images with Cloud Native Buildpacksを試してみたいと思います。

リリースノートには、以下記載がありました。

Support for building Docker images using Cloud Native Buildpacks has been added to the Maven and Gradle plugins via the spring-boot:build-image goal and the bootBuildImage task.

MavenかGradleプラグインでDockerイメージが作れるようになるようです。
早速試したいと思い、その活動のまとめとなります。

How to Get Started

前回と同じ設定で
Spring Initializr でアプリ雛形を作ります。
作成後に、環境情報を取得したかったので、SpringBoot Actuatorも有効にしておきます。

早速DockerImageを作ります。イメージを作成する環境ではDockerDeamonを起動しておく必要があるので、その点は注意してください。

これまでSpringBootアプリケーションをDockerImageビルドする場合は以下のステップを踏んでました。
1. mvn package でアプリのJarファイルを作成
2. どのようなDockerImageを作るのかをDockerfileに記載
3. docker buildコマンドでイメージを作成

  • Dockerfileの設定例
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Build Docker images with Cloud Native Buildpacksでは、上記手順を踏まずに1ステップでDockerImageまで作ることができます。
Mavenの場合は、mvn spring-boot:build-image、Gradleの場合はgradle bootBuildImageを叩くだけです。Cloud Native Buildpacksの機能で、ソースの中身を読み取ってDockerfileに設定が必要な内容を補完してくれます。

$ mvn spring-boot:build-image
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.3.0.M1:build-image (default-cli) > package @ demo >>>
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ demo ---
[INFO] Nothing to compile - all classes are up to date

中略

[INFO]  > Running builder
[INFO]     [builder]
[INFO]     [builder]     Cloud Foundry OpenJDK Buildpack v1.0.80
[INFO]     [builder]       OpenJDK JRE 11.0.5: Reusing cached layer
[INFO]     [builder]
[INFO]     [builder]     Cloud Foundry JVM Application Buildpack v1.0.113
[INFO]     [builder]       Executable JAR: Reusing cached layer
[INFO]     [builder]       Process types:
[INFO]     [builder]         executable-jar: java -cp $CLASSPATH $JAVA_OPTS org.springframework.boot.loader.JarLauncher
[INFO]     [builder]         task:           java -cp $CLASSPATH $JAVA_OPTS org.springframework.boot.loader.JarLauncher
[INFO]     [builder]         web:            java -cp $CLASSPATH $JAVA_OPTS org.springframework.boot.loader.JarLauncher
[INFO]     [builder]
[INFO]     [builder]     Cloud Foundry Spring Boot Buildpack v1.0.157
[INFO]     [builder]       Spring Boot 2.3.0.M1: Reusing cached layer
[INFO]     [builder]       Process types:
[INFO]     [builder]         spring-boot: java -cp $CLASSPATH $JAVA_OPTS com.example.demo.DemoApplication
[INFO]     [builder]         task:        java -cp $CLASSPATH $JAVA_OPTS com.example.demo.DemoApplication
[INFO]     [builder]         web:         java -cp $CLASSPATH $JAVA_OPTS com.example.demo.DemoApplication
[INFO]     [builder]
[INFO]     [builder]     Cloud Foundry Spring Auto-reconfiguration Buildpack v1.0.159
[INFO]     [builder]       Spring Auto-reconfiguration 2.11.0: Reusing cached layer
[INFO]
[INFO]  > Running exporter
[INFO]     [exporter]    Reusing layer 'app'
[INFO]     [exporter]    Reusing layer 'config'
[INFO]     [exporter]    Reusing layer 'launcher'
[INFO]     [exporter]    Reusing layer 'org.cloudfoundry.openjdk:openjdk-jre'
[INFO]     [exporter]    Reusing layer 'org.cloudfoundry.jvmapplication:executable-jar'
[INFO]     [exporter]    Reusing layer 'org.cloudfoundry.springboot:spring-boot'
[INFO]     [exporter]    Reusing layer 'org.cloudfoundry.springautoreconfiguration:auto-reconfiguration'
[INFO]     [exporter]    *** Images (89a7e99f9c15):
[INFO]     [exporter]          docker.io/library/demo:0.0.1-SNAPSHOT
[INFO]
[INFO]  > Running cacher
[INFO]     [cacher]      Reusing layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b'
[INFO]     [cacher]      Reusing layer 'org.cloudfoundry.jvmapplication:executable-jar'
[INFO]     [cacher]      Reusing layer 'org.cloudfoundry.springboot:spring-boot'
[INFO]     [cacher]      Reusing layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150'
[INFO]
[INFO] Successfully built image 'docker.io/library/demo:0.0.1-SNAPSHOT'
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:01 min
[INFO] Finished at: 2020-02-08T15:30:43+09:00
[INFO] ------------------------------------------------------------------------

初回はベースイメージのダウンロードとかで時間がかかりますが、2回目以上は上記の時間ぐらいでできます。

さて、ほんとにDockerImageができているのか、確認してみましょう。

docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED              SIZE
demo                                 0.0.1-SNAPSHOT      89a7e99f9c15        About a minute ago   226MB

ちゃんといますね。起動は通常のDockerImageと同じです。ここではdocker run -it -p8080:8080 demo:0.0.1-SNAPSHOT で起動させます。

docker run -it -p8080:8080 demo:0.0.1-SNAPSHOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.3.0.M1)

2020-02-08 06:33:43.540  WARN 1 --- [           main] pertySourceApplicationContextInitializer : Skipping 'cloud' property source addition because not in a cloud
2020-02-08 06:33:43.570  WARN 1 --- [           main] nfigurationApplicationContextInitializer : Skipping reconfiguration because not in a cloud
2020-02-08 06:33:43.635  INFO 1 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on 40fbed8bc770 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2020-02-08 06:33:43.636  INFO 1 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2020-02-08 06:33:48.387  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-02-08 06:33:48.456  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-08 06:33:48.466  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.30]
2020-02-08 06:33:48.738  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-08 06:33:48.740  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 4841 ms
2020-02-08 06:33:50.208  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-08 06:33:50.900  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 14 endpoint(s) beneath base path '/actuator'
2020-02-08 06:33:51.122  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-08 06:33:51.142  INFO 1 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 9.779 seconds (JVM running for 12.428)

無事、起動できています。前回アプリを使っているので、Actuatorも有効のままです。envの情報を取得してみましょう。

curl localhost:8080/actuator/env | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  8753    0  8753    0     0  10130      0 --:--:-- --:--:-- --:--:-- 10130
{
  "activeProfiles": [],
  "propertySources": [
    {
      "name": "server.ports",
      "properties": {
        "local.server.port": {
          "value": 8080
        }
      }
    },
    {
      "name": "servletContextInitParams",
      "properties": {}
    },
    {
      "name": "systemProperties",
      "properties": {
        "awt.toolkit": {
          "value": "sun.awt.X11.XToolkit"
        },
        "java.specification.version": {
          "value": "11"
        },
        "sun.cpu.isalist": {
          "value": ""
        },
        "sun.jnu.encoding": {
          "value": "ANSI_X3.4-1968"
        },
        "java.class.path": {
          "value": "/layers/org.cloudfoundry.springautoreconfiguration/auto-reconfiguration/java-buildpack-auto-reconfiguration-2.11.0.RELEASE.jar:/workspace/BOOT-INF/classes:/workspace/BOOT-INF/lib/HdrHistogram-2.1.11.jar:/workspace/BOOT-INF/lib/LatencyUtils-2.0.3.jar:/workspace/BOOT-INF/lib/jackson-annotations-2.10.1.jar:/workspace/BOOT-INF/lib/jackson-core-2.10.1.jar:/workspace/BOOT-INF/lib/jackson-databind-2.10.1.jar:/workspace/BOOT-INF/lib/jackson-datatype-jdk8-2.10.1.jar:/workspace/BOOT-INF/lib/jackson-datatype-jsr310-2.10.1.jar:/workspace/BOOT-INF/lib/jackson-module-parameter-names-2.10.1.jar:/workspace/BOOT-INF/lib/jakarta.annotation-api-1.3.5.jar:/workspace/BOOT-INF/lib/jakarta.el-3.0.3.jar:/workspace/BOOT-INF/lib/jul-to-slf4j-1.7.29.jar:/workspace/BOOT-INF/lib/log4j-api-2.12.1.jar:/workspace/BOOT-INF/lib/log4j-to-slf4j-2.12.1.jar:/workspace/BOOT-INF/lib/logback-classic-1.2.3.jar:/workspace/BOOT-INF/lib/logback-core-1.2.3.jar:/workspace/BOOT-INF/lib/micrometer-core-1.3.3.jar:/workspace/BOOT-INF/lib/slf4j-api-1.7.29.jar:/workspace/BOOT-INF/lib/snakeyaml-1.25.jar:/workspace/BOOT-INF/lib/spring-aop-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-beans-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-boot-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-actuator-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-actuator-autoconfigure-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-autoconfigure-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-starter-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-starter-actuator-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-starter-json-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-starter-logging-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-starter-tomcat-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-boot-starter-web-2.3.0.M1.jar:/workspace/BOOT-INF/lib/spring-context-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-core-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-expression-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-jcl-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-web-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/spring-webmvc-5.2.3.RELEASE.jar:/workspace/BOOT-INF/lib/tomcat-embed-core-9.0.30.jar:/workspace/BOOT-INF/lib/tomcat-embed-websocket-9.0.30.jar:/workspace"
        },
        "java.vm.vendor": {
          "value": "AdoptOpenJDK"
        },
        "sun.arch.data.model": {
          "value": "64"
        },
        "java.vendor.url": {
          "value": "https://adoptopenjdk.net/"
        },
        "catalina.useNaming": {
          "value": "false"
        },
        "user.timezone": {
          "value": "GMT"
        },
        "os.name": {
          "value": "Linux"
        },
        "java.vm.specification.version": {
          "value": "11"
        },
        "sun.java.launcher": {
          "value": "SUN_STANDARD"
        },
        "user.country": {
          "value": "US"
        },
        "sun.boot.library.path": {
          "value": "/layers/org.cloudfoundry.openjdk/openjdk-jre/lib"
        },
        "sun.java.command": {
          "value": "******"
        },
        "jdk.debug": {
          "value": "release"
        },
        "sun.cpu.endian": {
          "value": "little"
        },
        "user.home": {
          "value": "/home/cnb"
        },
        "user.language": {
          "value": "en"
        },
        "java.specification.vendor": {
          "value": "Oracle Corporation"
        },
        "java.version.date": {
          "value": "2019-10-15"
        },
        "java.home": {
          "value": "/layers/org.cloudfoundry.openjdk/openjdk-jre"
        },
        "file.separator": {
          "value": "/"
        },
        "java.vm.compressedOopsMode": {
          "value": "32-bit"
        },
        "line.separator": {
          "value": "\n"
        },
        "java.specification.name": {
          "value": "Java Platform API Specification"
        },
        "java.vm.specification.vendor": {
          "value": "Oracle Corporation"
        },
        "java.awt.graphicsenv": {
          "value": "sun.awt.X11GraphicsEnvironment"
        },
        "java.awt.headless": {
          "value": "true"
        },
        "sun.management.compiler": {
          "value": "HotSpot 64-Bit Tiered Compilers"
        },
        "java.runtime.version": {
          "value": "11.0.5+10"
        },
        "user.name": {
          "value": "cnb"
        },
        "path.separator": {
          "value": ":"
        },
        "os.version": {
          "value": "4.9.184-linuxkit"
        },
        "java.runtime.name": {
          "value": "OpenJDK Runtime Environment"
        },
        "file.encoding": {
          "value": "ANSI_X3.4-1968"
        },
        "spring.beaninfo.ignore": {
          "value": "true"
        },
        "java.vm.name": {
          "value": "OpenJDK 64-Bit Server VM"
        },
        "java.vendor.version": {
          "value": "AdoptOpenJDK"
        },
        "java.vendor.url.bug": {
          "value": "https://github.com/AdoptOpenJDK/openjdk-build/issues"
        },
        "java.io.tmpdir": {
          "value": "/tmp"
        },
        "catalina.home": {
          "value": "/tmp/tomcat.9057283283640867778.8080"
        },
        "java.version": {
          "value": "11.0.5"
        },
        "user.dir": {
          "value": "/workspace"
        },
        "os.arch": {
          "value": "amd64"
        },
        "java.vm.specification.name": {
          "value": "Java Virtual Machine Specification"
        },
        "PID": {
          "value": "1"
        },
        "java.awt.printerjob": {
          "value": "sun.print.PSPrinterJob"
        },
        "sun.os.patch.level": {
          "value": "unknown"
        },
        "catalina.base": {
          "value": "/tmp/tomcat.9057283283640867778.8080"
        },
        "java.library.path": {
          "value": "/layers/org.cloudfoundry.openjdk/openjdk-jre/lib:/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib"
        },
        "java.vendor": {
          "value": "AdoptOpenJDK"
        },
        "java.vm.info": {
          "value": "mixed mode"
        },
        "java.vm.version": {
          "value": "11.0.5+10"
        },
        "sun.io.unicode.encoding": {
          "value": "UnicodeLittle"
        },
        "java.class.version": {
          "value": "55.0"
        }
      }

後略

Javaの実装はAdoptOpenJDKの11が使われていますね。OSはLinuxのようです。

まとめ

Dockerfileを書かなくてもよいので、Dockerの知識がなくても簡単にDockerImageを作成できました。仕様や細かい動きを確認しないと本番へすぐ投入とはいかなそうですが、気軽にDockerImage作成できることは、メリットとなるのではないでしょうか?

使ったソースコードは以下に配置しておきました。
ご参考です。