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 сделаны