Tworzenie zoptymalizowanych obrazów Docker dla aplikacji Spring Boot

Kontenery stały się preferowanym środkiem pakowania aplikacji ze wszystkimi zależnościami oprogramowania i systemu operacyjnego, a następnie dostarczania ich do różnych środowisk.

W tym artykule omówiono różne metody konteneryzacji aplikacji Spring Boot:

  • tworzenie obrazu Dockera przy użyciu pliku Docker,
  • tworzenie obrazu OCI z kodu źródłowego za pomocą Cloud-Native Buildpack,
  • oraz optymalizacja obrazu w czasie rzeczywistym poprzez dzielenie części JAR na różne warstwy z użyciem narzędzi wielowarstwowych.

 Przykład kodu

Artykuł ten jest wspierany przykładem działającego kodu na GitHubie .

Terminologia kontenerów

Zaczniemy od terminologii kontenerów używanej w artykule:

  • Obraz kontenera (Container image): plik określonego formatu. Przekształcamy naszą aplikację w obraz kontenera, uruchamiając narzędzie do budowy.
  • Kontener: wykonywalna instancja obrazu kontenera.
  • Silnik kontenera (Container engine): proces demon, odpowiedzialny za uruchamianie kontenera.
  • Hosten kontenera (Container host): komputer gospodarza, na którym działa silnik kontenera.
  • Rejestr kontenerów (Container registry): ogólna lokalizacja używana do publikacji i rozpowszechniania obrazu kontenera.
  • Standard OCIOpen Container Initiative (OCI) — to lekka, otwarta struktura zarządzania, utworzona w ramach Linux Foundation. Specyfikacja obrazów OCI określa branżowe standardy dla formatów obrazów kontenerów oraz środowisk uruchomieniowych, aby zapewnić, że wszystkie silniki kontenerów mogą uruchamiać obrazy kontenerów stworzone przez dowolne narzędzie do budowy.

Aby umieścić aplikację w kontenerze, otaczamy naszą aplikację obrazem kontenera i publikujemy ten obraz w ogólnym rejestrze. Środowisko uruchomieniowe kontenera pobiera ten obraz z rejestru, rozpakowuje go i uruchamia aplikację wewnątrz niego.

Wersja 2.3 Spring Boot oferuje wtyczki do tworzenia obrazów OCI.

Docker — najczęściej używana implementacja kontenera, a w naszych przykładach używamy Dockera, dlatego wszystkie późniejsze odniesienia do kontenera w tym artykule będą odnosić się do Dockera.

Tworzenie obrazu kontenera tradycyjnym sposobem

Tworzenie obrazów Dockera dla aplikacji Spring Boot jest bardzo proste, wystarczy dodać kilka instrukcji do pliku Docker.

Na początku tworzymy plik wykonywalny JAR, a jako część instrukcji pliku Docker kopiujemy plik wykonywalny JAR na obraz bazowy JRE po zastosowaniu niezbędnych ustawień.

Stwórzmy naszą aplikację Spring na Spring Initializr z zależnościami weblombokactuator. Dodamy również kontroler rest, aby udostępnić API z i login/hasło: admin/admin.metodą.

Tworzenie pliku Docker

Następnie umieszczamy tę aplikację w kontenerze, dodając Dockerfile:

FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]

Nasz plik Docker zawiera obraz bazowy, z adoptopenjdk, na który kopiujemy nasz plik JAR, a następnie otwieramy port, 8080który będzie nasłuchiwał zapytań.

Budowanie aplikacji

Na początku musimy stworzyć aplikację za pomocą Mavena lub Gradle. Tutaj używamy Mavena:

mvn clean package

To tworzy wykonywalny plik JAR aplikacji. Musimy przekształcić ten plik JAR w obraz Dockera do pracy w silniku Docker.

Tworzenie obrazu kontenera

Następnie umieszczamy ten plik wykonywalny JAR w obrazie Docker, wykonując polecenie docker buildz katalogu głównego projektu, który zawiera wcześniej stworzony plik Docker:

docker build -t usersignup:v1 .

Możemy zobaczyć nasz obraz na liście za pomocą polecenia:

docker images 

Wynik wykonania powyższego polecenia zawiera nasz obraz usersignuprazem z obrazem bazowym, adoptopenjdk, wskazanym w naszym pliku Docker.

REPOSITORY          TAG                 SIZE
usersignup          v1                  249MB
adoptopenjdk        11-jre-hotspot      229MB

Podgląd warstw wewnątrz obrazu kontenera

Zobaczmy stos warstw wewnątrz obrazu. Użyjemy narzędzie  dive, aby zobaczyć te warstwy:

dive usersignup:v1

Oto część wyników wykonania polecenia Dive: 

Tworzenie zoptymalizowanych obrazów Docker dla aplikacji Spring Boot

Jak widać, poziom aplikacyjny stanowi znaczną część rozmiaru obrazu. Chcemy zmniejszyć rozmiar tej warstwy w następnych sekcjach w ramach naszej optymalizacji.

Tworzenie obrazu kontenera za pomocą Buildpack

Pakiety budujące (Buildpacks) to ogólny termin używany w różnych propozycjach «Platforma jako usługa» (PAAS) do tworzenia obrazu kontenera z kodu źródłowego. Został uruchomiony przez Heroku w 2011 roku i od tego czasu został przyjęty przez Cloud Foundry, Google App Engine, Gitlab, Knative i inne.

Tworzenie zoptymalizowanych obrazów Docker dla aplikacji Spring Boot

Zaleta korzystania z pakietów budujących w chmurze

Jedną z głównych zalet korzystania z Buildpack do tworzenia obrazów jest to, że Zmiany w konfiguracji obrazu można zarządzać centralnie (builder) i rozpowszechniać na wszystkie aplikacje, które korzystają z buildera.

Pakiety budowanie były ściśle związane z platformą. Cloud-Native Buildpacks zapewniają standaryzację między platformami, wspierając format obrazu OCI, który gwarantuje, że obraz może być uruchamiany przez silnik Docker.

Użycie wtyczki Spring Boot

Wtyczka Spring Boot tworzy obrazy OCI z kodu źródłowego za pomocą Buildpack. Obrazy są tworzony za pomocą bootBuildImagezadań (Gradle) lub spring-boot:build-imagecelów (Maven) oraz lokalnej instalacji Docker.

Możemy skonfigurować nazwę obrazu, który ma być wysłany do rejestru Docker, podając nazwę w image tag:

org.springframework.boot
  spring-boot-maven-plugin
  
    
      docker.io/pratikdas/${project.artifactId}:v1

Zastosujmy Maven do wykonania build-imagecelu do zbudowania aplikacji i stworzenia obrazu kontenera. Obecnie nie korzystamy z żadnych plików Docker.

mvn spring-boot:build-image

Wynik będzie mniej więcej taki:

[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Budowanie obrazu 'docker.io/pratikdas/usersignup:v1'
[INFO] 
[INFO]  > Pobieranie obrazu budującego 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator] Dodawanie etykiety 'org.springframework.boot.version'
.. [creator] *** Obrazy (c311fe74ec73):
.. [creator]           docker.io/pratikdas/usersignup:v1
[INFO] 
[INFO] Sukces: zbudowano obraz 'docker.io/pratikdas/usersignup:v1'

Z wyjściowych danych widzimy, że paketo Cloud-Native buildpackjest używany do tworzenia działającego obrazu OCI. Jak wcześniej, możemy zobaczyć obraz wskazany jako obraz Docker, wykonując polecenie:

docker images 

Wynik:

REPOSITORY                             SIZE
paketobuildpacks/run                  84.3MB
gcr.io/paketo-buildpacks/builder      652MB
pratikdas/usersignup                  257MB

Tworzenie obrazu kontenera za pomocą Jib

Jib to wtyczka do tworzenia obrazów od Google, która zapewnia alternatywną metodę tworzenia obrazu kontenera z kodu źródłowego.

Konfigurujemy jib-maven-pluginw pom.xml:

      com.google.cloud.tools
        jib-maven-plugin
        2.5.2

Następnie uruchamiamy wtyczkę Jib za pomocą polecenia Maven, aby zbudować aplikację i stworzyć obraz kontenera. Jak wcześniej, tutaj nie używamy żadnych plików Docker:

mvn compile jib:build -Dimage=/usersignup:v1

Po wykonaniu powyższego polecenia Maven otrzymujemy następujący wynik:

[INFO] Konteneryzacja aplikacji do pratikdas/usersignup:v1...
.
.
[INFO] Punkt wejścia kontenera ustawiony na [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO] 
[INFO] Zbudowano i wypchnięto obraz jako pratikdas/usersignup:v1
[INFO] Wykonywanie zadań:
[INFO] [==============================] 100,0% ukończono

Wyniki pokazują, że obraz kontenera został utworzony i umieszczony w rejestrze.

Motywacje i metody optymalizacji obrazów

Mamy dwa główne powody do optymalizacji:

  • Wydajność: w systemie orkiestracji kontenerów obraz kontenera jest pobierany z rejestru obrazów na hoście, na którym działa mechanizm kontenera. Proces ten nazywa się planowaniem. Pobieranie dużych obrazów z rejestru prowadzi do długiego czasu planowania w systemach orkiestracji kontenerów i długiego czasu budowy w potokach CI.
  • Bezpieczeństwo: duże obrazy mają także większy obszar do potencjalnych luk.

Obraz Docker składa się ze stosu warstw, z których każda reprezentuje instrukcję w naszym Dockerfile. Każda warstwa przedstawia deltę zmian niższej warstwy. Kiedy pobieramy obraz Docker z rejestru, jest on pobierany warstwami i buforowany na hoście.

Spring Boot korzysta z „grubego JAR-a” jako domyślnego formatu pakowania. Kiedy przeglądamy gruby JAR, widzimy, że aplikacja stanowi bardzo małą część całego JAR-a. To ta część zmienia się najczęściej. Pozostała część składa się z zależności Spring Framework. Formuła optymalizacji koncentruje się na izolowaniu aplikacji na osobnym poziomie od zależności Spring Framework.

Warstwa zależności, która tworzy główną część grubego pliku JAR, jest ładowana tylko raz i buforowana w systemie gospodarza.

Tylko cienka warstwa aplikacji jest pobierana podczas aktualizacji aplikacji i planowania kontenerów,

jak pokazano na tym diagramie: W kolejnych częściach przyjrzymy się, jak tworzyć te zoptymalizowane obrazy dla aplikacji Spring Boot.

Tworzenie zoptymalizowanych obrazów Docker dla aplikacji Spring Boot

Tworzenie zoptymalizowanego obrazu kontenera dla aplikacji Spring Boot za pomocą Buildpack

Spring Boot 2.3 wspiera wielowarstwowość poprzez wydzielenie części grubego pliku JAR w osobne warstwy. Funkcjonalność nakładania warstw jest domyślnie wyłączona i musi być wyraźnie włączona za pomocą wtyczki Spring Boot Maven:

Spring Boot 2.3 wspiera wiele warstw poprzez wydzielenie części grubego pliku JAR w osobne warstwy. Funkcja nakładania warstw domyślnie jest wyłączona i należy ją jawnie włączyć za pomocą wtyczki Spring Boot Maven:

org.springframework.boot
  spring-boot-maven-plugin
  
    
      true

Będziemy używać tej konfiguracji do tworzenia naszego obrazu kontenera, najpierw za pomocą Buildpack, a następnie za pomocą Dockera w następnych rozdziałach.

Uruchommy build-imagecel Maven do stworzenia obrazu kontenera:

mvn spring-boot:build-image

Jeśli uruchomimy Dive, aby zobaczyć warstwy w otrzymanym obrazie, zobaczymy, że warstwa aplikacji (podkreślona na czerwono) jest znacznie mniejsza w zakresie kilobajtów w porównaniu do tego, co uzyskaliśmy przy użyciu grubego formatu JAR:

Tworzenie zoptymalizowanych obrazów Docker dla aplikacji Spring Boot

Tworzenie zoptymalizowanego obrazu kontenera dla aplikacji Spring Boot za pomocą Dockera

Zamiast używać wtyczki Maven lub Gradle, możemy również stworzyć wielowarstwowy obraz JAR Dockera z plikiem Docker.

Kiedy używamy Dockera, musimy wykonać dwa dodatkowe kroki, aby wydobyć warstwy i skopiować je do ostatecznego obrazu.

Zawartość otrzymanego JAR po zbudowaniu za pomocą Maven z włączoną funkcją warstwowania będzie wyglądać następująco:

META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idx

W wyjściu wyświetla się dodatkowy JAR o nazwie spring-boot-jarmode-layertoolslayersfle.idxplik. Ten dodatkowy plik JAR umożliwia przetwarzanie wielowarstwowe, jak opisano w następnym rozdziale.

Ekstrakcja zależności w oddzielnych warstwach

Aby przeglądać i wydobywać warstwy z naszego wielowarstwowego JAR, używamy właściwości systemu -Djarmode=layertoolsdo uruchomienia spring-boot-jarmode-layertoolsJAR zamiast aplikacji:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar

Wykonanie tego polecenia zwraca wynik zawierający dostępne opcje poleceń:

Usage:
  java -Djarmode=layertools -jar usersignup-0.0.1-SNAPSHOT.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command

Wynik pokazuje polecenia listextracthelphelpbyć domyślnie. Uruchommy polecenie z listopcją:

java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application

Widzimy listę zależności, które można dodać jako warstwy.

Warstwy domyślnie:

Nazwa warstwy

Spis treści

dependencies

dowolna zależność, której wersja nie zawiera SNAPSHOT

spring-boot-loader

Klasy ładowarki JAR

snapshot-dependencies

dowolna zależność, której wersja zawiera SNAPSHOT

application

klasy aplikacji i zasoby

Warstwy zdefiniowane w layers.idxpliku w takiej kolejności, w jakiej mają być dodawane do obrazu Docker. Te warstwy są buforowane na hoście po pierwszym pobraniu, ponieważ się nie zmieniają. Na hosta ładowana jest tylko zaktualizowana warstwa aplikacji, co przebiega szybciej z powodu zmniejszonego rozmiaru. .

Budowanie obrazu z zależnościami pobranymi w osobnych warstwach.

Zbudujemy finalny obraz w dwóch etapach, korzystając z metody zwanej wielostopniowym budowaniem. W pierwszym etapie pobierzemy zależności, a w drugim etapie skopiujemy pobrane zależności do ostatecznego obrazu.

Dostosujmy nasz plik Docker do wielostopniowego budowania:

# the first stage of our build will extract the layers
FROM adoptopenjdk:14-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

# the second stage of our build will copy the extracted layers
FROM adoptopenjdk:14-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Zachowujemy tę konfigurację w osobnym pliku — Dockerfile2.

Budujemy obraz Docker za pomocą polecenia:

docker build -f Dockerfile2 -t usersignup:v1 .

Po wykonaniu tego polecenia otrzymujemy taki wynik:

Wysyłanie kontekstu budowy do demona Docker  20.41MB
Krok 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pobieranie z library/adoptopenjdk
.
.
Pomyslnie zbudowano a9ebf6970841
Pomyslnie oznaczono userssignup:v1

Widzimy, że obraz Docker jest tworzony z identyfikatorem obrazu, a następnie jest oznaczany.

Na koniec uruchamiamy polecenie Dive, jak wcześniej, aby sprawdzić warstwy wewnątrz wygenerowanego obrazu Docker. Możemy wskazać identyfikator obrazu lub tag jako dane wejściowe dla polecenia Dive:

dive userssignup:v1

Jak widać z wyników, warstwa zawierająca aplikację teraz zajmuje tylko 11 KB, a zależności są buforowane w osobnych warstwach. 

Tworzenie zoptymalizowanych obrazów Docker dla aplikacji Spring Boot

Pobieranie wewnętrznych zależności w osobnych warstwach.

Możemy dodatkowo zmniejszyć rozmiar warstwy aplikacji, pobierając wszelkie nasze zależności użytkownika w osobnej warstwie zamiast pakowania ich razem z aplikacją, deklarując je w ymlpodobnym pliku o nazwie layers.idx:

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "custom-dependencies":
  - "io/myorg/"
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

W tym pliku layers.idxdodaliśmy dostosowaną zależność o nazwie, io.myorgzawierającą zależności organizacji pobrane z ogólnego repozytorium.

Wnioski

W tym artykule omówiliśmy wykorzystanie Cloud-Native Buildpacks do tworzenia obrazu kontenera bezpośrednio z kodu źródłowego. To alternatywa dla używania Dockera do tradycyjnego tworzenia obrazu kontenera: najpierw tworzony jest gruby plik wykonywalny JAR, a następnie pakowany do obrazu kontenera za pomocą instrukcji w pliku Docker.

Omawialiśmy również optymalizację naszego kontenera, włączając funkcję nakładania, która wydziela zależności do oddzielnych warstw, które są buforowane na hoście, a cienka warstwa aplikacji jest ładowana podczas planowania w mechanizmach wykonawczych kontenera.

Możesz znaleźć cały kod źródłowy użyty w artykule na Podróż, którą przeszedłem, okazała się fascynującą wyprawą w przeszłość i cieszę się, że w końcu znalazłem rozwiązanie. Poza tym: .

Podręcznik poleceń

Oto krótki przegląd poleceń, które użyliśmy w tym artykule do szybkiego zapoznania się.

Oczyszczenie kontekstu:

docker system prune -a

Tworzenie obrazu kontenera za pomocą pliku Docker:

docker build -f  -t  .

Budowanie obrazu kontenera z kodu źródłowego (bez Dockerfile):

mvn spring-boot:build-image

Podgląd warstw zależności. Przed budową pliku JAR aplikacji upewnij się, że funkcja nakładania jest włączona w spring-boot-maven-plugin:

java -Djarmode=layertools -jar application.jar list

Wydobywanie warstw zależności. Przed budową pliku JAR aplikacji upewnij się, że funkcja nakładania jest włączona w spring-boot-maven-plugin:

 java -Djarmode=layertools -jar application.jar extract

Podgląd listy obrazów kontenerów

docker images

Podgląd warstw wewnątrz obrazu kontenera (upewnij się, że narzędzie do nurkowania jest zainstalowane):

dive

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster