Пару дней назад вышел релиз Spring Boot 2.3.0.M1, в описании которого первой строкой упоминается поддержка проекта Cloud Native Buildpacks, являющегося попыткой упростить жизнь разработчика, позволяя максимально автоматизировать сборку образов из исходных кодов. Так как на моем текущем проекте нашим микросервисам предстоит жить в контейнерах, решил попробовать его и разобраться в чем преимущества. Короткое продолжение под катом.

Cloud Native Buildpacks (CNB) позволяет разработчику не писать и не поддерживать свой собственный Dockerfile. Запуская единственную команду сборки проекта CNB сама определит какие зависимости собрать в образ. Выглядит все так:

image

Кроме того, за счет различных технологий: поддержки OCI, модульной структуры, кэширования слоев зависимостей — система CNB может широко применяться и по заверениям разработчиков довольно быстрая.

Проверим, что он представляет из себя на деле. Скачиваем сайта start.spring.io пустой проект. Добавим в него щепотку зависимостей, чтобы посмотреть работает ли вообще наш контейнер.

Нам этом подготовка заканчивается — наше приложение готово для помещения в контейнер. Для этого выполняем в gradle «./gradlew bootBuildImage» или же для maven «./mvnw spring-boot:build-image» и сборка начинается. После сборки получаем удобный лог с описанием всего процесса.

Изначально плагин достает нужные базовые артефакты:

> Task :bootBuildImage
Building image 'docker.io/library/cnb:0.0.1-SNAPSHOT'

 > Pulling builder image 'docker.io/cloudfoundry/cnb:0.0.43-bionic' ..................................................
 > Pulled builder image 'cloudfoundry/cnb@sha256:c983fb9602a7fb95b07d35ef432c04ad61ae8458263e7fb4ce62ca10de367c3b'
 > Pulling run image 'docker.io/cloudfoundry/run:base-cnb' ..................................................
 > Pulled run image 'cloudfoundry/run@sha256:ba9998ae4bb32ab43a7966c537aa1be153092ab0c7536eeef63bcd6336cbd0db'
 > Executing lifecycle version v0.5.0
 > Using build cache volume 'pack-cache-7cfae5296b92.build'

Запускаются детекторы, которые сканируют проект и определяют какие зависимости собирать дополнительно:

 > Running detector
    [detector]    6 of 13 buildpacks participating
    [detector]    org.cloudfoundry.openjdk                   v1.0.80
    [detector]    org.cloudfoundry.jvmapplication            v1.0.113
    [detector]    org.cloudfoundry.tomcat                    v1.1.74
    [detector]    org.cloudfoundry.springboot                v1.0.157
    [detector]    org.cloudfoundry.distzip                   v1.0.144
    [detector]    org.cloudfoundry.springautoreconfiguration v1.0.159

Скачиваются зависимости и запускается сборка:

 > Running builder
    [builder]     
    [builder]     Cloud Foundry OpenJDK Buildpack v1.0.80
    [builder]       OpenJDK JRE 11.0.5: Contributing to layer
    [builder]         Downloading from https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.5%2B10/OpenJDK11U-jre_x64_linux_hotspot_11.0.5_10.tar.gz
    [builder]         Verifying checksum
    [builder]         Expanding to /layers/org.cloudfoundry.openjdk/openjdk-jre
    [builder]         Writing JAVA_HOME to shared
   ...
    [builder]     Cloud Foundry Spring Boot Buildpack v1.0.157
    [builder]       Spring Boot 2.3.0.M1: Contributing to layer
    [builder]         Writing CLASSPATH to shared
    [builder]       Process types:
    [builder]         spring-boot: java -cp $CLASSPATH $JAVA_OPTS dev.ivanov.cnb.CnbApplication
    [builder]         task:        java -cp $CLASSPATH $JAVA_OPTS dev.ivanov.cnb.CnbApplication
    [builder]         web:         java -cp $CLASSPATH $JAVA_OPTS dev.ivanov.cnb.CnbApplication

Собирается образ:

> Running exporter
    [exporter]    Adding layer 'app'
    [exporter]    Adding layer 'config'
    [exporter]    Adding layer 'launcher'
    [exporter]    Adding layer 'org.cloudfoundry.openjdk:openjdk-jre'
    [exporter]    Adding layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [exporter]    Adding layer 'org.cloudfoundry.springboot:spring-boot'
    [exporter]    Adding layer 'org.cloudfoundry.springautoreconfiguration:auto-reconfiguration'
    [exporter]    *** Images (2757ab54378d):
    [exporter]          docker.io/library/cnb:0.0.1-SNAPSHOT

Кэшируются зависимости:

> Running cacher
    [cacher]      Caching layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b'
    [cacher]      Caching layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [cacher]      Caching layer 'org.cloudfoundry.springboot:spring-boot'
    [cacher]      Caching layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150'

Successfully built image 'docker.io/library/cnb:0.0.1-SNAPSHOT'
BUILD SUCCESSFUL in 25s


Образ уже виден в докере и его можно посмотреть командой docker images и запустить docker run docker run -d -p 8080:8080 imageid

Добавим новые зависимости в наше приложение и выполним сборку еще раз.

Система проходится по закэшированным слоям:

> Running restorer
    [restorer]    Restoring cached layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b'
    [restorer]    Restoring cached layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [restorer]    Restoring cached layer 'org.cloudfoundry.springboot:spring-boot'
    [restorer]    Restoring cached layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150'

 > Running analyzer
    [analyzer]    Using cached layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b'
    [analyzer]    Writing metadata for uncached layer 'org.cloudfoundry.openjdk:openjdk-jre'
    [analyzer]    Using cached launch layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [analyzer]    Rewriting metadata for layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [analyzer]    Using cached launch layer 'org.cloudfoundry.springboot:spring-boot'
    [analyzer]    Rewriting metadata for layer 'org.cloudfoundry.springboot:spring-boot'
    [analyzer]    Using cached layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150'
    [analyzer]    Writing metadata for uncached layer 'org.cloudfoundry.springautoreconfiguration:auto-reconfiguration'

Запускается сборка проекта аналогично прошлому шагу:


 > Running builder
    [builder]     
    [builder]     Cloud Foundry OpenJDK Buildpack v1.0.80
    [builder]       OpenJDK JRE 11.0.5: Reusing cached layer
    [builder]     
    [builder]     Cloud Foundry JVM Application Buildpack v1.0.113
    [builder]       Executable JAR: Reusing cached layer
    [builder]       Process types:
    [builder]         executable-jar: java -cp $CLASSPATH $JAVA_OPTS org.springframework.boot.loader.JarLauncher
    [builder]         task:           java -cp $CLASSPATH $JAVA_OPTS org.springframework.boot.loader.JarLauncher
    [builder]         web:            java -cp $CLASSPATH $JAVA_OPTS org.springframework.boot.loader.JarLauncher
    [builder]     
    [builder]     Cloud Foundry Spring Boot Buildpack v1.0.157
    [builder]       Spring Boot 2.3.0.M1: Contributing to layer
    [builder]         Writing CLASSPATH to shared
    [builder]       Process types:
    [builder]         spring-boot: java -cp $CLASSPATH $JAVA_OPTS dev.ivanov.cnb.CnbApplication
    [builder]         task:        java -cp $CLASSPATH $JAVA_OPTS dev.ivanov.cnb.CnbApplication
    [builder]         web:         java -cp $CLASSPATH $JAVA_OPTS dev.ivanov.cnb.CnbApplication
    [builder]     
    [builder]     Cloud Foundry Spring Auto-reconfiguration Buildpack v1.0.159
    [builder]       Spring Auto-reconfiguration 2.11.0: Reusing cached layer

 > Running exporter
    [exporter]    Adding layer 'app'
    [exporter]    Adding layer 'config'
    [exporter]    Reusing layer 'launcher'
    [exporter]    Reusing layer 'org.cloudfoundry.openjdk:openjdk-jre'
    [exporter]    Reusing layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [exporter]    Adding layer 'org.cloudfoundry.springboot:spring-boot'
    [exporter]    Reusing layer 'org.cloudfoundry.springautoreconfiguration:auto-reconfiguration'
    [exporter]    *** Images (7a83fadad1ce):
    [exporter]          docker.io/library/cnb:0.0.1-SNAPSHOT

 > Running cacher
    [cacher]      Reusing layer 'org.cloudfoundry.openjdk:2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b'
    [cacher]      Reusing layer 'org.cloudfoundry.jvmapplication:executable-jar'
    [cacher]      Caching layer 'org.cloudfoundry.springboot:spring-boot'
    [cacher]      Reusing layer 'org.cloudfoundry.springautoreconfiguration:46ab131165317d91fd4ad3186abf755222744e2d277dc413def06f3ad45ab150'

Successfully built image 'docker.io/library/cnb:0.0.1-SNAPSHOT'
BUILD SUCCESSFUL in 20s


Сборка собралась быстрее чем предыдущая, несмотря на появление дополнительных зависимостей в проекте — все благодаря повторному использованию слоев контейнера.

Для того чтобы подробнее изучить получившийся образ нужно скачать отсюда утилиту pack и с помощью команды inspect-image заглянуть внутрь любого образа:

"name": "openjdk-jre",
 "version": "11.0.5",
 "metadata": {
   "licenses": [
     {
       "type": "GPL-2.0 WITH Classpath-exception-2.0",
       "uri": "https://openjdk.java.net/legal/gplv2+ce.html"
     }
   ],
   "name": "OpenJDK JRE",
   "sha256": "2f08c469c9a8adea1b6ee3444ba2a8242a7e99d87976a077faf037a9eb7f884b",
   "stacks": [
     "io.buildpacks.stacks.bionic",
     "org.cloudfoundry.stacks.cflinuxfs3"
   ],
   "uri": "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.5%!B(MISSING)10/OpenJDK11U-jre_x64_linux_hotspot_11.0.5_10.tar.gz"
 },
 "buildpack": {
   "id": "org.cloudfoundry.openjdk",
   "version": "v1.0.80"
 }
},
{
 "name": "executable-jar",
 "version": "",
 "metadata": {
   "classpath": [
     "/workspace"
   ],
   "main-class": "org.springframework.boot.loader.JarLauncher"
 },
 "buildpack": {
   "id": "org.cloudfoundry.jvmapplication",
   "version": "v1.0.113"
 }

В данных метаданных указано что содержится в образе, что находится в каждом слое, и как оно было создано. Это однозначный плюс для безопасности, так как структура каждого образа известна и исключается попадание на прод «левых» библиотек.

Таким образом, Cloud Native Buildpacks предоставляет довольно интересный функционал для разработчиков микросервисов, в первую очередь, на Spring Boot, так как разработкой занимается Pivotal, а значит с поддержкой все будет ок. Но так же стоить упомянуть, что CNB совместима с другими фреймворками и языками.