Na wstępie chciałbym zaznaczyć, że Cloud Native Buildpack nie jest ograniczony tylko do konkretnego języka programowania i można tą technologią budować aplikację napisane w różnych językach. W poniższym artykule w niektórych miejscach zobaczysz nawiązania do Javy, ale analogicznie będzie to wyglądało w innych językach programowania.

Jakiś czas temu (maj 2020) do Spring Boot dodana została nowa funkcja pozwalająca zbudować obraz zgodny z dokerem (OCI) za pomocą wbudowanego pluginu. Dzięki nowej opcji od wersji 2.3.x możemy zbudować obraz dokerowy bez konieczności pisania ręcznie Dokerfile ani konfigurowania dodatkowych pluginów maven/gradle.

Generalnie jest to super sprawa, ponieważ nie musimy martwić się o nic, wszystko jest zrobione za nas "magicznie", jednak jak to często bywa, diabeł tkwi w szczegółach coś, co na ogół działa "magicznie" i automatycznie w pewnych okolicznościach może przestać nam wystarczać, powodować problemy lub wręcz przestać działać, wtedy przychodzi czas na zrozumienie tej "Magii" jeśli chcemy z niej dalej korzystać.

W tym i następnym poście chciałbym w przystępny sposób odpowiedzieć na pytania:

  • Czym jest projekt Cloud Native Buildpack?
  • Czym jest platforma? Czym jest Lifecycle, a czym Buildpack ?
  • Jak w prosty sposób wykorzystać CNB aby zbudować obraz aplikacji np. Java + Spring Boot.
  • Dlaczego warto się tym zainteresować?
  • Jakie problemy możecie napotkać wykorzystując CNB na produkcji i co ważniejsze jak je rozwiązać?
  • Jaka jest różnica pomiędzy Paketo Buildpacks, a Cloud Native Buildpacks ?
  • Czy CNB działa też z innymi językami programowania?

Przez ostatni rok używając tej technologii nie tylko na środowisku developerskim napotkałem sporo różnych "wyzwań", których bez zrozumienia samej technologii nie byłbym w stanie rozwiązać. I właśnie tym czego się dowiedziałem/nauczyłem chciałbym się z wami podzielić w tym i następnym blog poście.  

Co to tak właściwie jest Cloud Native Buildpacks?

Cloud Native Buildpacks · Cloud Native Buildpacks

Na początek odrobinę historii. Technologia Buildpack nie jest technologią nową, wersja v1 została stworzona przez Heroku w 2011 roku i później zaadaptowana przez CloudFoundry oraz inne PaaS (Platform as a Service) takie jak Google App Engine, GitLab czy Knative jednak każda z tych platform robiła to trochę inaczej niż pozostałe przez co nie było spójnego podejścia. Dopiero aktualna wersja nazwana Cloud Native Buildpacks doczekała się standaryzowania tego ekosystemu w postaci spisanego kontraktu i dołączenia go do Cloud Native Computing Foundation, które jest częścią Linux Foundation i skupia w sobie rozwiązania open source wyznaczające standardy nowoczesnych aplikacji cloud native. Masa dużych firm takich jak Google, Microsoft, Heroku, VMware, wspiera fundację i wykorzystuje promowane przez nią standardy.

CNB to owoc współpracy dwóch firm Heroku i Pivotal. Jest to ustandaryzowany sposób na zbudowanie obrazu zgodnego z Dokerem bez używania Dokerfile. Wprowadza on wyższy poziom abstrakcji w porównaniu do Dockerfile, dzięki czemu oddziela i automatyzuje cały narzut operacyjny z nim związany. Dzieje się to poprzez wprowadzenie modularnych elementów (buildpacków), które wykorzystywane są w procesie budowania obrazu aplikacji.

Projekt ten patrząc wysoko poziomowo to specyfikacje zamiany kodu aplikacji/artefaktu na obraz OCI, których implementacje dostarczają nam różni Vendorzy np. Cloud Native Buildpack, Paketo, Heroku, Google, Pivotal/VMware etc.

Platform API

Specyfikacja platformy, pozwalająca wchodzić w interakcję z Lifecycle i budować obrazy za pomocą Buildpacków. Referencyjną implementacją pochodzącą od CNB jest narzędzie pack, ale na rynku dostępne są też narzędzia dostarczone przez innych vendorów. Na tą chwilę API platformy implementują takie narzędzia/platformy jak:

  • pack
  • kpack
  • Spring Boot Plugin
  • Tanzu Build Server
  • Tekton
  • Heroku
  • Google App Engine
  • Circle CI Orb

Fakt, że wszystkie wymienione platformy implementują te same Platform Api i używają tego samego Lifecycle sprawia, że jeśli dostarczymy do nich ten sam kod źródłowy/artefakt i taki sam builder (o tym później) zawsze otrzymamy taki sam obraz niezależnie na jakiej platformie go zbudujemy. Dlatego zbudowanie obrazu lokalnie za pomocą pack CLI niczym nie będzie się różniło od tego, który zostanie zbudowany na produkcji np. poprzez Tanzu.

Buildpacks

Jest to najmniejsza jednostka implementująca Buildpacks API posiadająca jedną odpowiedzialność i wykonująca określone operacje na obrazie. Pisząc o jednej odpowiedzialności, mam na myśli to, że oddzielny buildpack odpowiada za pobranie Javy, oddzielny za skompilowanie aplikacji, a jeszcze inny za zoptymalizowanie aplikacji Spring Boot. Generalnie każdy buildpack biorący udział w procesie budowania dokłada swoją cegiełkę do obrazu wynikowego aplikacji, co często też skutkuje stworzeniem nowej warstwy lub cache na etapie budowania w celach optymalizacyjnych. Specyfikacja Buildpacka posiada 2 fazy do zaimplementowania:

  • Detect - służy do wykrywania na podstawie dostarczonego kodu źródłowego/artefaktu czy dany buildpack powinien być użyty podczas fazy budowania np. buildpack mavena szuka w kodzie źródłowym pliku pom.xml.
  • Build - W tej fazie implementacja buduje swoją część procesu realizując instrukcje zawarte w buildpacku np. wspomniany wcześniej buildpack mavena ściąga zależności i umieszcza je w cache.

W pewnym sensie (i w dużym uproszczeniu), buildpacki przypominają mi trochę startery Spring Boota, które też posiadają swoją domyślną konfigurację i uruchamiają się w momencie jak wykryją, że są potrzebne (np. jak znajdą jakąś klasę na classpath).

CNB, w przeciwieństwie do Platformy oraz Lifecycle, nie udostępnia żadnej referencyjnej implementacji buildpacków i zostawia to vendorom, którzy dostarczają swoje buildpacki spełniający kontrakt Buildpack API. Są to między innymi:

  • Paketo
  • Tanzu
  • Google
  • Heroku

Jednym z takich vendorów jest Projekt Open Source pod patronatem Cloud Foundry o nazwie Paketo, dostarcza on implementacje buildpacków dla różnych języków programowania. Dodatkowo, jeśli nie znajdziesz buildpacka, który spełnia twoje wymagania, to dzięki Specyfikacji Buildpack API łatwo jest go napisać samemu i włączyć do procesu budowania. Może to być np. skrypt bash lub program w Go lang wykonujący jakieś polecenie. Dokładne instrukcje jak zrobić swój buildpack można znaleźć (tutaj)

Lifecycle

Specyfikacja Cyklu budowania obrazu aplikacji, orkiestruje wywołanie buildpacków i składanie rezultatu w gotowy obraz aplikacji. Referencyjną implementację tej specyfikacji dostarcza główny projekt Cloud Native Buildpacks. Wyróżniamy w niej 4 główne fazy:

  • Detect w tej fazie uruchamiane są fazy detect zaimplementowane w dostępnych buildpackach co skutkuje zebraniem wszystkich buildpacków, które powinny być użyte do budowania aplikacji.
  • Analyze/Restore analizuje i ponownie wykorzystuje wcześniejsze warstwy oraz cache powstały podczas poprzedniego budowania. Dzięki temu znacząco przyspiesza cały proces budowania.
  • Build wykorzystuje fazę build zaimplementowaną w pojedynczych buildpackach do zbudowania warstw/cache, które przyczynią się do powstania końcowego obrazu aplikacji.
  • Export eksportuje potrzebne warstwy i tworzy z nich końcowy obraz aplikacji, pozostałe warstwy są zachowywane i udostępniane dla następnych budowa.

Dodatkowe komponenty

Wyżej opisałem główne specyfikacje, które dostarcza nam projekt CNB, ale oprócz specyfikacji często spotkamy się z takimi pojęciami/komponentami jak:

Stack

Składa się z dwóch typów obrazów
stack

  • Build Image - Obraz środowiska zawierającego wszystko to co potrzebne do zbudowania aplikacji. Czasami występuje w kilku wersjach, które różnią się ilością zainstalowanych pakietów a przez to też wielkością. Jeden z Vendorów Open Source, którego opisze później dostarcza nam 3 wersje obrazów Tiny, Base, Full.
  • Run Image - Mniejszy obraz, skrojony pod potrzeby aplikacji zawierający tylko to co niezbędne do uruchomienia i poprawnego działania aplikacji. Podobnie jak wyżej obraz ten może występować w kilku wersjach. Obrazy uruchomieniowe są pozbawione kompilatorów i innych narzędzi, które mogą stwarzać zagrożenie dla bezpieczeństwa.

Każdy z wyżej wymienionych obrazów jest uzupełniany poprawkami naprawiającymi typowe podatności systemu (CVE), gdy pojawi się dla nich łatka.

Paketo.io planuje aktualizować swój Stack oparty o ubuntu:bionic o krytyczne łatki bezpieczeństwa w ciągu 48 godzin od pojawienia się takiej poprawki. Łatki monitorowane są automatycznie i czesto aktualizacja dostarczana jest w ciągu kilku godzin od pojawienia się aktualizacji od Canonical. Warto wspomnieć też, że stack (w ramach głównej wersji np. ubuntu bionic) jest wstecznie kompatybilny, więc można go bezpiecznie aktualizować.

Builder

Builder to obraz OCI dostarczający środowisko spinające w sobie wszystkie komponenty potrzebne do zbudowania obrazu aplikacji oraz jej uruchomienia. Komponent ten został wprowadzony, aby nie musieć dostarczać wszystkich wymaganych elementów ręcznie tylko zebranie wszystkiego w pakiet co miało za zadanie ułatwić korzystanie z Buildpacków i ich dystrybuowanie. Składa się z referencyjnej implementacji Lifecycle, grupy Buildpacków (o tym później) oraz z Stacku, który będzie wykorzystywany do zbudowania i uruchomienia aplikacji.

Buildery dostarczane są przez vendorów tworzących buildpacki np. przez wcześniej wspomnianego już Paketo.io. Sami również możemy stworzyć sobie taki obraz budujący wystarczy przygotować plik builder.toml z konfiguracją i zlecić to platformie np. poprzez wykonanie polecenia pack builder create <nazwa> --config builder.toml. Jeśli potrzebujesz więcej informacji o tym jak zbudować własny builder zapraszam (tutaj).

Czas połączyć kropki

Cały flow wysoko poziomowy wygląda dość prosto, chcąc zbudować aplikację wysyłamy do platformy (np. pack cli ) kod źródłowy/artefakt wskazujemy builder image (lub nie jeśli w platformie ustawiliśmy default) po czym rozpoczyna się proces budowania. W obrazie wynikowym zawarty jest Run Image dostarczony przez użyty builder, wszystkie potrzebne zależności pobrane przez buildpacki oraz aplikacja, która również została zbudowana/przygotowana przez buildpacki.

Czym jest Paketo Buildpacks ?

https://paketo.io/ 

Tak jak wspomniałem wcześniej Cloud Native Buildpacks w skrócie CNB dostarcza API definiujące interfejs pomiędzy Buildpackiem, a środowiskiem/platformą, która go uruchamia.  Paketo.io Jest projektem Open Source należącym do Cloud Foundry Fundation, który dostarcza implementacje buildpacków dla sporej ilości ekosystemów popularnych języków programowania i frameworków.

Implementacje te są domyślnie skonfigurowanymi modułami napisanymi w języku GO i za jego pomocą wykonują określone operacje na obrazie dokerowym budującym aplikacje. Mogą np. ściągnąć potrzebne zależności, utworzyć dla nich oddzielną warstwę w obrazie czy też poustawiać jakieś zmienne konfiguracyjne dla środowiska uruchomieniowego. Dodatkowo są ciągle aktualizowane o łatki bezpieczeństwa, a dzięki implementacji Specyfikacji Buildpack API mogą być wykorzystywane przez wszystkie platformy implementujące Platform API, które wymieniłem na początku posta.

Buildpacki dzielą się na 2 rodzaje:

Komponentowy/pojedyńczy

Jest to modularna jednostka z zaimplementowaną fazą Detect oraz Build. Do swojego działania może wymagać innego buildpacka lub też może być wymagana do poprawnego uruchomienia następnego. Wiem, że może być to trochę niejasne, dlatego weźmy na warsztat /maven faza detect sprawdza czy w kodzie jest plik pom.xml, faza build ściąga mavena i kompiluje kod źródłowy aplikacji. Do swojego działania wymaga JDK, które może dostarczyć nam np. /bellsoft-liberica. Po wykonaniu wszystkich operacji zmiany widoczne są dla kolejnego buildpacka, jakim jest np. /spring-boot.

Meta Buildpack

Ten rodzaj składa się z listy buildpacków, z których niektóre mogą być opcjonalne i wykorzystywane tylko wtedy, gdy wykryją, że są potrzebne np. gdy budujemy aplikację springową opcjonalny buildpack springa zostanie użyty, gdy budujemy zwykłą aplikację buildpack springa zostanie pominiety.

Przykładem złożonego buildpacka może być wyżej pokazany /java zawiera on zestaw definicji dostępnych komponentów wykorzystywanych w ekosystemie Javy, które mogą być potrzebne do zbudowania i przygotowania środowiska uruchomieniowego aplikacji. Dodatkowo złożony buildpack definiuje kolejność w jakiej uruchamiane są wchodzące w jego skład buildpacki pojedyncze.

Jak to działa?

To jak uruchomić buildpacki zostawię na później, w tym akapicie zajmę się pokazaniem i omówieniem procesu budowania aplikacji. W tym artykule pójdziemy ścieżką "happy path" w drugim artykule zboczymy z tej ścieżki i pokażę kilka problemów (i rozwiązań), które napotkałem używając buildpacków na produkcji.

Pierwsze budowanie

W pierwszym budowaniu pominę fazy Analizing/Restoring, które nie biorą w nim aktywnie udziału, i wyjaśnię je przy opisywaniu kolejnego budowania.

Etap Detecting

===> 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.1.0

Do budowania używam /base-builder, który zawiera listę złożonych buildpacków dla Java, .NET Core, NodeJS, Go, Ruby, NGINX and Procfile wraz z kolejnością ich wywoływania.

Faza 'detected' na podstawie przekazanego kontekstu (kod źródłowy/artefakt etc.)  wybiera konkretny złożony buildpack, z którego będzie korzystać przy budowaniu obrazu. W naszym przypadku jest to paczka dla Javy, która zawiera 18 pojedyńczych buildpacków. Każdy buildpack implementuje fazę 'detect' i szuka w przekazanym kontekście czy jest potrzebny do zbudowania aplikacji, czy nie. Dla przykładu w tej fazie może być np. skanowany kod źródłowy w poszukiwaniu pom.xml lub wystąpienia konkretnej zależności. Jak widać powyżej, nie wszystkie buildpacki uznały, że są potrzebne do budowania obrazu. Te, które spełniają kryteria (7 z 18) i zostaną wykorzystane do zbudowania aplikacji, zawsze są wypisane w konsoli wraz z ich wersjami.

Etapy Analyzing/Restoring

Świadomie pomijam, ponieważ w pierwszym budowaniu nic szczególnego się tutaj nie dzieje. Opiszę je omawiając kolejne budowanie

Etap Building

Każdy pojedynczy buildpack, analogicznie do fazy detect, zobowiązany jest również do posiadania fazy build, która wykonuje zdefiniowane w nim operacje, które wpłyną na obraz wynikowy. W tym etapie budowania wykorzystywane są buildpacki wykryte w poprzednim etapie (Detecting) i rozpoczyna się ich uruchamianie w odpowiedniej kolejności (kolejność jest zawsze zdefiniowana w pliku konfiguracyjnym .toml np. tutaj)

Certyfikaty

===> 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  

Na pierwszy ogień idzie buildpack wspomagający konfigurowanie certyfikatów potrzebnych w obrazie budującym jak i w obrazie uruchomieniowym, swoje certyfikaty możemy dodać opcjonalnie poprzez tzw. 'bindings' (opisze je w drugiej części artykułu, który będzie opisywał problemy). Dodatkowo tworzona jest warstwa, aby nie wykonywać tej operacji za każdym razem, gdy nic nie zmieniamy w certyfikatach.

Java

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: 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  

Buildpack ten odpowiada za ściągnięcie i skonfigurowanie Javy. Jak można zauważyć powyżej, buildpacki nie posiadają żadnych binarek, zamiast tego zawierają link pozwalający ściągnąć zależność w czasie budowania i dodać ją do warstwy po to aby użyć jej ponownie w następnym budowaniu (wiąże się z tym pewien problem, który opiszę w drugiej części artykułu). Jeśli obraz budowany jest z kodu źródłowego to ściągane jest JDK wraz z JRE, gdybyśmy jednak budowali obraz z gotowego jara, wówczas JDK nie będzie potrzebne i ściągnięte zostanie jedynie JRE.

Dodatkowo wprowadzone są tu zmienne środowiskowe pozwalające na konfigurowanie Javy. Gdy nie nadpiszemy zmiennej środowiskowej zostanie wykorzystana jej domyślna wartość zdefiniowana w Buildpacku.

Maven

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.2/spring-boot-starter-parent-2.4.2.pom ... ... ... [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/eclipse/sisu/org.eclipse.sisu.inject/0.3.4/org.eclipse.sisu.inject-0.3.4.jar (379 kB at 453 kB/s) [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 53.982 s [INFO] Finished at: 2021-03-20T13:02:04Z [INFO] ------------------------------------------------------------------------ Removing source code  

Jako, że buduje obraz z kodu źródłowego i w środku obrazu budującego wykorzystuje do tego narzędzie maven to potrzebujemy je ściągnąć oraz skonfigurować. W moim projekcie buildpack mavena wykrył wraper więc nie ściągał dodatkowo mavena. Jednak gdyby takiego wrappera nie było to buildpack rozpoczą by pracę od sciągnięcia odpowiedniej wersji mavena. Link do ściągnięcia oraz wersję mavena można podejrzeć jak we wszystkich buildpackach (tutaj).

Również tutaj wystawione mamy zmienne, które pozwalają skonfigurować etap budowania np poprzez ustawienie argumentu -Dmaven.test.skip=false (domyślnie w tym buildpacku jest na true) czy też wskazać miejsce, z którego chcemy wziąć jar wynikowy (przydatne w przypadku posiadania projektu z kilkoma modułami maven). Na tym etapie również tworzony jest cache dla folderu .m2 zawierający ściągnięte zależności podczas budowania, dzięki temu nie są one ściągane za każdym razem od nowa, gdy ubędzie albo dojdzie jakaś zależność (tak jak w Dokerfile) tylko są one dokładane do warstwy cache. na koniec z obrazu zostaje usunięty kod źródłowy i nie uczestniczy już w dalszych etapach budowania.

Executable jar i Spring Boot

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.1.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.0: Contributing to layer Downloading from https://repo.spring.io/release/org/springframework/cloud/spring-cloud-bindings/1.7.0/spring-cloud-bindings-1.7.0.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

Oba wyżej wspomniane buildpacki operują już na zbudowanym artefakcie w naszym przypadku jest to jar. Pierwszy z buildpacków sprawdza czy odpowiednie JRE zostało już zainstalowane i dodaje "Application root" do CLASSPATH. Spring Boot Buildpack korzysta z funkcji pluginu springa, który przy budowaniu aplikacji dzieli ją na kawałki:

  • Dependencies - zawierają wszystkie zależności aplikacji, które nie są snapshotami.
  • Spring-boot-loader - loader ładujący jar springa.
  • Snapshot-dependencies - wszystkie zależności typu snapshot.
  • Application - wszystkie klasy aplikacji, którą piszemy, dzięki temu, jeśli zmieni się tylko kod aplikacji tylko ta warstwa zostanie przebudowana.

Podział na kawałki/katalogi jara wynikowego jest możliwy w springu od wersji 2.3.0 jednak musi być on włączony w pluginie poprzez ustawienie  layers enabled na true. Od wersji 2.4.0 jest to domyślne zachowanie Spring Boot maven plugin. Następnie sprawdzane jest jakiego typu aplikacji używamy Servlet/Reactive i następuje konfiguracja jvm pod znaleziony typ, w naszym przypadku wykryte zostało, że aplikacja korzysta z servletów i ustawiona została odpowiednia ilość wątków.
Dodatkowo ściągana jest biblioteka spring cloud bindings pozwalająca na dodatkową konfigurację aplikacji w runtime. Więcej o niej można przeczytać na GitHubie. (tutaj)

Możecie się zastanawiać po co spring wprowadza taki podział, otóż takie rozmieszczenie plików w warstwach optymalizuje i przyspiesza budowanie obrazu wynikowego. Kolejność warstw jest tutaj kluczowa, gdyż poukładane są one od najrzadziej do najczęściej zmieniającej się, przez co np. zmiana tylko w kodzie aplikacji nie powoduje przebudowy całego obrazu zamiast tego buduje i zmienia tylko warstwę aplikacji. Zaoszczędza to czas ale też transfer co może skutkować zaoszczędzeniem $$.

Etap Exporting

===> 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' *** Images (6887b40760d9): sample_app 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 sample_app

Ostatni etap wykorzystuje zbudowane wcześniej warstwy, wybiera te, które są potrzebne i składa z nich obraz wynikowy, resztę warstw oraz powstały cache przechowuje do wykorzystania przy następnych budowaniach.

Kolejne budowanie,

W tym budowaniu zmieniłem kod źródłowy aplikacji i dodałem jedną zależnosć do biblioteki MapStruct

Etapy Analyzing/Restoring

===> 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/bellsoft-liberica:jdk" from cache Restoring metadata for "paketo-buildpacks/maven:application" from cache Restoring metadata for "paketo-buildpacks/maven:cache" from cache 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 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

Pominąłem etap Detecting ponieważ jest taki sam jak w pierwszym budowaniu.

W Etapie Analyzing/Restoring pozyskiwane są wszystkie metadane, warstwy i cache z danymi, zapisane podczas poprzedniego budowania a następnie udostępniane dla buildpacków.  Podczas etapu budowania Buildpacki decydują, czy zachować, usunąć albo zmienić poszczególne dane z odtworzonych warstw.

Etap Building

===> 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] -----------------< pl.cupofcodes.buildpack.demo:demo >------------------ [INFO] Building demo 0.0.1-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/mapstruct/mapstruct/1.4.2.Final/mapstruct-1.4.2.Final.pom [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/mapstruct/mapstruct/1.4.2.Final/mapstruct-1.4.2.Final.pom (1.8 kB at 2.1 kB/s) [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/mapstruct/mapstruct-parent/1.4.2.Final/mapstruct-parent-1.4.2.Final.pom [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/mapstruct/mapstruct-parent/1.4.2.Final/mapstruct-parent-1.4.2.Final.pom (1.6 kB at 12 kB/s) [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/mapstruct/mapstruct/1.4.2.Final/mapstruct-1.4.2.Final.jar [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/mapstruct/mapstruct/1.4.2.Final/mapstruct-1.4.2.Final.jar (26 kB at 222 kB/s) [INFO] [INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ demo --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Using 'UTF-8' encoding to copy filtered properties files. [INFO] Copying 1 resource [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ demo --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to /workspace/target/classes [INFO] [INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ demo --- [INFO] Not copying test resources [INFO] [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ demo --- [INFO] Not compiling test sources [INFO] [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ demo --- [INFO] Tests are skipped. [INFO] [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ demo --- [INFO] Building jar: /workspace/target/demo-0.0.1-SNAPSHOT.jar [INFO] [INFO] --- spring-boot-maven-plugin:2.4.2:repackage (repackage) @ demo --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.878 s [INFO] Finished at: 2021-03-27T11:42:46Z [INFO] ------------------------------------------------------------------------ Removing source code

W tym etapie przy drugim budowaniu widzimy, że większość warstw jest pobrana z cache, warstwa aplikacji jest przebudowywana, ponieważ wykryto zmianę w kodzie źródłowym. Uruchamiany jest maven, który dociąga nową zależność i wrzuca ją do cache zawierającego resztę zależności zaciągniętych w poprzednich budowaniach.

W tym miejscu specjalnie dodałem nową zależność do mavena żeby pokazać wam różnicę CNB vs Dockerfile. Kiedyś próbowałem zbudować aplikację z kodu źródłowego za pomocą samego Dokerfile. Nawet jak podzieliłem go ładnie na warstwy i etapy(Stage), rozdzieliłem warstwę zależności mavenowych od warstwy kodu i przełączyłem mavena w tryp offline. to wraz po zmianie czegokolwiek w pom.xml docker przebudowywał całą warstwę mavane pobierając od nowa wszystkie zależności. Oczywiście jak nie ruszałem pom.xml tylko wyższe warstwy to warstwa zależności nie była ponownie przebudowywana.

Inaczej jest, gdy wykorzystujemy Cloud Native Buildpack jak widać wyżej zmieniłem pom.xml dodałem zależność do MapStruct i tylko to zostało dociągnięte do cache. W dalszej części widać, że maven skompilował klasę, którą zmieniłem.

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.1.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.0: 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

Dalej niektóre buildpacki również wykryły zmiany na podstawie, których musiały się przebudować np. zmiana w kodzie źródłowym wymusiła ponowne wywnioskowanie rodzaju aplikacji servlet/reactor i skonfigurowania jvm pod wykryty rodzaj.

Etap Exporting

===> 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 3/5 app layer(s) Adding 2/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' *** Images (81381443ee83): sample_app Reusing cache layer 'paketo-buildpacks/bellsoft-liberica:jdk' Adding cache layer 'paketo-buildpacks/maven:application' Adding cache layer 'paketo-buildpacks/maven:cache' Successfully built image sample_app

Ostatni etap tak jak w poprzednim budowaniu składa obraz wynikowy, z tą różnicą, że część z warstw stworzonych przez buildpacki w poprzednim budowaniu została wykorzystana ponownie. 3 z 5 warstw dotyczących samej aplikacji zostało wykorzystane ponownie a tylko 2 warstwy zostały przebudowane i dodane do obrazu.

⚡Ważne⚡
Aktualnie cache działa tylko w ramach obrazów o tej samej nazwie, czyli dla kolejnych budowań tego samego obrazu. Jest to znane przez twórców ograniczenie w jednej z rozmów na slacku projektu napisali mi, że dążą do tego, aby zasoby były trzymane w cache, który będzie dostępny dla wszystkich budowań nie tylko tych powtórzonych. Na szczęście można trochę usprawnić proces budowania i przez niewielką konfigurację samemu skonfigurować sobie taki cache pomiędzy budowaniami różnych obrazów o tym również w następnym poście.

Budowanie za pomocą pluginu springa

Jednym ze sposobów użycia buidlpacków do stworzenia obrazu naszej aplikacji jest wykorzystanie Spring Boot Plugin >= 2.3.x maven/gradle. Plugin zbuduje nam jar, po czym przekaże artefakt do buildera wykorzystywanego przez buildpacka.

Domyślny builder to paketobuildpacks/builder:base, ale jeśli potrzebujemy skorzystać z innego, to łatwo możemy go podmienić w konfiguracji (pokaże to w następnym poście). Gdy nie chcemy niczego konfigurować wystarczy maven(lub wrapper), docker i polecenie:

mvn spring-boot:build-image

Konfiguracja buildpacków za pomocą pluginu maven

Konfiguracja z wykorzystaniem pluginu springa na początku (Spring boot 2.3.x) była dość uboga i nie można było właściwie za dużo zmienić. To co było można skonfigurować to między innymi:

  • Przekazanie zmiennych środowiskowych wykorzystanych przez buildpacki np. skonfigurowanie flagi JVM $JAVA_TOOL_OPTIONS="-Xms2G -Xmx2G".
  • Zmienienie wykorzystywanego buildera.
  • Wskazanie run image, jeśli chcieliśmy uruchomić naszą aplikację na innym obrazie niż standardowy.
  • Wskazać nazwę dla stworzonego obrazu aplikacji.

Wszystkie dostępne opcje konfiguracyjne dla Spring Boot 2.3.x można znaleźć pod tym adresem

W wersji Spring boot 2.5 zostało dodane sporo więcej konfiguracji pozwalającej np. bindować zależności, ale o tym więcej w następnym artykule.

Wszystkie dostępne opcje konfiguracyjne dla Spring Boot 2.5.x można znaleźć pod tym adresem

Budowanie za pomocą pack CLI

Pack CLI to referencyjna implementacja interfejsu platformy o którym pisałem na początku artykułu. Odpowiada on za orkiestracje cyklu życia buildpacków i dostarczenie obrazu końcowego. Pack uruchamia i orkiestruje serię kontenerów dokerowych, dlatego aby go użyć musimy mieć zainstalowanego dockera. Przykładowe polecenie budujące wygląda podobnie do tego znanego z dokera. Znajdując się w folderze z kodem naszej aplikacji wykonujemy:

pack build demo:0.0.1-SNAPSHOT --builder paketobuildpacks/builder:base

Jak nie chcemy podawać za każdym razem buildera możemy ustawić domyślny, poleceniem:

pack config default-builder paketobuildpacks/builder:base

Wspomniana implementacją napisana została w Golang i można z niej korzystać zarówno z poziomu terminala jak i załączyć do projektu jako bibliotekę. Jeśli ktoś chciałby wypróbować to narzędzie to instrukcja instalacji znajduje się na oficjalnej stronie tutaj. Oprócz budowania obrazu narzędzie to umożliwia nam między innymi:

  • Zaawansowaną konfiguracje - mi najbardziej przydał się mechanizm bindowania pozwalający na podmienianie lokalizacji domyślnych zależności np. na prywatnego nexusa zamiast publicznego githuba oraz dodanie własnych certyfikatów do Javy.
  • Rebase - pozwala na podmianę run image w obrazie aplikacji na nowszy bez ponownego budowania całego obrazu (np. poprawki bezpieczeństwa).
  • Inspect - pokazuje wszystkie informacje na temat zbudowanego obrazu. Informacje te zawierają listę wszystkich buildpacków wraz z wersjami, które zostały wykorzystane do zbudowania obrazu, oraz wszystkie biblioteki wraz z ich wersjami, które są zależnościami zbudowanej aplikacji.

Poleceń wbudowanych w Pack CLI jest masa, wymieniłem tylko te, z których korzystałem. Niektóre rozwinę w następnym artykule, gdzie pokarzę problemy jakie napotkałem, gdy chciałem zbudować obraz OCI z wykorzystaniem Cloud Native Buildpacks na serwerze/komputerze bez dostępu do publicznej sieci a dokładnie bez dostępu do GitHuba.

Inspekcja

Informacje o obrazie

Dzięki wykorzystywaniu Cloud Native Buildpacks do budowania obrazów OCI jesteśmy w stanie jednym poleceniem prześwietlić taki obraz i uzyskać informacje na temat jego zawartości i tego jak został zbudowany bez ściągania całego obrazu i zaglądania do jego wnętrzności.  Zamiast tego mamy dostarczoną konfiguracje obrazu z informacjami jakie komponenty zostały użyte do jego budowy. Można to zrobić wykonując polecenie:

pack inspect-image demo:0.0.1-SNAPSHOT Inspecting image: demo:0.0.1-SNAPSHOT   REMOTE: (not present)   LOCAL:   Stack: io.buildpacks.stacks.bionic   Base Image: Reference: c1d4f720664820f776e562f15df6f54558f2412dabf18e7350ff22dd5588dcbd Top Layer: sha256:0762af506d26147646183afd2a4f82d075403365efb460f74c42d4ec27c1e1c4   Run Images: index.docker.io/paketobuildpacks/run:full-cnb gcr.io/paketo-buildpacks/run:full-cnb   Buildpacks: ID VERSION paketo-buildpacks/ca-certificates 2.2.0 paketo-buildpacks/bellsoft-liberica 8.0.0 paketo-buildpacks/executable-jar 5.0.0 paketo-buildpacks/dist-zip 4.0.0 paketo-buildpacks/spring-boot 4.2.0   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

Na tym poziomie widzimy jaki run image został użyty, które buildpacki i w jakich wersjach uczestniczyły w budowaniu obrazu i jakie procesy są dostępne do uruchomienia.

Proces oznaczony jako default uruchamia się automatycznie, jeśli chcemy oznaczyć inny proces jako domyślny, to trzeba dodać do polecenia budującego flagę i nazwę procesu np.

pack build demo:0.0.1-SNAPSHOT --default-process executable-jar

Jeśli nie chcemy przebudowywać aplikacji tylko po to, aby zmienić proces, możemy także skorzystać przy tworzeniu kontenera z opcji --entrypoint <nazwa procesu>

docker run -d --entrypoint executable-jar demo:0.0.1-SNAPSHOT

Obraz uruchomi się z procesem executable-jar zamiast procesu domyślnego.

Informacje o zależnościach obrazu i aplikacji

Powyżej widzieliśmy ogólne informacje o obrazie, gdy do polecenia dodamy --bom (bill of materials) dostaniemy bardziej szczegółowe dane zawierające spis wszystkich zależności używanych w zbudowanym obrazie jak i spis wszystkich zależności używanych przez aplikacje np. wszystkie zależności mavena wraz z wersjami.

{
  "remote": null,
  "local": [
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "ca-certificates-helper"
        ],
        "version": "2.3.2"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/ca-certificates",
        "version": "2.3.2"
      }
    },
    {
      "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": "b8ef03f5c6db0ecf1538865fbb615c28feec61a5814e3408ba4d168dc77451e3",
        "stacks": [
          "io.buildpacks.stacks.bionic",
          "org.cloudfoundry.stacks.cflinuxfs3"
        ],
        "uri": "https://github.com/bell-sw/Liberica/releases/download/11.0.12+7/bellsoft-jre11.0.12+7-linux-amd64.tar.gz",
        "version": "11.0.12"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "8.2.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": "8.2.0"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/bellsoft-liberica",
        "version": "8.2.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": "8.2.0"
      }
    },
    {
      "name": "dependencies",
      "metadata": {
        "dependencies": [
          {
            "name": "HdrHistogram",
            "sha256": "9b47fbae444feaac4b7e04f0ea294569e4bc282bc69d8c2ce2ac3f23577281e2",
            "version": "2.1.12"
          },
          {
            "name": "LatencyUtils",
            "sha256": "a32a9ffa06b2f4e01c5360f8f9df7bc5d9454a5d373cd8f361347fa5a57165ec",
            "version": "2.0.3"
          },
          {
            "name": "jackson-annotations",
            "sha256": "05da0a25bb44a217880a299a1a1e0a301d194b5656a9a07776b77a88f326e7e9",
            "version": "2.12.3"
          },
          {
            "name": "jackson-core",
            "sha256": "baef34fbce041d54f3af3ff4fc917ed8b43ed2a6fa30e0a6abfd9a2b2c3f71e0",
            "version": "2.12.3"
          },
          {
            "name": "jackson-databind",
            "sha256": "94d973062c2fda3dff2c9a85eafce57204821cce9085a99377693dbc9fb8da23",
            "version": "2.12.3"
          },
          {
            "name": "jackson-datatype-jdk8",
            "sha256": "1d131cf0f20c13cffb4f8bb5c2afd68c413c4e1b76fe7384e4fecc8a8c36cb1a",
            "version": "2.12.3"
          },
          {
            "name": "jackson-datatype-jsr310",
            "sha256": "a56dc7dfe15896680d64f746c1105e11f8f18b2331355739b0cdf7794adb4bc3",
            "version": "2.12.3"
          },
          {
            "name": "jackson-module-parameter-names",
            "sha256": "f9655527c39093ba744d02d09d6e254285d1447d3ffbd15ef8f6326907382063",
            "version": "2.12.3"
          },
          {
            "name": "jakarta.annotation-api",
            "sha256": "85fb03fc054cdf4efca8efd9b6712bbb418e1ab98241c4539c8585bbc23e1b8a",
            "version": "1.3.5"
          },
          {
            "name": "jul-to-slf4j",
            "sha256": "bbcbfdaa72572255c4f85207a9bfdb24358dc993e41252331bd4d0913e4988b9",
            "version": "1.7.30"
          },
          {
            "name": "log4j-api",
            "sha256": "8caf58db006c609949a0068110395a33067a2bad707c3da35e959c0473f9a916",
            "version": "2.14.1"
          },
          {
            "name": "log4j-to-slf4j",
            "sha256": "8ba1d1b1c8313731ee053368371b9606c1d71436ac010b0bf91b4c7fc643a1bf",
            "version": "2.14.1"
          },
          {
            "name": "logback-classic",
            "sha256": "fb53f8539e7fcb8f093a56e138112056ec1dc809ebb020b59d8a36a5ebac37e0",
            "version": "1.2.3"
          },
          {
            "name": "logback-core",
            "sha256": "5946d837fe6f960c02a53eda7a6926ecc3c758bbdd69aa453ee429f858217f22",
            "version": "1.2.3"
          },
          {
            "name": "mapstruct",
            "sha256": "e9ee43297854487ed5322705113036d850d2cee3e6646dbbde33f62e0653b376",
            "version": "1.4.2.Final"
          },
          {
            "name": "micrometer-core",
            "sha256": "7a7f6873e98a5ae7ca97c18820fb6c5693c1f9e73b40d69d9c5c85e83f6e34b4",
            "version": "1.7.0"
          },
          {
            "name": "ojdbc8",
            "sha256": "0ffdd8cf8b5012ef3b3c810ddbbaafc7c14bdcf93324d2cab45b0de79b2bde19",
            "version": "21.1.0.0"
          },
          {
            "name": "slf4j-api",
            "sha256": "cdba07964d1bb40a0761485c6b1e8c2f8fd9eb1d19c53928ac0d7f9510105c57",
            "version": "1.7.30"
          },
          {
            "name": "snakeyaml",
            "sha256": "35446a1421435d45e4c6ac0de3b5378527d5cc2446c07183e24447730ce1fffa",
            "version": "1.28"
          },
          {
            "name": "spring-aop",
            "sha256": "e6b3a15592a3b5a29131865ff82b3e2bacf7e64f1819924af93eb19b49085af1",
            "version": "5.3.7"
          },
          {
            "name": "spring-beans",
            "sha256": "8c02823ceb0370bcf6e9eeccbc2d429a484b429457faa7e8bc39506eaaf6ace6",
            "version": "5.3.7"
          },
          {
            "name": "spring-boot",
            "sha256": "7c1ca05f6a76670c232ff304a790766d01e2503488f62e4d0f90d858c9f9fdef",
            "version": "2.5.0"
          },
          {
            "name": "spring-boot-actuator",
            "sha256": "b5166bb202d494e66170b93b3f9c802fa7a0ed2e2d34224491a7eca1f76b3c90",
            "version": "2.5.0"
          },
          {
            "name": "spring-boot-actuator-autoconfigure",
            "sha256": "8f9edefef763d326761e51a2079f5d68dfa3dec1b5a2015d9a48a7743414fccb",
            "version": "2.5.0"
          },
          {
            "name": "spring-boot-autoconfigure",
            "sha256": "291adbe58b6ed20ddb788bfd62fbb4a43af7c55be38d1233714f5954ffe9142a",
            "version": "2.5.0"
          },
          {
            "name": "spring-boot-jarmode-layertools",
            "sha256": "8fa4acfd4e5f0564b7670e8551988d360ea914317a901cdf66bfe9e1bb155212",
            "version": "2.5.0"
          },
          {
            "name": "spring-context",
            "sha256": "6d9bb2dcc297f3eb5c5457cfe2159935ad7d77a2400c8252e4beb235c1168b9f",
            "version": "5.3.7"
          },
          {
            "name": "spring-core",
            "sha256": "cfb42af01d4796e567f0e64be439712551e0909f576ecae61dacf502f6b1a3e8",
            "version": "5.3.7"
          },
          {
            "name": "spring-expression",
            "sha256": "a818630d8e55077f2be7254f9dc03be1b7d01143f7c36f8cdcfc9c7be0b94573",
            "version": "5.3.7"
          },
          {
            "name": "spring-jcl",
            "sha256": "f7422de77287703879f04ad32df4fe8e24eee882654da34cd10121aaca1ef6aa",
            "version": "5.3.7"
          },
          {
            "name": "spring-web",
            "sha256": "bba996b82aaec5d896d9cd05e33525acabd263042d47486fbe2609d144e906a0",
            "version": "5.3.7"
          },
          {
            "name": "spring-webmvc",
            "sha256": "5989fdb8cfcfb0fe36ca45cdb55db2caab37d197e1c3ca5a341f7cb2a23845d8",
            "version": "5.3.7"
          },
          {
            "name": "tomcat-embed-core",
            "sha256": "9d0eb75a9cd27b16e6202ef575fd3a3104ce7be10f10761aa6056d035408884e",
            "version": "9.0.46"
          },
          {
            "name": "tomcat-embed-el",
            "sha256": "4072e18b53486cadb20a3a67ac45081ef26b7a2ef6e0471054b379caf8c610cd",
            "version": "9.0.46"
          },
          {
            "name": "tomcat-embed-websocket",
            "sha256": "9b5f9310a957859ac12f1429437845365a2c33d59100e63bbc4afd8a1e90747e",
            "version": "9.0.46"
          }
        ],
        "layer": "application"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.4.2"
      }
    },
    {
      "name": "helper",
      "metadata": {
        "layer": "helper",
        "names": [
          "spring-cloud-bindings"
        ],
        "version": "4.4.2"
      },
      "buildpacks": {
        "id": "paketo-buildpacks/spring-boot",
        "version": "4.4.2"
      }
    },
    {
      "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.4.2"
      }
    }
  ]
}

Widzimy tutaj np. jakiego dokładnie jre używamy, skąd było ściągnięte, przez jaki buildack, jaka jest jego wersja oraz na jakiej licencji jest dystrybuowane. Podobnie sprawa ma się do zależności wewnątrz aplikacji choć tutaj nie mamy już tak szerokiej gamy informacji jak przy zależnościach obrazu, to znalazły się tam takie informacje jak nazwa zależności, wersja i sha artefaktu.

Mając takie informacje na wyciągnięcie ręki łatwo jest sprawdzić, które obrazy korzystają z jakich wersji bibliotek, javy czy też OS. Dzięki temu jesteśmy w stanie w prosty sposób namierzyć obrazy, które trzeba zaktualizować np. w związku z jakąś podatnością, o czym napiszę w dalszej części posta. Oczywiście jak ktoś ma 10 aplikacji to obejdzie się bez inspekcji obrazu dostarczonej przez CNB, ale jak takich aplikacji jest 30, 50, 100 czy jeszcze więcej to ciężko na tym zapanować bez odpowiednich narzędzi.

Informacje o builderze

Oprócz sprawdzania obrazu aplikacji możemy również sprawdzić obraz budujący (Builder image):

pack inspect-builder paketobuildpacks/builder:base Inspecting default builder: 'paketobuildpacks/builder:base'   REMOTE:   Description: Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Ruby, NGINX and Procfile   Created By: Name: Pack CLI Version: 0.20.0+git-66a4f32.build-2668   Trusted: Yes   Stack: ID: io.buildpacks.stacks.bionic   Lifecycle: Version: 0.11.4 Buildpack APIs: Deprecated: (none) Supported: 0.2, 0.3, 0.4, 0.5, 0.6 Platform APIs: Deprecated: (none) Supported: 0.3, 0.4, 0.5, 0.6   Run Images: index.docker.io/paketobuildpacks/run:base-cnb gcr.io/paketo-buildpacks/run:base-cnb   Buildpacks: ID NAME VERSION HOMEPAGE paketo-buildpacks/apache-tomcat - 6.1.0 https://github.com/paketo-buildpacks/apache-tomcat paketo-buildpacks/azure-application-insights - 4.7.0 https://github.com/paketo-buildpacks/azure-application-insights paketo-buildpacks/bellsoft-liberica - 8.4.0 https://github.com/paketo-buildpacks/bellsoft-liberica paketo-buildpacks/bundle-install - 0.2.4 https://github.com/paketo-buildpacks/bundle-install paketo-buildpacks/bundler - 0.1.9 https://github.com/paketo-buildpacks/bundler paketo-buildpacks/ca-certificates - 2.3.2 https://github.com/paketo-buildpacks/ca-certificates paketo-buildpacks/ca-certificates - 2.4.0 https://github.com/paketo-buildpacks/ca-certificates paketo-buildpacks/debug - 3.2.0 https://github.com/paketo-buildpacks/debug paketo-buildpacks/dep - 0.1.1 https://github.com/paketo-buildpacks/dep paketo-buildpacks/dep-ensure - 0.1.1 https://github.com/paketo-buildpacks/dep-ensure paketo-buildpacks/dist-zip - 4.2.0 https://github.com/paketo-buildpacks/dist-zip paketo-buildpacks/dotnet-core - 0.6.0 https://github.com/paketo-buildpacks/dotnet-core paketo-buildpacks/dotnet-core-aspnet - 0.2.0 https://github.com/paketo-buildpacks/dotnet-core-aspnet paketo-buildpacks/dotnet-core-runtime - 0.2.0 https://github.com/paketo-buildpacks/dotnet-core-runtime paketo-buildpacks/dotnet-core-sdk - 0.1.14 https://github.com/paketo-buildpacks/dotnet-core-sdk paketo-buildpacks/dotnet-execute - 0.4.1 https://github.com/paketo-buildpacks/dotnet-execute paketo-buildpacks/dotnet-publish - 0.3.0 https://github.com/paketo-buildpacks/dotnet-publish paketo-buildpacks/encrypt-at-rest - 3.2.0 https://github.com/paketo-buildpacks/encrypt-at-rest paketo-buildpacks/environment-variables - 3.1.1 https://github.com/paketo-buildpacks/environment-variables paketo-buildpacks/environment-variables - 3.2.0 https://github.com/paketo-buildpacks/environment-variables paketo-buildpacks/executable-jar - 5.2.0 https://github.com/paketo-buildpacks/executable-jar paketo-buildpacks/go - 0.10.0 https://github.com/paketo-buildpacks/go paketo-buildpacks/go-build - 0.4.1 https://github.com/paketo-buildpacks/go-build paketo-buildpacks/go-dist - 0.6.0 https://github.com/paketo-buildpacks/go-dist paketo-buildpacks/go-mod-vendor - 0.3.1 https://github.com/paketo-buildpacks/go-mod-vendor paketo-buildpacks/google-stackdriver - 3.12.0 https://github.com/paketo-buildpacks/google-stackdriver paketo-buildpacks/graalvm - 6.4.1 https://github.com/paketo-buildpacks/graalvm paketo-buildpacks/gradle - 5.5.0 https://github.com/paketo-buildpacks/gradle paketo-buildpacks/icu - 0.0.102 https://github.com/paketo-buildpacks/icu paketo-buildpacks/image-labels - 3.1.2 https://github.com/paketo-buildpacks/image-labels paketo-buildpacks/image-labels - 3.2.0 https://github.com/paketo-buildpacks/image-labels paketo-buildpacks/java - 5.12.0 https://github.com/paketo-buildpacks/java paketo-buildpacks/java-native-image - 5.7.0 https://github.com/paketo-buildpacks/java-native-image paketo-buildpacks/jmx - 3.2.0 https://github.com/paketo-buildpacks/jmx paketo-buildpacks/leiningen - 3.3.0 https://github.com/paketo-buildpacks/leiningen paketo-buildpacks/maven - 5.4.0 https://github.com/paketo-buildpacks/maven paketo-buildpacks/mri - 0.2.5 https://github.com/paketo-buildpacks/mri paketo-buildpacks/native-image - 4.3.0 https://github.com/paketo-buildpacks/native-image paketo-buildpacks/nginx - 0.3.2 https://github.com/paketo-buildpacks/nginx paketo-buildpacks/node-engine - 0.7.1 https://github.com/paketo-buildpacks/node-engine paketo-buildpacks/node-module-bom - 0.1.2 https://github.com/paketo-buildpacks/node-module-bom paketo-buildpacks/node-run-script - 0.1.0 https://github.com/paketo-buildpacks/node-run-script paketo-buildpacks/node-start - 0.3.0 https://github.com/paketo-buildpacks/node-start paketo-buildpacks/nodejs - 0.7.3 https://github.com/paketo-buildpacks/nodejs paketo-buildpacks/npm-install - 0.4.0 https://github.com/paketo-buildpacks/npm-install paketo-buildpacks/npm-start - 0.3.0 https://github.com/paketo-buildpacks/npm-start paketo-buildpacks/passenger - 0.1.1 https://github.com/paketo-buildpacks/passenger paketo-buildpacks/procfile - 4.2.2 https://github.com/paketo-buildpacks/procfile paketo-buildpacks/procfile - 4.3.0 https://github.com/paketo-buildpacks/procfile paketo-buildpacks/puma - 0.0.61 https://github.com/paketo-buildpacks/puma paketo-buildpacks/rackup - 0.0.59 https://github.com/paketo-buildpacks/rackup paketo-buildpacks/rails-assets - 0.2.4 https://github.com/paketo-buildpacks/rails-assets paketo-buildpacks/rake - 0.0.14 https://github.com/paketo-buildpacks/rake paketo-buildpacks/ruby - 0.9.0 https://github.com/paketo-buildpacks/ruby paketo-buildpacks/sbt - 5.5.0 https://github.com/paketo-buildpacks/sbt paketo-buildpacks/spring-boot - 4.5.0 https://github.com/paketo-buildpacks/spring-boot paketo-buildpacks/thin - 0.0.54 https://github.com/paketo-buildpacks/thin paketo-buildpacks/unicorn - 0.0.52 https://github.com/paketo-buildpacks/unicorn paketo-buildpacks/upx - 1.1.0 https://github.com/paketo-buildpacks/upx paketo-buildpacks/yarn - 0.4.0 https://github.com/paketo-buildpacks/yarn paketo-buildpacks/yarn-install - 0.4.0 https://github.com/paketo-buildpacks/yarn-install paketo-buildpacks/yarn-start - 0.2.0 https://github.com/paketo-buildpacks/yarn-start   Detection Order: ├ Group #1: │ ├ paketo-buildpacks/ruby@0.9.0 │ │ └ Group #1: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/mri@0.2.5 │ │ ├ paketo-buildpacks/bundler@0.1.9 │ │ ├ paketo-buildpacks/bundle-install@0.2.4 │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/yarn@0.4.0 (optional) │ │ ├ paketo-buildpacks/yarn-install@0.4.0 (optional) │ │ ├ paketo-buildpacks/rails-assets@0.2.4 (optional) │ │ ├ paketo-buildpacks/puma@0.0.61 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ ├ paketo-buildpacks/ruby@0.9.0 │ │ └ Group #2: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/mri@0.2.5 │ │ ├ paketo-buildpacks/bundler@0.1.9 │ │ ├ paketo-buildpacks/bundle-install@0.2.4 │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/yarn@0.4.0 (optional) │ │ ├ paketo-buildpacks/yarn-install@0.4.0 (optional) │ │ ├ paketo-buildpacks/rails-assets@0.2.4 (optional) │ │ ├ paketo-buildpacks/thin@0.0.54 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ ├ paketo-buildpacks/ruby@0.9.0 │ │ └ Group #3: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/mri@0.2.5 │ │ ├ paketo-buildpacks/bundler@0.1.9 │ │ ├ paketo-buildpacks/bundle-install@0.2.4 │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/yarn@0.4.0 (optional) │ │ ├ paketo-buildpacks/yarn-install@0.4.0 (optional) │ │ ├ paketo-buildpacks/rails-assets@0.2.4 (optional) │ │ ├ paketo-buildpacks/unicorn@0.0.52 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ ├ paketo-buildpacks/ruby@0.9.0 │ │ └ Group #4: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/mri@0.2.5 │ │ ├ paketo-buildpacks/bundler@0.1.9 │ │ ├ paketo-buildpacks/bundle-install@0.2.4 │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/yarn@0.4.0 (optional) │ │ ├ paketo-buildpacks/yarn-install@0.4.0 (optional) │ │ ├ paketo-buildpacks/rails-assets@0.2.4 (optional) │ │ ├ paketo-buildpacks/passenger@0.1.1 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ ├ paketo-buildpacks/ruby@0.9.0 │ │ └ Group #5: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/mri@0.2.5 │ │ ├ paketo-buildpacks/bundler@0.1.9 │ │ ├ paketo-buildpacks/bundle-install@0.2.4 │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/yarn@0.4.0 (optional) │ │ ├ paketo-buildpacks/yarn-install@0.4.0 (optional) │ │ ├ paketo-buildpacks/rails-assets@0.2.4 (optional) │ │ ├ paketo-buildpacks/rackup@0.0.59 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ └ paketo-buildpacks/ruby@0.9.0 │ └ Group #6: │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ ├ paketo-buildpacks/mri@0.2.5 │ ├ paketo-buildpacks/bundler@0.1.9 (optional) │ ├ paketo-buildpacks/bundle-install@0.2.4 (optional) │ ├ paketo-buildpacks/rake@0.0.14 │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ └ paketo-buildpacks/image-labels@3.2.0 (optional) ├ Group #2: │ ├ paketo-buildpacks/dotnet-core@0.6.0 │ │ └ Group #1: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/dotnet-core-runtime@0.2.0 │ │ ├ paketo-buildpacks/dotnet-core-aspnet@0.2.0 (optional) │ │ ├ paketo-buildpacks/dotnet-core-sdk@0.1.14 │ │ ├ paketo-buildpacks/icu@0.0.102 (optional) │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/dotnet-publish@0.3.0 │ │ ├ paketo-buildpacks/dotnet-execute@0.4.1 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ ├ paketo-buildpacks/dotnet-core@0.6.0 │ │ └ Group #2: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/dotnet-core-runtime@0.2.0 │ │ ├ paketo-buildpacks/dotnet-core-aspnet@0.2.0 (optional) │ │ ├ paketo-buildpacks/dotnet-core-sdk@0.1.14 (optional) │ │ ├ paketo-buildpacks/icu@0.0.102 (optional) │ │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ │ ├ paketo-buildpacks/dotnet-execute@0.4.1 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ └ paketo-buildpacks/dotnet-core@0.6.0 │ └ Group #3: │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ ├ paketo-buildpacks/icu@0.0.102 (optional) │ ├ paketo-buildpacks/node-engine@0.7.1 (optional) │ ├ paketo-buildpacks/dotnet-execute@0.4.1 │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ └ paketo-buildpacks/image-labels@3.2.0 (optional) ├ Group #3: │ ├ paketo-buildpacks/nodejs@0.7.3 │ │ └ Group #1: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/node-engine@0.7.1 │ │ ├ paketo-buildpacks/yarn@0.4.0 │ │ ├ paketo-buildpacks/yarn-install@0.4.0 │ │ ├ paketo-buildpacks/node-module-bom@0.1.2 (optional) │ │ ├ paketo-buildpacks/node-run-script@0.1.0 (optional) │ │ ├ paketo-buildpacks/yarn-start@0.2.0 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ ├ paketo-buildpacks/nodejs@0.7.3 │ │ └ Group #2: │ │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ │ ├ paketo-buildpacks/node-engine@0.7.1 │ │ ├ paketo-buildpacks/npm-install@0.4.0 │ │ ├ paketo-buildpacks/node-module-bom@0.1.2 (optional) │ │ ├ paketo-buildpacks/node-run-script@0.1.0 (optional) │ │ ├ paketo-buildpacks/npm-start@0.3.0 │ │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ │ └ paketo-buildpacks/image-labels@3.2.0 (optional) │ └ paketo-buildpacks/nodejs@0.7.3 │ └ Group #3: │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ ├ paketo-buildpacks/node-engine@0.7.1 │ ├ paketo-buildpacks/node-module-bom@0.1.2 (optional) │ ├ paketo-buildpacks/node-start@0.3.0 │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ └ paketo-buildpacks/image-labels@3.2.0 (optional) ├ Group #4: │ ├ paketo-buildpacks/go@0.10.0 │ │ └ Group #1: │ │ ├ paketo-buildpacks/ca-certificates@2.3.2 (optional) │ │ ├ paketo-buildpacks/go-dist@0.6.0 │ │ ├ paketo-buildpacks/go-mod-vendor@0.3.1 │ │ ├ paketo-buildpacks/go-build@0.4.1 │ │ ├ paketo-buildpacks/procfile@4.2.2 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.1.1 (optional) │ │ └ paketo-buildpacks/image-labels@3.1.2 (optional) │ ├ paketo-buildpacks/go@0.10.0 │ │ └ Group #2: │ │ ├ paketo-buildpacks/ca-certificates@2.3.2 (optional) │ │ ├ paketo-buildpacks/go-dist@0.6.0 │ │ ├ paketo-buildpacks/dep@0.1.1 │ │ ├ paketo-buildpacks/dep-ensure@0.1.1 │ │ ├ paketo-buildpacks/go-build@0.4.1 │ │ ├ paketo-buildpacks/procfile@4.2.2 (optional) │ │ ├ paketo-buildpacks/environment-variables@3.1.1 (optional) │ │ └ paketo-buildpacks/image-labels@3.1.2 (optional) │ └ paketo-buildpacks/go@0.10.0 │ └ Group #3: │ ├ paketo-buildpacks/ca-certificates@2.3.2 (optional) │ ├ paketo-buildpacks/go-dist@0.6.0 │ ├ paketo-buildpacks/go-build@0.4.1 │ ├ paketo-buildpacks/procfile@4.2.2 (optional) │ ├ paketo-buildpacks/environment-variables@3.1.1 (optional) │ └ paketo-buildpacks/image-labels@3.1.2 (optional) ├ Group #5: │ └ paketo-buildpacks/nginx@0.3.2 ├ Group #6: │ └ paketo-buildpacks/java-native-image@5.7.0 │ └ Group #1: │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ ├ paketo-buildpacks/upx@1.1.0 (optional) │ ├ paketo-buildpacks/graalvm@6.4.1 │ ├ paketo-buildpacks/leiningen@3.3.0 (optional) │ ├ paketo-buildpacks/gradle@5.5.0 (optional) │ ├ paketo-buildpacks/maven@5.4.0 (optional) │ ├ paketo-buildpacks/sbt@5.5.0 (optional) │ ├ paketo-buildpacks/executable-jar@5.2.0 (optional) │ ├ paketo-buildpacks/spring-boot@4.5.0 (optional) │ ├ paketo-buildpacks/native-image@4.3.0 │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ └ paketo-buildpacks/image-labels@3.2.0 (optional) ├ Group #7: │ └ paketo-buildpacks/java@5.12.0 │ └ Group #1: │ ├ paketo-buildpacks/ca-certificates@2.4.0 (optional) │ ├ paketo-buildpacks/bellsoft-liberica@8.4.0 │ ├ paketo-buildpacks/leiningen@3.3.0 (optional) │ ├ paketo-buildpacks/gradle@5.5.0 (optional) │ ├ paketo-buildpacks/maven@5.4.0 (optional) │ ├ paketo-buildpacks/sbt@5.5.0 (optional) │ ├ paketo-buildpacks/executable-jar@5.2.0 (optional) │ ├ paketo-buildpacks/apache-tomcat@6.1.0 (optional) │ ├ paketo-buildpacks/dist-zip@4.2.0 (optional) │ ├ paketo-buildpacks/spring-boot@4.5.0 (optional) │ ├ paketo-buildpacks/procfile@4.3.0 (optional) │ ├ paketo-buildpacks/azure-application-insights@4.7.0 (optional) │ ├ paketo-buildpacks/debug@3.2.0 (optional) │ ├ paketo-buildpacks/google-stackdriver@3.12.0 (optional) │ ├ paketo-buildpacks/jmx@3.2.0 (optional) │ ├ paketo-buildpacks/encrypt-at-rest@3.2.0 (optional) │ ├ paketo-buildpacks/environment-variables@3.2.0 (optional) │ └ paketo-buildpacks/image-labels@3.2.0 (optional) └ Group #8: └ paketo-buildpacks/procfile@4.3.0  

Dowiemy się stąd między innymi, które wersje API Lifecycle,Buidlpacks i Platform są wspierane przez dany builder, a które są oznaczone jako deprecated. Dodatkowo mamy informacje jaki run image będzie wykorzystywany w zbudowanej aplikacji Oraz listę wszystkich wspieranych przez obraz buildpacków razem z ich wersjami i linkiem do githuba.

Na koniec przedstawiona jest kolejność wykrywania Buildpacków podzielona na grupy, względem języka, Niektóre grupy dodatkowo zawierają podgrupy różniące się od siebie zawartością buildpacków. Przykładowo język Ruby składa się z aż 6 podgrup, które różnią się od siebie rodzajem buildpacków wchodzących w ich skład np. podgrupa 1 zawiera pakiet puma a druga grupa zamiast tego pakietu zawiera pakiet thin.

Dzięki takiemu pogrupowaniu meta-buildpacków builder jest w stanie szybko znaleźć meta-buidlpack spełniający wszystkie kryteria potrzebne do zbudowania aplikacji.

⚡Ważne⚡


Nie martw się, jeśli nie znalazłeś swojego Języka programowania na liście buildera paketo, jest duże prawdopodobieństwo, że znajdziesz go w innym builderze, a pack CLI Ci w tym pomoże. Będąc w folderze z kodem źródłowym aplikacji wpisz:

pack builder suggestSuggested 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  

Lub wejdź na stronę Buildpack Registry (buildpacks.io) i wyszukaj swój język.

Zmiana Run Image bez Przebudowy aplikacji

Kolejną zaletą CNB jest możliwość podmienienia Run Image bez przebudowy całej aplikacji. Jest to szczególnie przydatne podczas wgrywania łatek bezpieczeństwa, ale o tym napisze już w następnym artykule, gdzie będę poruszał problemy jakie można napotkać używając buildpacków na produkcji.

Cloud Native Bildpacks zalety i wady

  • One Builder to Rule Them All. Jeden spójny sposób na budowanie aplikacji niezależnie od używanego języka i bez vendor locka.
  • Globalny standard Cloud Native Computing Foundation, który możesz uruchomić na prawie każdej chmurze.
  • Obsługuje dużą ilość popularnych języków programowania.
  • Rebase warstwy systemu bez przebudowy całego obrazu np. w celu wgrania poprawek bezpieczeństwa.
  • Nie musimy wnikać w to jak zbudować poprawnie obraz i dbać o "dobre praktyki", wszystko to jest już zrobione za nas.
  • Automatyczny oraz inteligentny podział obrazu na warstwy.
  • Podział aplikacji springowej na warstwy, rozdzielenie zależności od kodu.
  • Cache dla zależności mavena (gdy budujemy z kodu źródłowego).
  • Dobre praktyki tworzenia obrazów wpływające na ich rozmiar i bezpieczeństwo np. obrazy okrojone do wymaganych pakietów czy dedykowany użytkownik z ograniczonymi uprawnieniami zamiast roota (o czym nie zawsze się pamięta pisząc ręcznie dockerfile ).
  • Działa (prawie) out of the box (więcej o problemach w następnym artykule).
  • Możliwość zbudowania obrazu pluginem springa.
  • Obraz zawiera BOM (Bill of materials) czyli wszystkie informacje dotyczące zależności użytych do jego zbudowania jak i biblioteki użyte do zbudowania aplikacji.
  • Stałe poprawki bezpieczeństwa wraz oraz aktualizacje buildpacków.
  • Gdy serwer budujący jest odcięty od Internetu (githuba) trochę więcej pracy trzeba włożyć, gdy chcemy dostarczyć łatki bezpieczeństwa lub zaktualizować używane buidlpacki.
  • Gdy w obrazie base/tiny nie ma pakietu, którego potrzebujemy (np. czcionki czy fontconfig) musimy ściągnąć obraz full który sporo zajmuje. Lub dostarczyć swój własny run image zawierający potrzebne biblioteki/binarki.
  • Obecna wersja jest relatywnie młodą technologią, dlatego też jeszcze do niedawna mało konkretnych informacji można było znaleźć w Internecie.
  • Obraz Na starcie ma creation date - 41lat lub więcej zależności, w którym roku go budujemy :) (wyjaśnię to w następnym artykule).

Podsumowanie

W tym dość długim artykule chciałem przedstawić w przystępny sposób wszystko to czego dowiedziałem się podczas wykorzystywania opisywanej technologii.

Moim zdaniem fakt wprowadzenia tego standardu do Cloud Native Computing Fundation oraz to jakie firmy stoją za tym rozwiązaniem sugeruje, że w najbliższych latach będzie ono zyskiwało na popularności i coraz więcej projektów będzie je wykorzystywać. Kto wie może wtedy właśnie ten post pomoże komuś w zrozumieniu tej technologii i wdrożeniu Cloud Native Buildpack do swojego projektu. ;)  

A co Ty o tym sądzisz drogi czytelniku? Może widzisz jeszcze jakieś za i przeciw lub tematy związane z CNB, które należałoby rozwinąć? Zachęcam do podzielenia się swoimi przemyśleniami w komentarzach.  

Niedługo pojawi się kolejny artykuł dotyczący problemów jakie można napotkać podczas korzystania z CNB oraz tego jak je rozwiązać, dlatego bądźcie czujni :)  

Grafika główna posta została stworzona z wykorzystaniem vektorów popranych ze strony Freepik