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

Кроме того, за счет различных технологий: поддержки 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 совместима с другими фреймворками и языками.
 
           
 


Avvero
Спасибо за статью. У меня есть пара вопросов, если можно.
Мне кажется, что использование образов докера само по себе реализует этот эффект — «исключается попадание на прод «левых» библиотек», т.е. на прод пойдет то, что «разработчик» собрал.1 А чем такой подход выигрывает у того, когда можно собрать jar через gradle плагин application и просто положить его в образ?
2 Касательно этого:
bores
Главное преимущество показано на картинке в статье. Образ разбивается на легко кэшируемые слои. Похоже на автоматизацию вот этого https://spring.io/guides/gs/spring-boot-Docker/ (ближе к концу статьи)
ivanovdev Автор
Спасибо! На мой взгляд, это дает преимущество при работе с большим количеством сервисов — все образы собираются в одинаковой «манере».
Рекомендую посмотреть эту презентацию — youtu.be/SK6e_ZatOaw
Borz
они и так буду собираться в одинаковой манере. Более того — сборка "не через Spring Boot" даст больше универсальности, когда у вас сервисы не только на Spring Boot сделаны