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?
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
- 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
- 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 ?
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 paketo-buildpacks/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. paketo-buildpacks/bellsoft-liberica. Po wykonaniu wszystkich operacji zmiany widoczne są dla kolejnego buildpacka, jakim jest np. paketo-buildpacks/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 paketo-buildpacks/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 paketo-buildpacks/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 suggest
Suggested builders:
Google: 'gcr.io/buildpacks/builder:v1'
Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python
Heroku: 'heroku/buildpacks:18'
Base builder for Heroku-18 stack, based on ubuntu:18.04 base image
Heroku: 'heroku/buildpacks:20'
Base builder for Heroku-20 stack, based on ubuntu:20.04 base image
Paketo Buildpacks: 'paketobuildpacks/builder:base'
Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, Ruby, NGINX and Procfile
Paketo Buildpacks: 'paketobuildpacks/builder:full'
Ubuntu bionic base image with buildpacks for Java, .NET Core, NodeJS, Go, PHP, Ruby, Apache HTTPD, NGINX and Procfile
Paketo Buildpacks: 'paketobuildpacks/builder:tiny'
Tiny base image (bionic build image, distroless-like run image) with buildpacks for Java Native Image and Go
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