以下のソフトウェアをインストールしてください。

この資料は次のバージョンで動作確認しています。

$ java -version              
openjdk version "11.0.10" 2021-01-19 LTS
OpenJDK Runtime Environment (build 11.0.10+9-LTS)
OpenJDK 64-Bit Server VM (build 11.0.10+9-LTS, mixed mode)

$ docker version
Client: Docker Engine - Community
 Cloud integration: 1.0.9
 Version:           20.10.5
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        55c4c88
 Built:             Tue Mar  2 20:13:00 2021
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.5
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       363e9a8
  Built:            Tue Mar  2 20:15:47 2021
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ pack version
0.18.0+git-e00ee4a.build-2328


TBD


まずはとにかくCloud Native Buildpacksを試してみましょう。Spring Bootは2.3からMaven / Gradle PluginでCloud Native Buildpacksが使えるようになったので非常に簡単に始められます。Spring Bootで簡単なサンプルアプリを作成し、Cloud Native Buildpacksを使ってOCIイメージを作成します。

Prepare a sample app

Spring InitializrでSpring Bootの雛形アプリケーションを作成します。

作業場所としてholディレクトリを作成してください。

mkdir hol
cd hol


次のコマンドで雛形アプリケーションを作成します。

curl -s https://start.spring.io/starter.tgz \
 -d artifactId=vehicle-api \
 -d baseDir=vehicle-api \
 -d dependencies=web,jdbc,postgresql,actuator \
 -d packageName=com.example \
 -d applicationName=VehicleApiApplication | tar -xzvf -

tarコマンドが使えない場合はこのリンクをクリックして"GENERATE"ボタンをクリックして、zipファイルをダウンロードしてholディレクトリで展開してください。

src/main/java/com/example/VehicleController.javaを作成し、次の内容を記述してください。

package com.example;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.PreparedStatement;
import java.util.List;

@RestController
public class VehicleController {

  private final JdbcTemplate jdbcTemplate;

  public VehicleController(JdbcTemplate jdbcTemplate) {
     this.jdbcTemplate = jdbcTemplate;
  }

  @GetMapping(path = "/vehicles")
  public ResponseEntity<?> getVehicles() {
     final List<Vehicle> vehicles = this.jdbcTemplate.query("SELECT id, name FROM vehicle ORDER BY id", (rs, i) -> new Vehicle(rs.getInt("id"), rs.getString("name")));
     return ResponseEntity.ok(vehicles);
  }

  @PostMapping(path = "/vehicles")
  public ResponseEntity<?> postVehicles(@RequestBody Vehicle vehicle) {
     final KeyHolder keyHolder = new GeneratedKeyHolder();
     this.jdbcTemplate.update(connection -> {
        final PreparedStatement statement = connection.prepareStatement("INSERT INTO vehicle(name) VALUES (?)", new String[]{"id"});
        statement.setString(1, vehicle.getName());
        return statement;
     }, keyHolder);
     vehicle.setId(keyHolder.getKey().intValue());
     return ResponseEntity.status(HttpStatus.CREATED).body(vehicle);
  }

  @DeleteMapping(path = "/vehicles/{id}")
  public ResponseEntity<?> deleteVehicle(@PathVariable("id") Integer id) {
     this.jdbcTemplate.update("DELETE FROM vehicle WHERE id = ?", id);
     return ResponseEntity.noContent().build();
  }

  static class Vehicle {

     public Vehicle(Integer id, String name) {
        this.id = id;
        this.name = name;
     }

     private Integer id;

     private String name;

     public Integer getId() {
        return id;
     }

     public void setId(Integer id) {
        this.id = id;
     }

     public String getName() {
        return name;
     }

     public void setName(String name) {
        this.name = name;
     }
  }
}

src/main/resources/schema.sqlを作成し、次の内容を記述してください。

CREATE TABLE IF NOT EXISTS vehicle
(
   id   SERIAL PRIMARY KEY,
   name VARCHAR(16) UNIQUE
);

また、src/main/resources/data.sqlを作成し、次の内容を記述してください。

INSERT INTO vehicle(name) VALUES ('Avalon') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Corolla') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Crown') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Levin') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Yaris') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Vios') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Glanza') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;
INSERT INTO vehicle(name) VALUES ('Aygo') ON CONFLICT ON CONSTRAINT vehicle_name_key DO NOTHING;


次にsrc/main/resources/application.propertiesに次の内容を記述してください。

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/vehicle
spring.datasource.username=vehicle
spring.datasource.password=vehicle
spring.datasource.initialization-mode=always


アプリケーションをビルドしてください。

cd vehicle-api
./mvnw clean package -Dmaven.test.skip=true

ローカルでアプリケーションの動作を確認するためにPostgreSQLをDockerで起動します。新しいターミナルを開いて、次のコマンドを実行してください。

docker run --rm \
 -p 5432:5432 \
 -e POSTGRES_DB=vehicle \
 -e POSTGRES_USER=vehicle \
 -e POSTGRES_PASSWORD=vehicle \
 bitnami/postgresql:11.11.0-debian-10-r59

アプリケーションを起動します。

$ java -jar target/vehicle-api-0.0.1-SNAPSHOT.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.4)

2021-04-09 15:14:09.018  INFO 81564 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on makinoMacBook-Pro.local with PID 81564 (/Users/toshiaki/hol/vehicle-api/target/vehicle-api-0.0.1-SNAPSHOT.jar started by toshiaki in /Users/toshiaki/hol/vehicle-api)
...

起動したアプリケーションにアクセスします。

$ curl -s http://localhost:8080/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  }
]

$ curl -s http://localhost:8080/vehicles -d "{\"name\": \"Lexus\"}" -H "Content-Type: application/json" | jq .
{
  "id": 9,
  "name": "Lexus"
}

$ curl -s http://localhost:8080/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  },
  {
    "id": 9,
    "name": "Lexus"
  }
]


ここまででアプリケーションの準備ができました。Ctrl+Cでアプリケーションを停止してください。

Build with Spring Boot Maven Plugin

Spring Boot Maven Pluginを使い、作成したアプリケーションのOCIイメージを作成します。次のコマンドを実行してください。

./mvnw spring-boot:build-image -Dmaven.test.skip=true

次のようなログが出力がされます。

[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:vehicle-api >-----------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
...
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.4.4:build-image (default-cli) < package @ vehicle-api <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.4:build-image (default-cli) @ vehicle-api ---
[INFO] Building image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO]  > Pulling builder image 'docker.io/paketobuildpacks/builder:base' 100%
[INFO]  > Pulled builder image 'paketobuildpacks/builder@sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e'
[INFO]  > Pulling run image 'docker.io/paketobuildpacks/run:base-cnb' 100%
[INFO]  > Pulled run image 'paketobuildpacks/run@sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf'
[INFO]  > Executing lifecycle version v0.11.1
[INFO]  > Using build cache volume 'pack-cache-13a8f10fbd06.build'
[INFO] 
[INFO]  > Running creator
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     5 of 18 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/ca-certificates   2.1.0
[INFO]     [creator]     paketo-buildpacks/bellsoft-liberica 7.1.0
[INFO]     [creator]     paketo-buildpacks/executable-jar    5.0.0
[INFO]     [creator]     paketo-buildpacks/dist-zip          4.0.0
[INFO]     [creator]     paketo-buildpacks/spring-boot       4.2.0
[INFO]     [creator]     ===> ANALYZING
[INFO]     [creator]     Previous image with name "docker.io/library/vehicle-api:0.0.1-SNAPSHOT" not found
[INFO]     [creator]     ===> RESTORING
[INFO]     [creator]     ===> BUILDING
[INFO]     [creator]     
[INFO]     [creator]     Paketo CA Certificates Buildpack 2.1.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/ca-certificates
[INFO]     [creator]       Launch Helper: Contributing to layer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
[INFO]     [creator]     
[INFO]     [creator]     Paketo BellSoft Liberica Buildpack 7.1.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/bellsoft-liberica
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_JVM_VERSION              11.*            the Java version
[INFO]     [creator]       Launch Configuration:
[INFO]     [creator]         $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
[INFO]     [creator]         $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
[INFO]     [creator]         $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
[INFO]     [creator]         $JAVA_TOOL_OPTIONS                           the JVM launch flags
[INFO]     [creator]       BellSoft Liberica JRE 11.0.10: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jre11.0.10+9-linux-amd64.tar.gz
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
[INFO]     [creator]         Adding 129 container CA certificates to JVM truststore
[INFO]     [creator]         Writing env.launch/BPI_APPLICATION_PATH.default
[INFO]     [creator]         Writing env.launch/BPI_JVM_CACERTS.default
[INFO]     [creator]         Writing env.launch/BPI_JVM_CLASS_COUNT.default
[INFO]     [creator]         Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
[INFO]     [creator]         Writing env.launch/JAVA_HOME.default
[INFO]     [creator]         Writing env.launch/MALLOC_ARENA_MAX.default
[INFO]     [creator]       Launch Helper: Contributing to layer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
[INFO]     [creator]       JVMKill Agent 1.16.0: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.append
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.delim
[INFO]     [creator]       Java Security Properties: Contributing to layer
[INFO]     [creator]         Writing env.launch/JAVA_SECURITY_PROPERTIES.default
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.append
[INFO]     [creator]         Writing env.launch/JAVA_TOOL_OPTIONS.delim
[INFO]     [creator]     
[INFO]     [creator]     Paketo Executable JAR Buildpack 5.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/executable-jar
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env/CLASSPATH.delim
[INFO]     [creator]         Writing env/CLASSPATH.prepend
[INFO]     [creator]       Process types:
[INFO]     [creator]         executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
[INFO]     [creator]         task:           java org.springframework.boot.loader.JarLauncher (direct)
[INFO]     [creator]         web:            java org.springframework.boot.loader.JarLauncher (direct)
[INFO]     [creator]     
[INFO]     [creator]     Paketo Spring Boot Buildpack 4.2.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/spring-boot
[INFO]     [creator]       Creating slices from layers index
[INFO]     [creator]         dependencies
[INFO]     [creator]         spring-boot-loader
[INFO]     [creator]         snapshot-dependencies
[INFO]     [creator]         application
[INFO]     [creator]       Launch Helper: Contributing to layer
[INFO]     [creator]         Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
[INFO]     [creator]       Spring Cloud Bindings 1.7.1: Contributing to layer
[INFO]     [creator]         Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
[INFO]     [creator]       Web Application Type: Contributing to layer
[INFO]     [creator]         Servlet web application detected
[INFO]     [creator]         Writing env.launch/BPL_JVM_THREAD_COUNT.default
[INFO]     [creator]       4 application slices
[INFO]     [creator]       Image labels:
[INFO]     [creator]         org.opencontainers.image.title
[INFO]     [creator]         org.opencontainers.image.version
[INFO]     [creator]         org.springframework.boot.spring-configuration-metadata.json
[INFO]     [creator]         org.springframework.boot.version
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding layer 'paketo-buildpacks/ca-certificates:helper'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:helper'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/executable-jar:classpath'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/spring-boot:helper'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
[INFO]     [creator]     Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
[INFO]     [creator]     Adding 5/5 app layer(s)
[INFO]     [creator]     Adding layer 'launcher'
[INFO]     [creator]     Adding layer 'config'
[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.spring-configuration-metadata.json'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     Saving docker.io/library/vehicle-api:0.0.1-SNAPSHOT...
[INFO]     [creator]     *** Images (c0cbc7fd51b0):
[INFO]     [creator]           docker.io/library/vehicle-api:0.0.1-SNAPSHOT
[INFO] 
[INFO] Successfully built image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  57.056 s
[INFO] Finished at: 2021-04-09T15:21:50+09:00
[INFO] ------------------------------------------------------------------------

docker.io/library/vehicle-api:0.0.1-SNAPSHOTという名前のOCIイメージが作成されました。

次のコマンドでイメージ一覧を確認してください。

$ docker images
REPOSITORY                 TAG                     IMAGE ID       CREATED        SIZE
bitnami/postgresql         11.11.0-debian-10-r59   c416eed2ffd6   17 hours ago   258MB
paketobuildpacks/run       base-cnb                8e1ab607b638   6 days ago     87.2MB
vehicle-api                0.0.1-SNAPSHOT          c0cbc7fd51b0   41 years ago   262MB
paketobuildpacks/builder   base                    c8f7e51dbb67   41 years ago   660MB

作成したイメージを実行します。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

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

2021-04-09 06:41:03.173  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on ab785b1ccd44 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-09 06:41:03.177  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-09 06:41:04.623  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-09 06:41:04.636  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-09 06:41:04.636  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-09 06:41:04.696  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-09 06:41:04.697  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1454 ms
2021-04-09 06:41:04.997  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-09 06:41:05.128  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-09 06:41:05.330  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-09 06:41:05.646  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-09 06:41:05.685  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-09 06:41:05.701  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 3.083 seconds (JVM running for 3.565)

アプリケーションにアクセスしてください。

$ curl -s http://localhost:8080/vehicles | jq .
[
  {
    "id": 1,
    "name": "Avalon"
  },
  {
    "id": 2,
    "name": "Corolla"
  },
  {
    "id": 3,
    "name": "Crown"
  },
  {
    "id": 4,
    "name": "Levin"
  },
  {
    "id": 5,
    "name": "Yaris"
  },
  {
    "id": 6,
    "name": "Vios"
  },
  {
    "id": 7,
    "name": "Glanza"
  },
  {
    "id": 8,
    "name": "Aygo"
  },
  {
    "id": 9,
    "name": "Lexus"
  }
]

Dockerfileを作成することなく、Spring BootアプリケーションのOCIイメージを作成することができました。

Build with pack CLI

次はpack CLIを使用して作成したアプリケーションのOCIイメージを作成します。

from an executable jar file

まずは事前にビルド済みの実行可能なjarファイルからOCIイメージを作成します。次のコマンドを実行してください。

pack build vehicle-api \
 --path ./target/vehicle-api-0.0.1-SNAPSHOT.jar \
 --builder paketobuildpacks/builder:base

次のようなログが出力されます。

base: Pulling from paketobuildpacks/builder
Digest: sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
5 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
===> ANALYZING
Previous image with name "vehicle-api" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  BellSoft Liberica JRE 11.0.10: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jre11.0.10+9-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_bellsoft-liberica/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
  Spring Cloud Bindings 1.7.1: Contributing to layer
    Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar
    Verifying checksum
    Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version
===> EXPORTING
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:helper'
Adding layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jre'
Adding layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
Adding layer 'paketo-buildpacks/executable-jar:classpath'
Adding layer 'paketo-buildpacks/spring-boot:helper'
Adding layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
Adding layer 'paketo-buildpacks/spring-boot:web-application-type'
Adding 5/5 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Adding label 'org.opencontainers.image.title'
Adding label 'org.opencontainers.image.version'
Adding label 'org.springframework.boot.spring-configuration-metadata.json'
Adding label 'org.springframework.boot.version'
Setting default process type 'web'
Saving vehicle-api...
*** Images (c0cbc7fd51b0):
      vehicle-api
Successfully built image vehicle-api

vehicle-apiという名前のイメージが作成されました。前と同様に、次のコマンドを実行して動作確認ください。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api

作られたイメージはMaven Pluginで作ったものと実質的に同じです。pack CLIを使うかMaven Pluginを使うかは使いやすい方を選択してください。

from source code

Maven PluginがjarファイルからのみOCIイメージを作成すると異なり、pack CLIを使用する場合は

のいずれかからOCIイメージを作成できます。

今回はソースコードから直接イメージを作成します。

この場合は、Mavenによるビルドもコンテナ上で行われます。次のコマンドを実行してください。

pack build vehicle-api \
 --builder paketobuildpacks/builder:base

次のようなログが出力されます。

base: Pulling from paketobuildpacks/builder
Digest: sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
7 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/maven             5.0.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/apache-tomcat     5.1.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
===> ANALYZING
Restoring metadata for "paketo-buildpacks/ca-certificates:helper" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:helper" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:java-security-properties" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jre" from app image
Restoring metadata for "paketo-buildpacks/bellsoft-liberica:jvmkill" from app image
Restoring metadata for "paketo-buildpacks/spring-boot:helper" from app image
Restoring metadata for "paketo-buildpacks/spring-boot:spring-cloud-bindings" from app image
Restoring metadata for "paketo-buildpacks/spring-boot:web-application-type" from app image
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  BellSoft Liberica JDK 11.0.10: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jdk11.0.10+9-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 11.0.10: Reusing cached layer
  Launch Helper: Reusing cached layer
  JVMKill Agent 1.16.0: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Maven Buildpack 5.0.0
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package
[INFO] Scanning for projects...
[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.4.4/spring-boot-starter-parent-2.4.4.pom
...
[INFO] Downloaded from central: https://repo.maven.apache.org/maven2/net/java/dev/jna/jna/5.5.0/jna-5.5.0.jar (1.5 MB at 398 kB/s)
[INFO] Replacing main artifact with repackaged archive
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:28 min
[INFO] Finished at: 2021-04-09T07:12:25Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.7.1: Reusing cached layer
  Web Application Type: Reusing cached layer
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version
===> EXPORTING
Reusing layer 'paketo-buildpacks/ca-certificates:helper'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:helper'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:java-security-properties'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:jre'
Reusing layer 'paketo-buildpacks/bellsoft-liberica:jvmkill'
Reusing layer 'paketo-buildpacks/executable-jar:classpath'
Reusing layer 'paketo-buildpacks/spring-boot:helper'
Reusing layer 'paketo-buildpacks/spring-boot:spring-cloud-bindings'
Reusing layer 'paketo-buildpacks/spring-boot:web-application-type'
Reusing 5/5 app layer(s)
Reusing layer 'launcher'
Adding layer 'config'
Reusing layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Adding label 'org.opencontainers.image.title'
Adding label 'org.opencontainers.image.version'
Adding label 'org.springframework.boot.spring-configuration-metadata.json'
Adding label 'org.springframework.boot.version'
Setting default process type 'web'
Saving vehicle-api...
*** Images (bd392fae2c31):
      vehicle-api
Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api

pack buildコマンドを実行後にMavenによるアプリケーションのビルドが実行されました。初回はキャッシュがないため、時間がかかりましたが、二回目のビルド以降はキャッシュが使われるため速くなります。

前と同様に、次のコマンドを実行して動作確認ください。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api

Cloud Native BuildpacksのEcosystemを紹介します。

Builder

Cloud Native Buildpacksでコンテナイメージを作成する場合、まずBuilderを選択します。Builderは以下のコンポーネントから構成されます。

使用するBuilderによって出来上がるイメージが異なりますし、サポートされている言語も異なります。

代表的なBuilderは次のようにpack builder suggestコマンドで確認できます。

$ pack builder suggest
Suggested builders:
        Google:                gcr.io/buildpacks/builder:v1      Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python                                              
        Heroku:                heroku/buildpacks:18              Base builder for Heroku-18 stack, based on ubuntu:18.04 base image                                                        
        Heroku:                heroku/buildpacks:20              Base builder for Heroku-20 stack, based on ubuntu:20.04 base image                                                        
        Paketo Buildpacks:     paketobuildpacks/builder:base     Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Ruby, NGINX and Procfile                        
        Paketo Buildpacks:     paketobuildpacks/builder:full     Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, PHP, Ruby, Apache HTTPD, NGINX and Procfile     
        Paketo Buildpacks:     paketobuildpacks/builder:tiny     Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java Native Image and Go              

Tip: Learn more about a specific builder with:
        pack builder inspect <builder-image>

このhands-onでは基本的にはpaketobuildpacks/builder:baseを使用します。Spring Boot Maven Pluginでイメージを作った際にもこのBuilderが使用されていました。一部でpaketobuildpacks/builder:tinyを使用します。

Platform

Cloud Native Buildpacksの用語ではPlatformはLifecycleやBuildpackを使ってコンテナイメージを作る機能を含むアプリケーションやシステムです。ここまでで使用したpack CLIやSpring Boot Maven PluginもPlatformの一つです。既に様々なPlatformが存在します。

Local Build

CI

Application Platform

pack inspectコマンドでイメージがどのBuildpackで作成されたかなどの情報を見ることができます。

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT               
Inspecting image: docker.io/library/vehicle-api:0.0.1-SNAPSHOT

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.bionic

Base Image:
  Reference: 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162
  Top Layer: sha256:10aba31d775e5770b8dc5fc9a28047f71245d8bfdd7e5b7c15035f95ba46b8c5

Run Images:
  index.docker.io/paketobuildpacks/run:base-cnb
  gcr.io/paketo-buildpacks/run:base-cnb

Buildpacks:
  ID                                         VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates          2.1.0          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/bellsoft-liberica        7.1.0          https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/executable-jar           5.0.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/dist-zip                 4.0.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/spring-boot              4.2.0          https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                  SHELL        COMMAND        ARGS
  web (default)                      java           org.springframework.boot.loader.JarLauncher
  executable-jar                     java           org.springframework.boot.loader.JarLauncher
  task                               java           org.springframework.boot.loader.JarLauncher

Run ImageのIDが8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162であることがわかります。このIDは次のコマンドよりpaketobuildpacks/run:base-cnbのものと一致することがわかります。

$ docker images --no-trunc | grep 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162
paketobuildpacks/run       base-cnb                sha256:8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162   6 days ago       87.2MB

--bomオプションをつけるとそのイメージが使用しているバイナリのバージョンや依存ライブラリ一覧も取得できます。

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT --bom
{
  "remote": null,
  "local": [
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "ca-certificates-helper"
        ],
        "version": "2.1.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/ca-certificates",
        "version": "2.1.0"
      }
    },
    {
      "name": "jre",
      "metadata": {
        "layer": "jre",
        "licenses": [
          {
            "type": "GPL-2.0 WITH Classpath-exception-2.0",
            "uri": "https://openjdk.java.net/legal/gplv2+ce.html"
          }
        ],
        "name": "BellSoft Liberica JRE",
        "sha256": "2ca618d3122535aa39e713dce18286622a410ab2f003d73b321520f28aad0c36",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://github.com/bell-sw/Liberica/releases/download/11.0.10+9/bellsoft-jre11.0.10+9-linux-amd64.tar.gz",
        "version": "11.0.10"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "7.1.0"
      }
    },
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "active-processor-count",
          "java-opts",
          "link-local-dns",
          "memory-calculator",
          "openssl-certificate-loader",
          "security-providers-configurer",
          "security-providers-classpath-9"
        ],
        "version": "7.1.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "7.1.0"
      }
    },
    {
      "name": "jvmkill",
      "metadata": {
        "layer": "jvmkill",
        "licenses": [
          {
            "type": "Apache-2.0",
            "uri": "https://github.com/cloudfoundry/jvmkill/blob/main/LICENSE"
          }
        ],
        "name": "JVMKill Agent",
        "sha256": "a3092627b082cb3cdbbe4b255d35687126aa604e6b613dcda33be9f7e1277162",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so",
        "version": "1.16.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "7.1.0"
      }
    },
    {
      "name": "dependencies",
      "metadata": {
        "dependencies": [
          {
            "name": "HdrHistogram",
            "sha256": "9b47fbae444feaac4b7e04f0ea294569e4bc282bc69d8c2ce2ac3f23577281e2",
            "version": "2.1.12"
          },
          {
            "name": "HikariCP",
            "sha256": "8b732f9470570d4a841dc1ef6c826b586978b25ba830712ff1fa59de275dfa61",
            "version": "3.4.5"
          },
          {
            "name": "LatencyUtils",
            "sha256": "a32a9ffa06b2f4e01c5360f8f9df7bc5d9454a5d373cd8f361347fa5a57165ec",
            "version": "2.0.3"
          },
          {
            "name": "checker-qual",
            "sha256": "729990b3f18a95606fc2573836b6958bcdb44cb52bfbd1b7aa9c339cff35a5a4",
            "version": "3.5.0"
          },
          {
            "name": "jackson-annotations",
            "sha256": "2ab76f64048673675f26ddd1008a32889855d8a126273edae2aeee516785a1ec",
            "version": "2.11.4"
          },
          {
            "name": "jackson-core",
            "sha256": "e1dda269f16f3be00578f3a46c754f098245c7a480b86e07030b6ee7087ee1f1",
            "version": "2.11.4"
          },
          {
            "name": "jackson-databind",
            "sha256": "dc64fa3907bd299f29ad6116169e583333d04404b23a0f81ed679afa8e2a2ee8",
            "version": "2.11.4"
          },
          {
            "name": "jackson-datatype-jdk8",
            "sha256": "2ed8e417d190c370753ed3eb4f5193378e11ddea8736f952bd0264844600faaf",
            "version": "2.11.4"
          },
          {
            "name": "jackson-datatype-jsr310",
            "sha256": "4f70ec64696f8de547773d7f8d5243ab7d11dc42a854df1764fa16837b6f53af",
            "version": "2.11.4"
          },
          {
            "name": "jackson-module-parameter-names",
            "sha256": "fb3f84a7318af5a7b577ecf23d577d6b0e2162496dd61ab3f2a39df1a1c1cc3e",
            "version": "2.11.4"
          },
          {
            "name": "jakarta.annotation-api",
            "sha256": "85fb03fc054cdf4efca8efd9b6712bbb418e1ab98241c4539c8585bbc23e1b8a",
            "version": "1.3.5"
          },
          {
            "name": "jakarta.el",
            "sha256": "e2bcb8551b02a5c2afdc4cab77302ba5c76705cf1fc832345ca880df80bf4716",
            "version": "3.0.3"
          },
          {
            "name": "jul-to-slf4j",
            "sha256": "bbcbfdaa72572255c4f85207a9bfdb24358dc993e41252331bd4d0913e4988b9",
            "version": "1.7.30"
          },
          {
            "name": "log4j-api",
            "sha256": "2b4b1965c9dce7f3732a0fbf5c8493199c1e6bf8cf65c3e235b57d98da5f36af",
            "version": "2.13.3"
          },
          {
            "name": "log4j-to-slf4j",
            "sha256": "9624e9aaf60b1875adde33d8e7997de110b70be09e94e55ad8fc39637ec002c4",
            "version": "2.13.3"
          },
          {
            "name": "logback-classic",
            "sha256": "fb53f8539e7fcb8f093a56e138112056ec1dc809ebb020b59d8a36a5ebac37e0",
            "version": "1.2.3"
          },
          {
            "name": "logback-core",
            "sha256": "5946d837fe6f960c02a53eda7a6926ecc3c758bbdd69aa453ee429f858217f22",
            "version": "1.2.3"
          },
          {
            "name": "micrometer-core",
            "sha256": "766e5ac5f3ae1e07738ff3ce5a8b818399d6b05250179d3d4b5b93ad027d59e6",
            "version": "1.6.5"
          },
          {
            "name": "postgresql",
            "sha256": "232747fa0924d88a26dfa42b8128b6fa1140802d804125891596e8ca5f291322",
            "version": "42.2.19"
          },
          {
            "name": "slf4j-api",
            "sha256": "cdba07964d1bb40a0761485c6b1e8c2f8fd9eb1d19c53928ac0d7f9510105c57",
            "version": "1.7.30"
          },
          {
            "name": "snakeyaml",
            "sha256": "7e7cce6740ed705bfdfaac7b442c1375d2986d2f2935936a5bd40c14e18fd736",
            "version": "1.27"
          },
          {
            "name": "spring-aop",
            "sha256": "18d08f02b52dc3734b22957200f53120595e41c5023400a6f82672abe5d0b38e",
            "version": "5.3.5"
          },
          {
            "name": "spring-beans",
            "sha256": "a5736e63f93f68f32d9785c3a4a74fd956b382d92a55cd123908141cf0d9b220",
            "version": "5.3.5"
          },
          {
            "name": "spring-boot",
            "sha256": "1f805f990961be541a4465978487e1cd35cdb078e8a8ec17ebf1553fecad2657",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-actuator",
            "sha256": "dbae617d0529c796cf50d9229bbcf5ffc1b2d4eb4f1f65d910b539860eff8360",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-actuator-autoconfigure",
            "sha256": "21274f14b84ada232ed79d4bab72937cb571434f5428c7ed2b7c97c29ad0f86a",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-autoconfigure",
            "sha256": "6d17db0d2662edf413020165f5cd955eecdd0f506973b57144055f69873b0f2d",
            "version": "2.4.4"
          },
          {
            "name": "spring-boot-jarmode-layertools",
            "sha256": "637b05a542e988e882c715750c7394a36b08af22ea084c6c117f63d0b2d01d55",
            "version": "2.4.4"
          },
          {
            "name": "spring-context",
            "sha256": "a00f670b873b9bf23829939c014c89ee0f0cca5777d05e7eb83cd26a4084898d",
            "version": "5.3.5"
          },
          {
            "name": "spring-core",
            "sha256": "4f25c412aed640d50200ef3b8013482d29878bd03eaa722af20b4fc7e2126d19",
            "version": "5.3.5"
          },
          {
            "name": "spring-expression",
            "sha256": "e4c7acd4aaaee2fa13ab9bdb664fdd29ee4b04d1046bbdff64cfbebcab651791",
            "version": "5.3.5"
          },
          {
            "name": "spring-jcl",
            "sha256": "e7d57085197d182f63d0c63dc40a545f10f30334889b5bda2d60a4287184ee1a",
            "version": "5.3.5"
          },
          {
            "name": "spring-jdbc",
            "sha256": "1bb609e5ca48e8a3e8c93f7f49867bc7a523d85713514f37117c74fb8fe68efa",
            "version": "5.3.5"
          },
          {
            "name": "spring-tx",
            "sha256": "f3653761d94adfa30904db68f837a235f0b5b2d35539a416266321c107d09b33",
            "version": "5.3.5"
          },
          {
            "name": "spring-web",
            "sha256": "3b1c02204ebf66916a2770db98438c2cd1b5cb870e3c1ed182a10c81e9e37e09",
            "version": "5.3.5"
          },
          {
            "name": "spring-webmvc",
            "sha256": "d663076cf51140e4a5a489cf6eced2af2457531a75d9cbf6ffb658b7be9d4099",
            "version": "5.3.5"
          },
          {
            "name": "tomcat-embed-core",
            "sha256": "35610e1de90902423345cb835c7c22e4c39d928a06e922d02bf8c5d0062d6249",
            "version": "9.0.44"
          },
          {
            "name": "tomcat-embed-websocket",
            "sha256": "33e62273dd1978745e1675f028f0291f064e390d30584b94549dfaacb0696151",
            "version": "9.0.44"
          }
        ],
        "layer": "application"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.2.0"
      }
    },
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "spring-cloud-bindings"
        ],
        "version": "4.2.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.2.0"
      }
    },
    {
      "name": "spring-cloud-bindings",
      "metadata": {
        "layer": "spring-cloud-bindings",
        "licenses": [
          {
            "type": "Apache-2.0",
            "uri": "https://github.com/spring-cloud/spring-cloud-bindings/blob/main/LICENSE"
          }
        ],
        "name": "Spring Cloud Bindings",
        "sha256": "a52c2592d58555b6d70a3b0128be70852c83a0c58b70a7b23c07ebd9631ec47a",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.1/spring-cloud-bindings-1.7.1.jar",
        "version": "1.7.1"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.2.0"
      }
    }
  ]
}

Stackはビルドのベースイメージとして使用されるBuild Imageとランタイムのベースイメージとして使用されるRun Imageの組み合わせです。代表的なStackはpack stack suggestコマンドで確認できます。

$ pack stack suggest
Stacks maintained by the community:

    Stack ID: heroku-18
    Description: The official Heroku stack based on Ubuntu 18.04
    Maintainer: Heroku
    Build Image: heroku/pack:18-build
    Run Image: heroku/pack:18

    Stack ID: io.buildpacks.stacks.bionic
    Description: A minimal Paketo stack based on Ubuntu 18.04
    Maintainer: Paketo Project
    Build Image: paketobuildpacks/build:base-cnb
    Run Image: paketobuildpacks/run:base-cnb

    Stack ID: io.buildpacks.stacks.bionic
    Description: A large Paketo stack based on Ubuntu 18.04
    Maintainer: Paketo Project
    Build Image: paketobuildpacks/build:full-cnb
    Run Image: paketobuildpacks/run:full-cnb

    Stack ID: io.paketo.stacks.tiny
    Description: A tiny Paketo stack based on Ubuntu 18.04, similar to distroless
    Maintainer: Paketo Project
    Build Image: paketobuildpacks/build:tiny-cnb
    Run Image: paketobuildpacks/run:tiny-cnb

既にpack inspectコマンドで確認したように、今回作成したイメージにはRun Imageとしてpaketobuildpacks/run:base-cnbが使用されています。

Rebase Stack

例えばRun Imageが更新された場合に、作成したイメージに対してアプリケーションレイヤを変更することなくStackを更新するにはpack rebaseコマンドが使えます。

Image Credit: https://buildpacks.io/docs/concepts/operations/rebase/

次のコマンドを実行してください。

$ pack rebase docker.io/library/vehicle-api:0.0.1-SNAPSHOT  
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:6c5acd07ae6820c606bb4aea6dd76c7db5bafa53b5b3b3f938267779966f8daf
Status: Image is up to date for paketobuildpacks/run:base-cnb
Rebasing docker.io/library/vehicle-api:0.0.1-SNAPSHOT on run image index.docker.io/paketobuildpacks/run:base-cnb
*** Images (660e5dedf241):
      docker.io/library/vehicle-api:0.0.1-SNAPSHOT
Rebased Image: 660e5dedf24107aad36c31585a289cd0849b1bf6709e414d287545ee589fda27
Successfully rebased image docker.io/library/vehicle-api:0.0.1-SNAPSHOT

Image is up to date for paketobuildpacks/run:base-cnbというメッセージが出ているように、このRun Imageは既に最新なので、この例では実質的には変更がありません。Rebase後のイメージに対してpack inspectコマンドを実行すると確かにRun ImageのIDが依然として8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162であることを確認できます。

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT 
Inspecting image: docker.io/library/vehicle-api:0.0.1-SNAPSHOT

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.bionic

Base Image:
  Reference: 8e1ab607b6386d5d9455047383dccecc89cb4ecd8891f18e6aa35e7a686db162
  Top Layer: sha256:10aba31d775e5770b8dc5fc9a28047f71245d8bfdd7e5b7c15035f95ba46b8c5

Run Images:
  index.docker.io/paketobuildpacks/run:base-cnb
  gcr.io/paketo-buildpacks/run:base-cnb

Buildpacks:
  ID                                         VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates          2.1.0          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/bellsoft-liberica        7.1.0          https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/executable-jar           5.0.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/dist-zip                 4.0.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/spring-boot              4.2.0          https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                  SHELL        COMMAND        ARGS
  web (default)                      java           org.springframework.boot.loader.JarLauncher
  executable-jar                     java           org.springframework.boot.loader.JarLauncher
  task                               java           org.springframework.boot.loader.JarLauncher

Update Run Image

ここでRun Imageを自分で更新してみましょう。例えばOpenSSLに脆弱性が見つかり、公式にRun Imageが更新されるよりも先に自分でパッチを当てたいケースを想定します。run.Dockerfileファイルを作成し、次の内容を記述してください。

FROM index.docker.io/paketobuildpacks/run:base-cnb
USER root
RUN apt-get update && \
   apt-get upgrade -y openssl && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/*
USER cnb

次のコマンドを実行して、Run Imageを作成してください。

$ docker build . -t run-image -f run.Dockerfile
[+] Building 14.7s (6/6) FINISHED                                                                                                                                                                                                                                                                  
 => [internal] load build definition from run.Dockerfile                                                                                                                                                                                                                                      0.0s
 => => transferring dockerfile: 233B                                                                                                                                                                                                                                                          0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                             0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                      0.0s
 => CACHED [1/2] FROM docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                                 0.0s
 => [2/2] RUN apt-get update &&     apt-get upgrade -y openssl &&     apt-get clean &&     rm -rf /var/lib/apt/lists/*                                                                                                                                                                       14.5s
 => exporting to image                                                                                                                                                                                                                                                                        0.1s 
 => => exporting layers                                                                                                                                                                                                                                                                       0.1s 
 => => writing image sha256:5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6                                                                                                                                                                                                  0.0s 
 => => naming to docker.io/library/run-image  

pack rebaseコマンドに--run-imageオプションを追加して作成したRun Imageを指定します。

$ pack rebase docker.io/library/vehicle-api:0.0.1-SNAPSHOT --run-image run-image
Rebasing docker.io/library/vehicle-api:0.0.1-SNAPSHOT on run image run-image
*** Images (dc92034129ab):
      docker.io/library/vehicle-api:0.0.1-SNAPSHOT
Rebased Image: dc92034129ab45bcacab0c2f11a7eb96dc5d2fb3aa48077494e6a8bc0188c04a
Successfully rebased image docker.io/library/vehicle-api:0.0.1-SNAPSHOT

pack inspectコマンドを実行するとRun ImageのIDが変わっていることがわかります。

$ pack inspect docker.io/library/vehicle-api:0.0.1-SNAPSHOT                     
Inspecting image: docker.io/library/vehicle-api:0.0.1-SNAPSHOT

REMOTE:
(not present)

LOCAL:

Stack: io.buildpacks.stacks.bionic

Base Image:
  Reference: 5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6
  Top Layer: sha256:7100daa8b77f6e5e0479bebad98656d454298701e0559c69d39155affa300e8c

Run Images:
  index.docker.io/paketobuildpacks/run:base-cnb
  gcr.io/paketo-buildpacks/run:base-cnb

Buildpacks:
  ID                                         VERSION        HOMEPAGE
  paketo-buildpacks/ca-certificates          2.1.0          https://github.com/paketo-buildpacks/ca-certificates
  paketo-buildpacks/bellsoft-liberica        7.1.0          https://github.com/paketo-buildpacks/bellsoft-liberica
  paketo-buildpacks/executable-jar           5.0.0          https://github.com/paketo-buildpacks/executable-jar
  paketo-buildpacks/dist-zip                 4.0.0          https://github.com/paketo-buildpacks/dist-zip
  paketo-buildpacks/spring-boot              4.2.0          https://github.com/paketo-buildpacks/spring-boot

Processes:
  TYPE                  SHELL        COMMAND        ARGS
  web (default)                      java           org.springframework.boot.loader.JarLauncher
  executable-jar                     java           org.springframework.boot.loader.JarLauncher
  task                               java           org.springframework.boot.loader.JarLauncher

次のコマンドを実行して、このIDが自分で作成したイメージのものであることがわかります。

$ docker images --no-trunc | grep 5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6
run-image                  latest                  sha256:5348813eed51ae53b8a1bfb6ca81708125a18e80b425120c97079df461c01dd6   About an hour ago   92.2MB

Build a Image with the updated run image

自分で更新したRun Imageを使用してイメージを作成してみましょう。例えば、デフォルトのRun Imageに使用したいコマンドがインストールされていない場合に追加したいケースを想定します。

run.Dockerfileファイルを次の内容を更新してください。ここではImageMagickのインストールを追加します。

FROM index.docker.io/paketobuildpacks/run:base-cnb
USER root
RUN apt-get update && \
   apt-get install -y --no-install-recommends imagemagick && \
   apt-get clean && \
   rm -rf /var/lib/apt/lists/*
USER cnb

次のコマンドを実行して、Run Imageを作成してください。

$ docker build . -t run-image -f run.Dockerfile
[+] Building 20.0s (6/6) FINISHED                                                                                                                                                                                                                                                                  
 => [internal] load build definition from run.Dockerfile                                                                                                                                                                                                                                      0.0s
 => => transferring dockerfile: 261B                                                                                                                                                                                                                                                          0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                             0.0s
 => => transferring context: 2B                                                                                                                                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                      0.0s
 => CACHED [1/2] FROM docker.io/paketobuildpacks/run:base-cnb                                                                                                                                                                                                                                 0.0s
 => [2/2] RUN apt-get update &&     apt-get install -y --no-install-recommends imagemagick &&     apt-get clean &&     rm -rf /var/lib/apt/lists/*                                                                                                                                           19.6s
 => exporting to image                                                                                                                                                                                                                                                                        0.3s
 => => exporting layers                                                                                                                                                                                                                                                                       0.3s
 => => writing image sha256:6dccf4e29d3a8d0ec13e30a5053c9ddd85cca268565233b64b9362211e22f4f9                                                                                                                                                                                                  0.0s 
 => => naming to docker.io/library/run-image 

Spring Boot Maven PluginでこのRun Imageを使ってイメージをビルドする場合は、spring-boot.build-image.runImageプロパティにイメージ名を指定すれば良いです。なお、Run ImageがLocalにしかない場合はspring-boot.build-image.pullPolicyプロパティにNEVERを指定しないと、Remoteからの取得を試みエラーが発生します。

./mvnw spring-boot:build-image \
 -Dmaven.test.skip=true \
 -Dspring-boot.build-image.runImage=run-image \
 -Dspring-boot.build-image.pullPolicy=NEVER

次のコマンドで作成したイメージを実行して、ImageMagickのconvertコマンドが使用できることを確認してください。

$ docker run --entrypoint bash --rm docker.io/library/vehicle-api:0.0.1-SNAPSHOT -c 'convert --version'
Version: ImageMagick 6.9.7-4 Q16 x86_64 20170114 http://www.imagemagick.org
Copyright: © 1999-2017 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib djvu fftw fontconfig freetype jbig jng jpeg lcms lqr ltdl lzma openexr pangocairo png tiff wmf x xml zlib


各種Buildpackが提供する色々な機能を使うための設定を行います。

JVM Version

コンテナで使われるJVMのバージョンはpack CLIの場合はデフォルトで11、Spring Boot Maven Pluginの場合はpom.xmlで指定されているJavaのバージョンになります。

変更したい場合はビルド時に環境変数BP_JVM_VERSIONを設定してください。

Java 16を使いたい場合は次のように環境変数を設定してください。

pack build vehicle-api:16 \
 --builder paketobuildpacks/builder:base \
 --env BP_JVM_VERSION=16


次のようなログが出力されます。JDK 16(ビルド用)とJRE 16(実行用)がダウンロードされていることがわかります。

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Downloaded newer image for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
...
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              16              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  BellSoft Liberica JDK 16.0.0: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/16+36/bellsoft-jdk16+36-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 16.0.0: Contributing to layer
    Downloading from https://github.com/bell-sw/Liberica/releases/download/16+36/bellsoft-jre16+36-linux-amd64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
...
Saving vehicle-api:16...
*** Images (6cc5e40ea762):
      vehicle-api:16
Adding cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api:16

Spring Boot Maven Pluginの場合は、pom.xmlに次のように設定して./mvnw spring-boot:build-imageを実行してください。

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
     <image>
        <env>
           <BP_JVM_VERSION>16</BP_JVM_VERSION>
        </env>
     </image>
  </configuration>
</plugin>

作成したイメージvehicle-api:16を実行し、ログを確認するとJava 16が使われていることがわかります。

$ docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api:16
Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449786K -XX:MaxMetaspaceSize=86789K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 12909, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449786K -XX:MaxMetaspaceSize=86789K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

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

2021-04-10 15:11:33.843  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 16 on e80d92234e74 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)

...

Memory Calculator

Paketo Buildpackで作成したJavaアプリはメモリ設定が次の図のように自動で行われます。

自動で設定されるのはJavaのMemoryのうちNon Heapの領域です。Non Heapの値が決まった後、

Heap = Container Memory - Non Heap - Headroom

でHeapの最大値(-Xmx)が算出されます。

Non Heapの値は

Non Heap = Metaspace + Reserved CodeCache + Direct Memory + Tread Stack

で決まります。このうち、次項目はデフォルトでは固定値です。

Metaspaceはロードされるクラス数から自動で算出されます。ロードされるクラス数はjarファイルまたはwarファイルに含まれるclassファイルの数 * 0.35で推測されます。

Headroomすなわちコンテナ内でJava以外に使用されるメモリはデフォルトで0です。

ServletアプリではHeapとMetaspace以外に500MB、Spring WebFluxアプリでは300MB固定で確保されることになります。

次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

直後に計算されたメモリ設定が次のようなログとして出力されます。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)

ロードされるクラス数が13028と推測され、そこからMetaspaceの値が自動で87463KBと計算されています。コンテナのメモリは1GBと指定されているので、設定されるHeapサイズは1GB - (87463KB + 500MB) - 0B = 449112KBと算出されます。次の図のような構成になります。

Reduce heap

前述の通り、Non Heapサイズは.classファイル数によって自動で決まるため、同一のjarまたはwarファイルに対しては一定値になります。そのため、コンテナのメモリサイズを減らすと必然的にHeapサイズが減ります。

次のコマンドを実行してください。コンテナのメモリサイズを768MBに減らしました。

docker run --rm \
 -p 8080:8080 \
 -m 768m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx186968K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 768M, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)

コンテナのメモリサイズが1GBの時と比べて、Heapのサイズ(-Xmx)が小さくなっており、それ以外は同じ値であることがわかります。次の図のような構成になります。

ではさらにコンテナのメモリサイズを減らして、512MBにしてみます。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

エラーが発生し、次のようなログが出力されます。

unable to calculate memory configuration
fixed memory regions require 599463K which is greater than 512M available for allocation: -XX:MaxDirectMemorySize=10M, -XX:MaxMetaspaceSize=87463K, -XX:ReservedCodeCacheSize=240M, -Xss1M * 250 threads
ERROR: failed to launch: exec.d: failed to execute exec.d file at path '/layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator': exit status 1

Non Heapに必要なサイズよりもコンテナのメモリサイズが小さく、Heapに割り当てる余地がなくなったためエラーになりました。

Reduce thread count

コンテナサイズを512MBに抑えられるようにNon Heapサイズを変更しましょう。まずはスレッド数を減らしてみます。スレッド数は実行時の環境変数BPL_JVM_THREAD_COUNTで指定できます。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx150104K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 512M, Thread Count: 30, Loaded Class Count: 13028, Headroom: 0%)

Non Heap用のJVMオプションは変わっていませんが、スレッド数が250から30に減り、スレッドスタックサイズ1MB * 220 = 220MB分のメモリをHeapに割り当てる余地ができてため、エラーが発生することなくアプリケーションが起動しました。次の図のような構成になります。

Configure loaded class count

Metaspaceはロードされるクラス数から決まり、ロードされるクラス数は.classファイル数から推測されます。実際にロードされるクラス数が推測された数よりも多い場合または小さい場合は、実行時の環境変数BPL_JVM_LOADED_CLASS_COUNTで明示することができます。

先の例ではクラス数は13028と推測されましたが、ここでは10000を指定します。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e BPL_JVM_LOADED_CLASS_COUNT=10000 \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx167255K -XX:MaxMetaspaceSize=70312K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 512M, Thread Count: 30, Loaded Class Count: 10000, Headroom: 0%)

次の図のような構成になります。

Configure headroom

実行環境によってはJava以外にもメモリが必要になる場合はがあります。JVMはOut of memoryになっていないのにコンテナがOut of memoryになった場合は、Headroomを増やしてください。次のコマンドを実行してください。コンテナのメモリサイズのうち5%をJava以外に割り当てます。

docker run --rm \
 -p 8080:8080 \
 -m 512m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e BPL_JVM_HEADROOM=5 \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx123890K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 512M, Thread Count: 30, Loaded Class Count: 13028, Headroom: 5%)

次の図のような構成になります。

Reduce reserved code cache and thread stack

Non Heapのその他のエリアはデフォルトで固定値が設定されますが、環境変数JAVA_TOOL_OPTIONSでオーバーライドできます。これらの値を小さくして、コンテナのメモリサイズを256MBまで減らしてみます。
次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 256m \
 -e BPL_JVM_THREAD_COUNT=30 \
 -e BPL_JVM_HEADROOM=5 \
 -e JAVA_TOOL_OPTIONS="-XX:ReservedCodeCacheSize=64M -Xss512k" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx70437K -XX:MaxMetaspaceSize=87463K (Total Memory: 256M, Thread Count: 30, Loaded Class Count: 13028, Headroom: 5%)

次の図のような構成になります。

Memory Calculatorの調整方法を説明しました。Memory Calculatorはデフォルトで高負荷時に耐えられる設定を提供してくれます。値を小さくしたい場合は、トレードオフがあることを認識した上で変更してください

Debbuging

Paketo Debug Buildpackを使うと、コンテナのアプリケーションに対してIDEを使ってデバック実行できます。

Debugを有効にするには、ビルド時と実行時の両方に環境変数を設定する必要があります。

まず、コンテナイメージ内にデバッグ実行用のファイル(環境変数JAVA_TOOL_OPTIONSに対してデバッグオプションを追加するコマンド)を追加するためにビルド時に環境変数BP_DEBUG_ENABLEDtrueを設定します。次のコマンドを実行してください。

pack build vehicle-api \
 --builder paketobuildpacks/builder:base \
 --env BP_DEBUG_ENABLED=true


次のようなログが出力されます。Paketo Debug Buildpackに関するログが出力されていることがわかります。

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
===> DETECTING
8 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/maven             5.0.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/apache-tomcat     5.1.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
paketo-buildpacks/debug             3.0.0
===> ANALYZING
...
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  BellSoft Liberica JDK 11.0.10: Reusing cached layer
  BellSoft Liberica JRE 11.0.10: Reusing cached layer
  Launch Helper: Reusing cached layer
  JVMKill Agent 1.16.0: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Maven Buildpack 5.0.0
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:vehicle-api >-----------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.747 s
[INFO] Finished at: 2021-04-10T13:34:12Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.7.1: Reusing cached layer
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version

Paketo Debug Buildpack 3.0.0
  https://github.com/paketo-buildpacks/debug
  Build Configuration:
    $BP_DEBUG_ENABLED   true  whether to contribute debug support
  Launch Configuration:
    $BPL_DEBUG_ENABLED        whether to enable debug support
    $BPL_DEBUG_PORT     8080  what port the debug agent will listen on
    $BPL_DEBUG_SUSPEND  n     whether the JVM will suspend execution until a debugger has attached
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_debug/helper/exec.d/debug
===> EXPORTING
...
Saving vehicle-api...
*** Images (048a01061034):
      vehicle-api
Reusing cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Reusing cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api

Spring Boot Maven Pluginの場合は、pom.xmlに次のように設定して./mvnw spring-boot:build-imageを実行してください。

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
     <image>
        <env>
           <BP_DEBUG_ENABLED>true</BP_DEBUG_ENABLED>
        </env>
     </image>
  </configuration>
</plugin>

次に環境変数BPL_DEBUG_ENABLEDtrueを設定してコンテナを起動します。デバッグ接続用のポートとして8000を公開します。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -p 8000:8000 \
 -m 1g \
 -e BPL_DEBUG_ENABLED=true \
 -e BPL_DEBUG_PORT="*:8000" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api


次のようなログが出力されます。環境変数JAVA_TOOL_OPTIONSにデバッグ用のオプションがついていることがわかります。

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Debugging enabled on port *:8000
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true -agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n
Listening for transport dt_socket at address: 8000

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

IDEからデバック実行します。ソースコードをIDEで開いてください。
IntelliJ IDEAの場合は次のような設定を行い、デバッグ実行してください。

ソースコードにブレークポイントを設定し、そのコードが実行されるようにアプリケーションにアクセスしてください。


Eclipseの場合はこちらのドキュメントを参照してください。

JMX

Paketo JMX Buildpackを使うことで、アプリケーションのJMX設定を有効にします。仕組みはDebug Buildpackとほぼ同じです。

まず、コンテナイメージ内にJMX設定用のファイル(環境変数JAVA_TOOL_OPTIONSに対してJMXオプションを追加するコマンド)を追加するためにビルド時に環境変数BP_JMX_ENABLEDtrueを設定します。次のコマンドを実行してください。

pack build vehicle-api \
 --builder paketobuildpacks/builder:base \
 --env BP_JMX_ENABLED=true

次のようなログが出力されます。Paketo JMX Buildpackに関するログが出力されていることがわかります。

base: Pulling from paketobuildpacks/builder
Digest: sha256:25fc8c5ab1fb229415e58cab0bd1bb1fa8a6b96ba40867cb8e94541ed618de1e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Downloaded newer image for paketobuildpacks/run:base-cnb
===> DETECTING
8 of 18 buildpacks participating
paketo-buildpacks/ca-certificates   2.1.0
paketo-buildpacks/bellsoft-liberica 7.1.0
paketo-buildpacks/maven             5.0.0
paketo-buildpacks/executable-jar    5.0.0
paketo-buildpacks/apache-tomcat     5.1.0
paketo-buildpacks/dist-zip          4.0.0
paketo-buildpacks/spring-boot       4.2.0
paketo-buildpacks/jmx               3.0.0
===> ANALYZING
...
===> RESTORING
Restoring data for "paketo-buildpacks/bellsoft-liberica:jdk" from cache
Restoring data for "paketo-buildpacks/maven:application" from cache
Restoring data for "paketo-buildpacks/maven:cache" from cache
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 7.1.0
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  BellSoft Liberica JDK 11.0.10: Reusing cached layer
  BellSoft Liberica JRE 11.0.10: Reusing cached layer
  Launch Helper: Reusing cached layer
  JVMKill Agent 1.16.0: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Maven Buildpack 5.0.0
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    $BP_MAVEN_BUILD_ARGUMENTS  -Dmaven.test.skip=true package  the arguments to pass to Maven
    $BP_MAVEN_BUILT_ARTIFACT   target/*.[jw]ar                 the built application artifact explicitly.  Supersedes $BP_MAVEN_BUILT_MODULE
    $BP_MAVEN_BUILT_MODULE                                     the module to find application artifact in
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=true package
[INFO] Scanning for projects...
[INFO] 
[INFO] ----------------------< com.example:vehicle-api >-----------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.321 s
[INFO] Finished at: 2021-04-09T13:29:31Z
[INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 5.0.0
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 4.2.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.7.1: Reusing cached layer
  Web Application Type: Reusing cached layer
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.spring-configuration-metadata.json
    org.springframework.boot.version

Paketo JMX Buildpack 3.0.0
  https://github.com/paketo-buildpacks/jmx
  Build Configuration:
    $BP_JMX_ENABLED   true  whether to contribute JMX support
  Launch Configuration:
    $BPL_JMX_ENABLED        whether to enable JMX support
    $BPL_JMX_PORT     5000  what port the JMX Connector will listen on
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_jmx/helper/exec.d/jmx
===> EXPORTING
...
Saving vehicle-api...
*** Images (98b6f417d46c):
      vehicle-api
Reusing cache layer 'paketo-buildpacks/bellsoft-liberica:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Reusing cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api

Spring Boot Maven Pluginの場合は、pom.xmlに次のように設定して./mvnw spring-boot:build-imageを実行してください。

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
     <image>
        <env>
           <BP_JMX_ENABLED>true</BP_JMX_ENABLED>
        </env>
     </image>
  </configuration>
</plugin>

次に環境変数BPL_JMX_ENABLEDtrueを設定してコンテナを起動します。JMX用のポートとして5000を公開します。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -p 5000:5000 \
 -m 1g \
 -e BPL_JMX_ENABLED=true \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api


次のようなログが出力されます。環境変数JAVA_TOOL_OPTIONSにJMX用のオプションがついていることがわかります。

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
JMX enabled on port 5000
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.rmi.port=5000

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

JConsoleを使ってlocalhost:5000にアクセスしてください。

アプリケーションからデータベースへなど外部サービスに接続するための情報を設定する際に環境変数を使うことが多いですが、Bindingは外部サービスのインスタンスをBind可能なリソースとして扱い、このリソースをアプリケーションにBindすることで接続情報を注入する仕組みです。アプリケーション側には明示的に環境変数を設定する必要がありません。

The Twelve-Factor AppIV. Backing servicesの説明で使用されている"Attach"がまさにBindingを表しています。

Image Credit: https://12factor.net/backing-services

Bindingの実装としては

がありますが、前者は後者もカバーし、後者はDeprecatedです。Paketo BuildpackはこのBindingの仕組みに対応しています。様々なサービスへの接続をこのBindingの仕組みで提供します。例えば、Azure Application InsightsGoogle StackdriverのようなAPMへの接続するための設定はこちらです。

サービスへの接続情報は環境変数でなくVolumeMountを経由して取得されます。

PostgreSQL

これまで、アプリケーションがPostgreSQLにアクセスするために環境変数SPRING_DATASOURCE_URLを設定してきましたが、今回はBindingを使用してPostgreSQLへの接続情報を渡します。

Service Binding Specification for Kubernetesの仕様に合わせて、接続情報を作成します。

vehicle-apiディレクトリで次のコマンドを実行してください。

mkdir -p bindings/vehicle-db
echo postgresql > bindings/vehicle-db/type
echo host.docker.internal > bindings/vehicle-db/host
echo 5432 > bindings/vehicle-db/port
echo vehicle > bindings/vehicle-db/username
echo vehicle > bindings/vehicle-db/password
echo vehicle > bindings/vehicle-db/database

次のようなファイル構造になります。vehicle-dbというリソースにBindするための情報がそのディレクトリの下の各ファイルに設定されています。

$ tree bindings 
bindings
`-- vehicle-db
    |-- database
    |-- host
    |-- password
    |-- port
    |-- type
    `-- username

1 directory, 6 files

このファイル構造をSpring Bootのプロパティに変換してくれるライブラリがSpring Cloud Bindingsです。Paketo Java Buildpackでコンテナイメージを作成した場合、Spring Cloud Bindingsのjarは自動で追加されます。

PostgreSQLの接続に対応するプロパティの表は次のリンクを確認してください。

https://github.com/spring-cloud/spring-cloud-bindings#postgresql-rdbms

接続情報を用意したbindingsディレクトリをコンテナの/bindingsにマウントし、このディレクトリをService Binding Specification for Kubernetesの仕様に合わせて環境変数SERVICE_BINDING_ROOTに設定します。次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SERVICE_BINDING_ROOT=/bindings \
 -e MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE="*" \
 -v ${PWD}/bindings:/bindings \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

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

2021-04-10 09:46:07.569  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on b7b649589f37 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-10 09:46:07.571  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 09:46:07.644  INFO 1 --- [           main] .BindingSpecificEnvironmentPostProcessor : Creating binding-specific PropertySource from Kubernetes Service Bindings
2021-04-10 09:46:08.612  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-10 09:46:08.621  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-10 09:46:08.622  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 09:46:08.663  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-10 09:46:08.664  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1006 ms
2021-04-10 09:46:08.853  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 09:46:08.953  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 09:46:09.111  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 09:46:09.373  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 13 endpoint(s) beneath base path '/actuator'
2021-04-10 09:46:09.409  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 09:46:09.421  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 2.25 seconds (JVM running for 2.582)

アプリケーションが起動していれば、Bindingを使用したPostgreSQLへの接続に成功しています。

Spring Cloud Bindingsによって自動設定されたプロパティは次のコマンドで確認できます。

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"kubernetesServiceBindingSpecific\")" 
{
  "name": "kubernetesServiceBindingSpecific",
  "properties": {
    "spring.datasource.driver-class-name": {
      "value": "org.postgresql.Driver"
    },
    "spring.datasource.username": {
      "value": "vehicle"
    },
    "spring.datasource.url": {
      "value": "jdbc:postgresql://host.docker.internal:5432/vehicle"
    },
    "spring.r2dbc.password": {
      "value": "******"
    },
    "spring.r2dbc.url": {
      "value": "r2dbc:postgresql://host.docker.internal:5432/vehicle"
    },
    "spring.r2dbc.username": {
      "value": "vehicle"
    },
    "spring.datasource.password": {
      "value": "******"
    }
  }
}

また、次のBindingに関する次のプロパティも設定されます。

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"kubernetesServiceBindingFlattened\")"
{
  "name": "kubernetesServiceBindingFlattened",
  "properties": {
    "k8s.bindings.vehicle-db.password": {
      "value": "******"
    },
    "k8s.bindings.vehicle-db.host": {
      "value": "host.docker.internal"
    },
    "k8s.bindings.vehicle-db.port": {
      "value": "5432"
    },
    "k8s.bindings.vehicle-db.database": {
      "value": "vehicle"
    },
    "k8s.bindings.vehicle-db.username": {
      "value": "vehicle"
    }
  }
}

CA certificates

次は信頼されたCA証明書をBindingの仕組みでコンテナに渡します。vehicle-apiがPostgreSQLに対してTLSで接続するように、PostgreSQLサーバーに設定する証明書のCAを信頼します。

まずはPostgreSQLを自己署名のTLSに対応させます。vehicle-apiディレクトリの下にcertsディレクトリを作成し、そのディレクトリ内にgenerate-certs.shというファイルを作成し、次の内容を記述してください。

#!/bin/bash
set -ex

cd /certs
openssl req -new -nodes -out root.csr \
 -keyout root.key -subj "/CN=docker.internal"
chmod og-rwx root.key

openssl x509 -req -in root.csr -days 3650 \
 -extfile /etc/ssl/openssl.cnf -extensions v3_ca \
 -signkey root.key -out root.crt

openssl req -new -nodes -out server.csr \
 -keyout server.key -subj "/CN=host.docker.internal"
chmod og-rwx server.key

openssl x509 -req -in server.csr -days 3650 \
 -CA root.crt -CAkey root.key -CAcreateserial \
 -out server.crt

vehicle-apiディレクトリで次のコマンドを実行してください。

docker run --rm \
 -v ${PWD}/certs:/certs \
 paketobuildpacks/run:base-cnb \
 sh /certs/generate-certs.sh

次のようなログが出力されます。

+ cd /certs
+ openssl req -new -nodes -out root.csr -keyout root.key -subj /CN=docker.internal
Can't load /home/cnb/.rnd into RNG
139648000373184:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/cnb/.rnd
Generating a RSA private key
.+++++
..................+++++
writing new private key to 'root.key'
-----
+ chmod og-rwx root.key
+ openssl x509 -req -in root.csr -days 3650 -extfile /etc/ssl/openssl.cnf -extensions v3_ca -signkey root.key -out root.crt
Signature ok
subject=CN = docker.internal
Getting Private key
+ openssl req -new -nodes -out server.csr -keyout server.key -subj /CN=host.docker.internal
Can't load /home/cnb/.rnd into RNG
140412413399488:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/cnb/.rnd
Generating a RSA private key
.........+++++
....+++++
writing new private key to 'server.key'
-----
+ chmod og-rwx server.key
+ openssl x509 -req -in server.csr -days 3650 -CA root.crt -CAkey root.key -CAcreateserial -out server.crt
Signature ok
subject=CN = host.docker.internal
Getting CA Private Key

certsディレクトリ以下に次のファイルが作成されていることを確認してください。

$ ls -l certs 
total 64
-rw-r--r--  1 toshiaki  staff   499 Apr 10 02:14 generate-certs.sh
-rw-r--r--  1 toshiaki  staff  1131 Apr 10 02:14 root.crt
-rw-r--r--  1 toshiaki  staff   899 Apr 10 02:14 root.csr
-rw-------  1 toshiaki  staff  1704 Apr 10 02:14 root.key
-rw-r--r--  1 toshiaki  staff    41 Apr 10 02:14 root.srl
-rw-r--r--  1 toshiaki  staff  1013 Apr 10 02:14 server.crt
-rw-r--r--  1 toshiaki  staff   907 Apr 10 02:14 server.csr
-rw-------  1 toshiaki  staff  1708 Apr 10 02:14 server.key

この証明書を使い、PostgreSQLをTLS対応させます。次のコマンドを実行してください。

docker run --rm \
 -p 5432:5432 \
 -e POSTGRES_DB=vehicle \
 -e POSTGRES_USER=vehicle \
 -e POSTGRES_PASSWORD=vehicle \
 -e POSTGRESQL_ENABLE_TLS=yes \
 -e POSTGRESQL_TLS_CERT_FILE=/certs/server.crt \
 -e POSTGRESQL_TLS_KEY_FILE=/certs/server.key \
 -e POSTGRESQL_TLS_CA_FILE=/certs/root.crt \
 -e POSTGRESQL_PGHBA_REMOVE_FILTERS=hostssl \
 -v ${PWD}/certs:/certs \
 bitnami/postgresql:11.11.0-debian-10-r59

まずはBindingなしで、PostgreSQLにTLSで接続します。次のコマンドを実行してください。sslModeがverify-fullなので、接続するサーバーが信頼されていないといけません。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE=verify-full \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY=org.postgresql.ssl.DefaultJavaSSLFactory \
 -e SERVICE_BINDING_ROOT=/bindings \
 -v ${PWD}/bindings:/bindings \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

自己署名の証明書は信頼されていないため、次のログのようにunable to find valid certification path to requested targetというエラーメッセージが出力されます。

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

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

2021-04-10 09:50:49.865  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on 74e090162265 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-10 09:50:49.868  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 09:50:49.917  INFO 1 --- [           main] .BindingSpecificEnvironmentPostProcessor : Creating binding-specific PropertySource from Kubernetes Service Bindings
2021-04-10 09:50:50.881  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-10 09:50:50.891  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-10 09:50:50.892  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 09:50:50.938  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-10 09:50:50.938  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1020 ms
2021-04-10 09:50:51.142  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 09:50:52.426 ERROR 1 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Exception during pool initialization.

org.postgresql.util.PSQLException: SSL error: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at org.postgresql.ssl.MakeSSL.convert(MakeSSL.java:43) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.v3.ConnectionFactoryImpl.enableSSL(ConnectionFactoryImpl.java:534) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:149) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:213) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:51) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:223) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.Driver.makeConnection(Driver.java:465) ~[postgresql-42.2.19.jar:42.2.19]
        at org.postgresql.Driver.connect(Driver.java:264) ~[postgresql-42.2.19.jar:42.2.19]
        at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138) ~[HikariCP-3.4.5.jar:na]
...

次にこのCA証明書を信頼するようにBindingを作成します。次のコマンドを実行してください。CA Certificates Buildpackの仕様に合わせます。

mkdir -p bindings/trusted-certs
echo ca-certificates > bindings/trusted-certs/type
cp certs/root.crt bindings/trusted-certs/

次のようなファイル構造になります。

$ tree bindings 
bindings
|-- trusted-certs
|   |-- root.crt
|   `-- type
`-- vehicle-db
    |-- database
    |-- host
    |-- password
    |-- port
    |-- type
    `-- username

2 directories, 8 files

再度次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 1g \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE=verify-full \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY=org.postgresql.ssl.DefaultJavaSSLFactory \
 -e SERVICE_BINDING_ROOT=/bindings \
 -v ${PWD}/bindings:/bindings \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

Added 1 additional CA certificate(s) to system truststore
Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13028, Headroom: 0%)
Adding 130 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_bellsoft-liberica/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx449112K -XX:MaxMetaspaceSize=87463K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true

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

2021-04-10 09:52:56.993  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication v0.0.1-SNAPSHOT using Java 11.0.10 on b46e4ab3e521 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2021-04-10 09:52:56.996  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 09:52:57.046  INFO 1 --- [           main] .BindingSpecificEnvironmentPostProcessor : Creating binding-specific PropertySource from Kubernetes Service Bindings
2021-04-10 09:52:58.047  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-04-10 09:52:58.056  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-04-10 09:52:58.057  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 09:52:58.098  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-04-10 09:52:58.098  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1051 ms
2021-04-10 09:52:58.307  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 09:52:58.640  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 09:52:58.815  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 09:52:59.109  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-10 09:52:59.186  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 09:52:59.197  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 2.622 seconds (JVM running for 2.97)

2021-04-09 17:25:30.410  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1009 ms
2021-04-09 17:25:30.608  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-09 17:25:30.941  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-09 17:25:31.097  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-09 17:25:31.429  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-04-09 17:25:31.474  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-09 17:25:31.487  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 2.493 seconds (JVM running for 2.79)

これまでAdding 129 container CA certificates to JVM truststoreというメッセージが出力されていましたが、今回はAdding 130 container CA certificates to JVM truststoreというメッセージが出力され、1件CA証明書がTruststoreに追加されたことが分かります。アプリケーションも起動し、PostgreSQLにsslModeをverify-fullの設定で接続することができました。

Paketo BuildpackのJava Buildpackでは環境変数SSL_CERT_DIRに追加したいCA証明書のディレクトリを指定すれば、そのディレクトリ内の証明書を起動時にJavaのTruststoreに追加してくれます。CA certificate Buildpackはtypeがca-certificatesなBindingがある場合に、その値を環境変数SSL_CERT_DIRに追加してくれます。

PostgreSQLのServiceをBindした際とCA CertificatesのService Bindをした際にアプリケーションに設定するService Binidngに関する環境変数は変わっていません。Bindする情報が増えただけで、この情報は自動でアプリケーションに注入されます。

GraalVMを使うことでJavaアプリケーションをnative imageにコンパイルでき、JVMに比べてアプリケーションの起動を非常に高速にすることができます。
Spring Nativeを使うことでSpring Bootアプリケーションがnative image化する場合に必要な作業を大幅に省略できるようになります。vehicle-apiをGraal VMでnative image化しましょう。

Paketo GraalVM Buildpackを使うことでLaptop上にGraalVMをインストールすることなく、native imageのコンテナイメージを作成できます。

Spring Nativeを使うために、アプリケーションのpom.xmlを更新します。次のコマンドを実行してください。

curl -s https://start.spring.io/pom.xml \
 -d artifactId=vehicle-api \
 -d baseDir=vehicle-api \
 -d dependencies=web,jdbc,postgresql,actuator,native \
 -d packageName=com.example >pom.xml

spring-nativeライブラリの追加と、paketobuildpacks/builder:tinyを使用するための設定に更新されます。

次のコマンドを実行してください。

./mvnw clean spring-boot:build-image -Dmaven.test.skip=true

次のようなログが出力がされます。GraalVMがダウンロードされ、native imageを作成するための処理が実行されていることがわかります。

実行環境のSpecによって異なりますが、ビルド時間は5分以上になるでしょう。

[INFO] Scanning for projects...
...
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.4.5-SNAPSHOT:build-image (default-cli) < package @ vehicle-api <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.4.5-SNAPSHOT:build-image (default-cli) @ vehicle-api ---
[INFO] Building image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO]  > Pulling builder image 'docker.io/paketobuildpacks/builder:tiny' 100%
[INFO]  > Pulled builder image 'paketobuildpacks/builder@sha256:e1b1c8f5e76b266924aae21d93dd14e351145b5106f39ba71b2cdbb2a5bf3758'
[INFO]  > Pulling run image 'docker.io/paketobuildpacks/run:tiny-cnb' 100%
[INFO]  > Pulled run image 'paketobuildpacks/run@sha256:1ab5cd68c56bceecb3127a75bc3cb167cbc7f117f1808f02c41f93d7a84e2b92'
[INFO]  > Executing lifecycle version v0.11.1
[INFO]  > Using build cache volume 'pack-cache-13a8f10fbd06.build'
[INFO] 
[INFO]  > Running creator
[INFO]     [creator]     ===> DETECTING
[INFO]     [creator]     4 of 11 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/graalvm        6.0.0
[INFO]     [creator]     paketo-buildpacks/executable-jar 5.0.0
[INFO]     [creator]     paketo-buildpacks/spring-boot    4.2.0
[INFO]     [creator]     paketo-buildpacks/native-image   4.0.0
[INFO]     [creator]     ===> ANALYZING
[INFO]     [creator]     Restoring metadata for "paketo-buildpacks/spring-boot:helper" from app image
[INFO]     [creator]     Restoring metadata for "paketo-buildpacks/spring-boot:spring-cloud-bindings" from app image
[INFO]     [creator]     Restoring metadata for "paketo-buildpacks/spring-boot:web-application-type" from app image
[INFO]     [creator]     ===> RESTORING
[INFO]     [creator]     ===> BUILDING
[INFO]     [creator]     
[INFO]     [creator]     Paketo GraalVM Buildpack 6.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/graalvm
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_JVM_VERSION              11.*            the Java version
[INFO]     [creator]       Launch Configuration:
[INFO]     [creator]         $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
[INFO]     [creator]         $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
[INFO]     [creator]         $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
[INFO]     [creator]         $JAVA_TOOL_OPTIONS                           the JVM launch flags
[INFO]     [creator]       GraalVM JDK 11.0.10: Contributing to layer
[INFO]     [creator]         Downloading from https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/graalvm-ce-java11-linux-amd64-21.0.0.2.tar.gz
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Expanding to /layers/paketo-buildpacks_graalvm/jdk
[INFO]     [creator]         Adding 129 container CA certificates to JVM truststore
[INFO]     [creator]       GraalVM Native Image Substrate VM 11.0.10
[INFO]     [creator]         Downloading from https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-21.0.0.2/native-image-installable-svm-java11-linux-amd64-21.0.0.2.jar
[INFO]     [creator]         Verifying checksum
[INFO]     [creator]         Installing substrate VM
[INFO]     [creator]     Processing Component archive: /tmp/24e5a08e2714aee343b22c266285090721ff882ab0a31b7e8e4a68585c38f421/native-image-installable-svm-java11-linux-amd64-21.0.0.2.jar
[INFO]     [creator]     Installing new component: Native Image (org.graalvm.native-image, version 21.0.0.2)
[INFO]     [creator]         Writing env.build/JAVA_HOME.override
[INFO]     [creator]         Writing env.build/JDK_HOME.override
[INFO]     [creator]     
[INFO]     [creator]     Paketo Executable JAR Buildpack 5.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/executable-jar
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env.build/CLASSPATH.delim
[INFO]     [creator]         Writing env.build/CLASSPATH.prepend
[INFO]     [creator]     
[INFO]     [creator]     Paketo Spring Boot Buildpack 4.2.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/spring-boot
[INFO]     [creator]       Class Path: Contributing to layer
[INFO]     [creator]         Writing env.build/CLASSPATH.append
[INFO]     [creator]         Writing env.build/CLASSPATH.delim
[INFO]     [creator]       Image labels:
[INFO]     [creator]         org.opencontainers.image.title
[INFO]     [creator]         org.opencontainers.image.version
[INFO]     [creator]         org.springframework.boot.spring-configuration-metadata.json
[INFO]     [creator]         org.springframework.boot.version
[INFO]     [creator]     
[INFO]     [creator]     Paketo Native Image Buildpack 4.0.0
[INFO]     [creator]       https://github.com/paketo-buildpacks/native-image
[INFO]     [creator]       Build Configuration:
[INFO]     [creator]         $BP_NATIVE_IMAGE                  true  enable native image build
[INFO]     [creator]         $BP_NATIVE_IMAGE_BUILD_ARGUMENTS        arguments to pass to the native-image command
[INFO]     [creator]       Native Image: Contributing to layer
[INFO]     [creator]         GraalVM Version 21.0.0.2 (Java Version 11.0.10+8-jvmci-21.0-b06)
[INFO]     [creator]         Executing native-image -H:+StaticExecutableWithDynamicLibC -H:Name=/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication -cp /workspace:/workspace/BOOT-INF/classes:/workspace/BOOT-INF/lib/spring-boot-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-boot-autoconfigure-2.4.5-SNAPSHOT.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/log4j-to-slf4j-2.13.3.jar:/workspace/BOOT-INF/lib/log4j-api-2.13.3.jar:/workspace/BOOT-INF/lib/jul-to-slf4j-1.7.30.jar:/workspace/BOOT-INF/lib/jakarta.annotation-api-1.3.5.jar:/workspace/BOOT-INF/lib/snakeyaml-1.27.jar:/workspace/BOOT-INF/lib/spring-boot-actuator-autoconfigure-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-boot-actuator-2.4.5-SNAPSHOT.jar:/workspace/BOOT-INF/lib/jackson-databind-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-annotations-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-core-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-datatype-jsr310-2.11.4.jar:/workspace/BOOT-INF/lib/micrometer-core-1.6.5.jar:/workspace/BOOT-INF/lib/HdrHistogram-2.1.12.jar:/workspace/BOOT-INF/lib/LatencyUtils-2.0.3.jar:/workspace/BOOT-INF/lib/HikariCP-3.4.5.jar:/workspace/BOOT-INF/lib/slf4j-api-1.7.30.jar:/workspace/BOOT-INF/lib/spring-jdbc-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-beans-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-tx-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/jackson-datatype-jdk8-2.11.4.jar:/workspace/BOOT-INF/lib/jackson-module-parameter-names-2.11.4.jar:/workspace/BOOT-INF/lib/tomcat-embed-core-9.0.44.jar:/workspace/BOOT-INF/lib/jakarta.el-3.0.3.jar:/workspace/BOOT-INF/lib/tomcat-embed-websocket-9.0.44.jar:/workspace/BOOT-INF/lib/spring-web-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-webmvc-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-aop-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-context-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-expression-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-native-0.9.2-SNAPSHOT.jar:/workspace/BOOT-INF/lib/postgresql-42.2.19.jar:/workspace/BOOT-INF/lib/checker-qual-3.5.0.jar:/workspace/BOOT-INF/lib/spring-core-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-jcl-5.3.6-SNAPSHOT.jar:/workspace/BOOT-INF/lib/spring-boot-jarmode-layertools-2.4.5-SNAPSHOT.jar com.example.VehicleApiApplication
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]    classlist:   3,797.93 ms,  0.96 GB
[INFO]     [creator]     Warning: class initialization of class org.springframework.boot.validation.MessageInterpolatorFactory failed with exception java.lang.NoClassDefFoundError: javax/validation/ValidationException. This class will be initialized at run time because option --allow-incomplete-classpath is used for image building. Use the option --initialize-at-run-time=org.springframework.boot.validation.MessageInterpolatorFactory to explicitly request delayed initialization of this class.
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        (cap):     458.32 ms,  1.19 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        setup:   1,912.29 ms,  1.19 GB
[INFO]     [creator]     WARNING: Could not register reflection metadata for org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Dbcp2. Reason: java.lang.NoClassDefFoundError: org/apache/commons/dbcp2/BasicDataSource.
...
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     (clinit):   1,720.26 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]   (typeflow):  38,729.68 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]    (objects):  29,370.84 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]   (features):   4,312.88 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     analysis:  76,526.02 ms,  4.09 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     universe:   2,836.68 ms,  4.06 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]      (parse):  14,582.05 ms,  4.97 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]     (inline):   9,640.34 ms,  5.72 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]    (compile):  41,254.15 ms,  6.04 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]      compile:  69,025.23 ms,  6.04 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        image:   7,755.51 ms,  5.95 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]        write:     886.82 ms,  5.95 GB
[INFO]     [creator]     [/layers/paketo-buildpacks_native-image/native-image/com.example.VehicleApiApplication:159]      [total]: 163,358.86 ms,  5.95 GB
[INFO]     [creator]       Removing bytecode
[INFO]     [creator]       Process types:
[INFO]     [creator]         native-image: /workspace/com.example.VehicleApiApplication (direct)
[INFO]     [creator]         task:         /workspace/com.example.VehicleApiApplication (direct)
[INFO]     [creator]         web:          /workspace/com.example.VehicleApiApplication (direct)
[INFO]     [creator]     ===> EXPORTING
[INFO]     [creator]     Adding 1/1 app layer(s)
[INFO]     [creator]     Reusing layer 'launcher'
[INFO]     [creator]     Adding layer 'config'
[INFO]     [creator]     Adding layer 'process-types'
[INFO]     [creator]     Adding label 'io.buildpacks.lifecycle.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.build.metadata'
[INFO]     [creator]     Adding label 'io.buildpacks.project.metadata'
[INFO]     [creator]     Adding label 'org.opencontainers.image.title'
[INFO]     [creator]     Adding label 'org.opencontainers.image.version'
[INFO]     [creator]     Adding label 'org.springframework.boot.spring-configuration-metadata.json'
[INFO]     [creator]     Adding label 'org.springframework.boot.version'
[INFO]     [creator]     Setting default process type 'web'
[INFO]     [creator]     Saving docker.io/library/vehicle-api:0.0.1-SNAPSHOT...
[INFO]     [creator]     *** Images (e4e8ed011ce3):
[INFO]     [creator]           docker.io/library/vehicle-api:0.0.1-SNAPSHOT
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/graalvm:jdk'
[INFO]     [creator]     Adding cache layer 'paketo-buildpacks/native-image:native-image'
[INFO] 
[INFO] Successfully built image 'docker.io/library/vehicle-api:0.0.1-SNAPSHOT'
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:48 min
[INFO] Finished at: 2021-04-11T00:37:47+09:00
[INFO] ------------------------------------------------------------------------

次のコマンドを実行して、作成したイメージを確認してください。paketobuildpacks/builder:tiny Builderはベースイメージとして最小構成のUbuntuを使用するため、JVMを使ったイメージよりもサイズが小さくなります。

$ docker images | grep vehicle
vehicle-api                     0.0.1-SNAPSHOT          e4e8ed011ce3   41 years ago   112MB
vehicle-api                     16                      6cc5e40ea762   41 years ago   291MB
vehicle-api                     latest                  048a01061034   41 years ago   266MB

次のコマンドを実行してください。native imageはJVMと比べて消費メモリも小さいので、ここではメモリサイズを150MBにします。

docker run --rm \
 -p 8080:8080 \
 -m 150m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。非常に高速に起動していることを確認してください。

2021-04-10 15:58:55.798  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.4.5-SNAPSHOT)

2021-04-10 15:58:55.799  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication using Java 11.0.10 on 5b6ce3d3203e with PID 1 (/workspace/com.example.VehicleApiApplication started by cnb in /workspace)
2021-04-10 15:58:55.800  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 15:58:55.848  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
Apr 10, 2021 3:58:55 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
Apr 10, 2021 3:58:55 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Apr 10, 2021 3:58:55 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 15:58:55.850  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 50 ms
Apr 10, 2021 3:58:55 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
2021-04-10 15:58:55.853  WARN 1 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2021-04-10 15:58:55.865  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 15:58:55.875  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 15:58:55.895  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 15:58:55.910  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
Apr 10, 2021 3:58:55 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-04-10 15:58:55.916  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 15:58:55.917  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 0.123 seconds (JVM running for 0.125)

次はPostgreSQLに対してTLSで通信します。TLSを使うにはGraalVMのnative-imageオプション--enable-all-security-servicesをつける必要があります。Spring Nativeでは@NativeHintアノテーションでnative-imageのオプションを指定できるので、src/main/java/com/example/VehicleApplication.javaを次のように編集してください。

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.nativex.hint.NativeHint;

@SpringBootApplication
@NativeHint(options = { "--enable-all-security-services" })
public class VehicleApiApplication {

  public static void main(String[] args) {
     SpringApplication.run(VehicleApiApplication.class, args);
  }

}

再度、次のコマンドを実行してください。

./mvnw clean spring-boot:build-image -Dmaven.test.skip=true

Buildpackでnative imageを作る場合、現時点ではこれまで紹介してきたBuildpackの機能やService Bindingは使えません。CA証明書の設定はPostgreSQL JDBC Driverの機能を使用しします。sslFactoryorg.postgresql.ssl.LibPQFactory(デフォルト)に変更し、$HOME/.postgreql/root.crtに配置します。

次のコマンドを実行してください。

docker run --rm \
 -p 8080:8080 \
 -m 150m \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLMODE=verify-full \
 -e SPRING_DATASOURCE_HIKARI_DATASOURCEPROPERTIES_SSLFACTORY=org.postgresql.ssl.LibPQFactory \
 -v ${PWD}/certs/root.crt:/home/cnb/.postgresql/root.crt \
 docker.io/library/vehicle-api:0.0.1-SNAPSHOT

次のようなログが出力されます。

2021-04-10 16:37:08.722  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.4.5-SNAPSHOT)

2021-04-10 16:37:08.723  INFO 1 --- [           main] com.example.VehicleApiApplication        : Starting VehicleApiApplication using Java 11.0.10 on 98e28fb9c99c with PID 1 (/workspace/com.example.VehicleApiApplication started by cnb in /workspace)
2021-04-10 16:37:08.723  INFO 1 --- [           main] com.example.VehicleApiApplication        : No active profile set, falling back to default profiles: default
2021-04-10 16:37:08.778  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
Apr 10, 2021 4:37:08 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
Apr 10, 2021 4:37:08 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Apr 10, 2021 4:37:08 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.44]
2021-04-10 16:37:08.779  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 55 ms
Apr 10, 2021 4:37:08 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring embedded WebApplicationContext
2021-04-10 16:37:08.782  WARN 1 --- [           main] i.m.c.i.binder.jvm.JvmGcMetrics          : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2021-04-10 16:37:08.795  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-04-10 16:37:08.824  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-04-10 16:37:08.843  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-10 16:37:08.859  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
Apr 10, 2021 4:37:08 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
2021-04-10 16:37:08.865  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-10 16:37:08.866  INFO 1 --- [           main] com.example.VehicleApiApplication        : Started VehicleApiApplication in 0.148 seconds (JVM running for 0.15)

アプリケーションにアクセスできることを確認してください。

Paketo BuildpacksのBuilderでJavaアプリのコンテナイメージを作る場合はデフォルトでPaketo BellSoft Liberica Buildpackが使われ、Bellsoft LibericaのJREが組み込まれます。その他のDistributionを使う方法を紹介します。

Adopt OpenJDK

Paketo AdoptOpenJDK Buildpackを使うことでAdopt OpenJDKを使うことができます。Builderから提供されるBuildpack一式を使わず、使用するBuildpackをpack CLIに全て渡すことで差し替えることができます。

次のコマンドを実行してください。

pack build vehicle-api:adopt-openjdk \
 --builder paketobuildpacks/builder:base \
 --buildpack paketo-buildpacks/ca-certificates \
 --buildpack paketo-buildpacks/adopt-openjdk \
 --buildpack paketo-buildpacks/maven \
 --buildpack paketo-buildpacks/executable-jar \
 --buildpack paketo-buildpacks/spring-boot

次のようなログが出力されます。Adopt OpenJDKがダウンロードされていることが確認できます。

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
gcr.io/paketo-buildpacks/adopt-openjdk@sha256:9fd288108e7b34bea89986446966f885bbf08e7dd80d07277ff3b229549fb6da: Pulling from paketo-buildpacks/adopt-openjdk
Digest: sha256:9fd288108e7b34bea89986446966f885bbf08e7dd80d07277ff3b229549fb6da
Status: Image is up to date for gcr.io/paketo-buildpacks/adopt-openjdk@sha256:9fd288108e7b34bea89986446966f885bbf08e7dd80d07277ff3b229549fb6da
===> DETECTING
paketo-buildpacks/ca-certificates 2.1.0
paketo-buildpacks/adopt-openjdk   7.1.0
paketo-buildpacks/maven           5.0.0
paketo-buildpacks/executable-jar  5.0.0
paketo-buildpacks/spring-boot     4.2.0
===> ANALYZING
Previous image with name "vehicle-api:adopt-openjdk" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo AdoptOpenJDK Buildpack 7.1.0
  https://github.com/paketo-buildpacks/adopt-openjdk
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  AdoptOpenJDK JDK 11.0.10: Contributing to layer
    Downloading from https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_adopt-openjdk/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  AdoptOpenJDK JRE 11.0.10: Contributing to layer
    Downloading from https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jre_x64_linux_hotspot_11.0.10_9.tar.gz


    Verifying checksum
    Expanding to /layers/paketo-buildpacks_adopt-openjdk/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_adopt-openjdk/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_adopt-openjdk/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
...
Saving vehicle-api:adopt-openjdk...
*** Images (d3ae69cce52e):
      vehicle-api:adopt-openjdk
Adding cache layer 'paketo-buildpacks/adopt-openjdk:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api:adopt-openjdk


次のコマンドを実行してください。

docker run --rm -p 8080:8080 \
 -m 1g \
 -e MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE="*" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api:adopt-openjdk

次のようなログが出力されます。Adopt OpenJDKが使用されていることがわかります。

Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx448954K -XX:MaxMetaspaceSize=87621K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13056, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_adopt-openjdk/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_adopt-openjdk/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx448954K -XX:MaxMetaspaceSize=87621K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
...

次のコマンドを実行してください。実際に稼働しているJavaのベンダー名やバージョンを確認できます。

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vendor.version\"]"
{
  "value": "AdoptOpenJDK"
}

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vm.version\"]"
{
  "value": "11.0.10+9"
}

Azul Zulu

Paketo Azul Zulu Buildpackを使うことでAzul Zuluを使うことができます。Adopt OpenJDKの場合と同じように次のコマンドで利用可能です。

pack build vehicle-api:azul \
 --builder paketobuildpacks/builder:base \
 --buildpack paketo-buildpacks/ca-certificates \
 --buildpack paketo-buildpacks/azul-zulu \
 --buildpack paketo-buildpacks/maven \
 --buildpack paketo-buildpacks/executable-jar \
 --buildpack paketo-buildpacks/spring-boot

Azul Zuluの場合は、Buildpackの集合であるMeta BuildpackのPaketo Azure Java Buildpackに含まれています。このBuildpackはAzure上で使うことを想定したBuildpack一式を定義してうため、Buildpackを一つ一つ指定する必要がなくなります。次のコマンドを実行してください。

次のコマンドを実行してください。

pack build vehicle-api:azul \
 --builder paketobuildpacks/builder:base \
 --buildpack paketo-buildpacks/java-azure

次のようなログが出力されます。Azul Zuluがダウンロードされていることがわかります。

base: Pulling from paketobuildpacks/builder
Digest: sha256:7f2b74ba47b235e5a30393ad83c3e3c12ff5229c190faffd67d842e064b7743e
Status: Image is up to date for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
Digest: sha256:aa9a8a0a4bb607422d3ff59923638f2a6cf3d1e6b404aee9ae027127ea88a5c1
Status: Image is up to date for paketobuildpacks/run:base-cnb
gcr.io/paketo-buildpacks/java-azure@sha256:4109b133b548c4eea85c84a082d64da64609714d105e13b5fdae57689d693f5c: Pulling from paketo-buildpacks/java-azure
Digest: sha256:4109b133b548c4eea85c84a082d64da64609714d105e13b5fdae57689d693f5c
Status: Downloaded newer image for gcr.io/paketo-buildpacks/java-azure@sha256:4109b133b548c4eea85c84a082d64da64609714d105e13b5fdae57689d693f5c
===> DETECTING
7 of 17 buildpacks participating
paketo-buildpacks/ca-certificates 2.1.0
paketo-buildpacks/azul-zulu       7.1.0
paketo-buildpacks/maven           5.0.0
paketo-buildpacks/executable-jar  5.0.0
paketo-buildpacks/apache-tomcat   5.1.0
paketo-buildpacks/dist-zip        4.0.0
paketo-buildpacks/spring-boot     4.2.0
===> ANALYZING
Previous image with name "vehicle-api:azul" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.1.0
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo Azul Zulu Buildpack 7.1.0
  https://github.com/paketo-buildpacks/azul-zulu
  Build Configuration:
    $BP_JVM_VERSION              11              the Java version
  Launch Configuration:
    $BPL_JVM_HEAD_ROOM           0               the headroom in memory calculation
    $BPL_JVM_LOADED_CLASS_COUNT  35% of classes  the number of loaded classes in memory calculation
    $BPL_JVM_THREAD_COUNT        250             the number of threads in memory calculation
    $JAVA_TOOL_OPTIONS                           the JVM launch flags
  Azul Zulu JDK 11.0.10: Contributing to layer
    Downloading from https://repos.azul.com/azure-only/zulu/packages/zulu-11/11.0.10/zulu-11-azure-jdk_11.45.27-11.0.10-linux_x64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_azul-zulu/jdk
    Adding 129 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  Azul Zulu JRE 11.0.10: Contributing to layer
    Downloading from https://repos.azul.com/azure-only/zulu/packages/zulu-11/11.0.10/zulu-11-azure-jre_11.45.27-11.0.10-linux_x64.tar.gz
    Verifying checksum
    Expanding to /layers/paketo-buildpacks_azul-zulu/jre
    Adding 129 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_azul-zulu/helper/exec.d/security-providers-classpath-9
  JVMKill Agent 1.16.0: Contributing to layer
    Downloading from https://github.com/cloudfoundry/jvmkill/releases/download/v1.16.0.RELEASE/jvmkill-1.16.0-RELEASE.so
    Verifying checksum
    Copying to /layers/paketo-buildpacks_azul-zulu/jvmkill
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim

...

Saving vehicle-api:azul...
*** Images (7928ea4ea26a):
      vehicle-api:azul
Adding cache layer 'paketo-buildpacks/azul-zulu:jdk'
Adding cache layer 'paketo-buildpacks/maven:application'
Adding cache layer 'paketo-buildpacks/maven:cache'
Successfully built image vehicle-api:azul

次のコマンドを実行してください。

docker run --rm -p 8080:8080 \
 -m 1g \
 -e MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE="*" \
 -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/vehicle \
 vehicle-api:azul

次のようなログが出力されます。Azul Zuluがダウンロードされていることがわかります。

Setting Active Processor Count to 4
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx447509K -XX:MaxMetaspaceSize=89066K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1G, Thread Count: 250, Loaded Class Count: 13311, Headroom: 0%)
Adding 129 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_azul-zulu/java-security-properties/java-security.properties -agentpath:/layers/paketo-buildpacks_azul-zulu/jvmkill/jvmkill-1.16.0-RELEASE.so=printHeapHistogram=1 -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx447509K -XX:MaxMetaspaceSize=89066K -XX:ReservedCodeCacheSize=240M -Xss1M -Dorg.springframework.cloud.bindings.boot.enable=true
...

次のコマンドを実行してください。実際に稼働しているJavaのベンダー名やバージョンを確認できます。

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vendor\"]"    
{
  "value": "Azul Systems, Inc."
}

$ curl -s http://localhost:8080/actuator/env | jq ".propertySources[] | select(.name == \"systemProperties\").properties[\"java.vm.version\"]"
{
  "value": "11.0.10+9-LTS"
}


TBD