Containerele au devenit metoda preferată de ambalare a aplicației împreună cu toate dependențele software-ului și ale sistemului de operare, apoi livrarea acestora în diverse medii.
În acest articol sunt discutate diferite moduri de containerizare a aplicației Spring Boot:
- crearea unei imagini Docker folosind un fișier Docker,
- crearea unei imagini OCI din codul sursă folosind Cloud-Native Buildpack,
- și optimizarea imaginii la rulare prin împărțirea părților JAR în diferite niveluri folosind uneltele multi-stage.
Exemplu de cod
Acest articol este însoțit de un exemplu de cod funcțional .
Terminologia containerelor
Vom începe cu terminologia containerelor utilizată în articol:
- Imaginea containerului (Container image): un fișier de un anumit format. Noi convertim aplicația noastră într-o imagine a containerului, rulând instrumentul de construcție.
- Container: o instanță executabilă a imaginii containerului.
- Motorul containerului (Container engine): un proces daemon responsabil de rularea containerului.
- Gazda containerului (Container host): computerul gazdă pe care rulează motorul containerului.
- Registrul containerelor (Container registry): un depozit comun folosit pentru publicarea și distribuția imaginii containerului.
- Standardul OCI: — este o structură deschisă, ușor de gestionat, formată sub egida Linux Foundation. Specificația imaginilor OCI definește standardele industriei pentru formatele imaginilor containerelor și rularea acestora, pentru a asigura că toate motoarele containerelor pot rula imagini ale containerelor create de orice instrument de construcție.
Pentru a plasa aplicația într-un container, noi înconjurăm aplicația noastră într-o imagine a containerului și publicăm această imagine într-un registru comun. Mediu de rulare al containerului extrage această imagine din registru, o despachetează și rulează aplicația în interiorul acesteia.
Versiunea 2.3 Spring Boot oferă pluginuri pentru crearea imaginilor OCI.
— cea mai utilizată implementare a containerului, și folosim Docker în exemplele noastre, deci toate referințele ulterioare la container în acest articol se vor referi la Docker.
Construirea imaginii containerului în mod tradițional
Crearea imaginilor Docker pentru aplicațiile Spring Boot este foarte simplă, adăugând câteva instrucțiuni în fișierul Docker.
În primul rând, creăm un fișier executabil JAR și, ca parte a instrucțiunilor din fișierul Docker, copiem fișierul executabil JAR peste imaginea de bază JRE după aplicarea configurărilor necesare.
Să creăm aplicația noastră Spring pe cu dependențe web, lombokși actuator. De asemenea, adăugăm un controler REST pentru a oferi un API cu metoda GET.методом.
Crearea fișierului Docker
Apoi, plasăm această aplicație într-un container, adăugând Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=targetoldere/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]Fișierul nostru Docker conține imaginea de bază, din adoptopenjdk, peste care copiem fișierul nostru JAR, iar apoi deschidem portul 8080care va asculta cererile.
Construirea aplicației
În primul rând, trebuie să creăm aplicația folosind Maven sau Gradle. Aici folosim Maven:
mvn clean packageAceasta creează un fișier JAR executabil al aplicației. Trebuie să transformăm acest JAR executabil într-o imagine Docker pentru a funcționa în motorul Docker.
Crearea imaginii containerului
Apoi, plasăm acest fișier JAR executabil în imaginea Docker, executând comanda docker builddin directorul rădăcină al proiectului, care conține fișierul Docker creat anterior:
docker build -t usersignup:v1 .Putem vedea imaginea noastră în listă folosind comanda:
docker images Rezultatul executării comenzii de mai sus include imaginea noastră usersignupîmpreună cu imaginea de bază, adoptopenjdk, specificată în fișierul nostru Docker.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MBVizualizarea straturilor din imaginea containerului
Să ne uităm la stiva straturilor din imagine. Vom folosi pentru a vizualiza aceste straturi:
dive usersignup:v1Iată o parte din rezultatele executării comenzii Dive:

După cum vedem, nivelul aplicației reprezintă o parte semnificativă din dimensiunea imaginii. Vrem să reducem dimensiunea acestui strat în secțiunile următoare ca parte a optimizării noastre.
Crearea imaginii containerului folosind Buildpack
() este un termen general folosit de diverse oferte de „Platform as a Service” (PAAS) pentru a crea imagini de container din codul sursă. A fost lansat de Heroku în 2011 și a fost adoptat de Cloud Foundry, Google App Engine, Gitlab, Knative și altele.

Avantajul utilizării Buildpack în cloud
Unul dintre principalele avantaje ale utilizării Buildpack pentru a crea imagini este acela că Modificările configurației imaginii pot fi gestionate centralizat (builder) și distribuite către toate aplicațiile care folosesc builder.
Pachetele de construire erau strâns legate de platformă. Cloud-Native Buildpacks oferă standardizare între platforme, susținând formatul imaginii OCI, care garantează că imaginea poate fi rulată pe motorul Docker.
Folosirea pluginului Spring Boot
Pluginul Spring Boot creează imagini OCI din codul sursă folosind Buildpack. Imaginile sunt create folosind bootBuildImagesarcini (Gradle) sau spring-boot:build-imageobiective (Maven) și instalarea locală Docker.
Putem configura numele imaginii necesare pentru a fi trimisă în registrul Docker, specificând numele în image tag:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<name>docker.io/pratikdas/${project.artifactId}:v1</name>
</image>
</configuration>
</plugin>Să folosim Maven pentru a executa build-imageobiectivul de creare a aplicației și generarea imaginii containerului. Momentan nu folosim niciun fișier Docker.
mvn spring-boot:build-imageRezultatul va fi aproximativ așa:
[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Construind imaginea 'docker.io/pratikdas/usersignup:v1'
[INFO]
[INFO] > Trăgând imaginea builder 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator] Adăugând eticheta 'org.springframework.boot.version'
.. [creator] *** Imagini (c311fe74ec73):
.. [creator] docker.io/pratikdas/usersignup:v1
[INFO]
[INFO] Imaginea 'docker.io/pratikdas/usersignup:v1' a fost construită cu succesDin ieșirea anterioară vedem că paketo Cloud-Native buildpackeste utilizat pentru a crea o imagine OCI funcțională. Ca și înainte, putem vedea imaginea specificată ca imagine Docker, executând comanda:
docker images Concluzie:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MBCrearea imaginii containerului cu Jib
Jib este un plugin pentru crearea imaginilor de la Google, care oferă o metodă alternativă de a crea imaginea containerului din codul sursă.
Configurăm jib-maven-pluginîn pom.xml:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.5.2</version>
</plugin>Apoi, executăm pluginul Jib folosind comanda Maven pentru a construi aplicația și a crea imaginea containerului. Ca și înainte, aici nu folosim niciun fișier Docker:
mvn compile jib:build -Dimage=<docker registry name>/usersignup:v1După executarea comenzii Maven de mai sus, obținem următoarea ieșire:
[INFO] Containerează aplicația în pratikdas/usersignup:v1...
.
.
[INFO] Punctul de intrare al containerului a fost setat la [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO]
[INFO] Imagine construită și împinsă ca pratikdas/usersignup:v1
[INFO] Executând sarcini:
[INFO] [==============================] 100.0% completIeșirea arată că imaginea containerului a fost creată și plasată în registru.
Motivații și metode pentru crearea imaginilor optimizate
Avem două motive principale pentru optimizare:
- Performanță: în sistemul de orchestrare a containerelor, imaginea containerului este extrasă din registrul imaginilor pe gazda pe care rulează motorul containerului. Acest proces se numește planificare. Extracția imaginilor de mari dimensiuni din registru duce la timpi lungi de planificare în sistemele de orchestrare a containerelor și la timpi de construcție extinși în canalizările CI.
- Securitate: imaginile de mari dimensiuni au, de asemenea, o suprafață mare pentru vulnerabilități.
O imagine Docker constă dintr-un stivă de straturi, fiecare reprezentând o instrucțiune în Dockerfile-ul nostru. Fiecare strat reprezintă o deltă a modificărilor stratului de dedesubt. Când extragem o imagine Docker din registru, ea este extrasă strat cu strat și este cache-uită pe gazdă.
Spring Boot folosește Formula de optimizare este centrată în jurul izolării aplicației la un nivel separat de dependențele Spring Framework.
Stratul de dependențe, care formează partea principală a fișierului JAR gros, este încărcat o singură dată și cache-uit în sistemul gazdă.
Doar stratul subțire al aplicației este extras în timpul actualizărilor aplicației și planificării containerelor,
așa cum se arată în această diagramă: În secțiunile următoare, vom explora cum să creăm aceste imagini optimizate pentru aplicația Spring Boot.

Crearea unei imagini optimizate a containerului pentru aplicația Spring Boot folosind Buildpack
Spring Boot 2.3 suportă multilayering prin extracția părților fișierului JAR gros în straturi separate. Funcția de layering este dezactivată implicit și trebuie activată explicit folosind pluginul Spring Boot Maven:
Spring Boot 2.3 поддерживает многоуровневость путем извлечения частей толстого JAR-файла в отдельные слои. Функция наслоения по умолчанию отключена, и ее необходимо явно включить с помощью плагина Spring Boot Maven:
org.springframework.boot
spring-boot-maven-plugin
trueVom vom utiliza această configurație pentru a crea imaginea nostru de container, mai întâi cu Buildpack și apoi cu Docker în următoarele secțiuni.
Să lansăm build-imageținta Maven pentru a crea imaginea containerului:
mvn spring-boot:build-imageDacă rulăm Dive pentru a vedea straturile din imaginea rezultată, vom observa că stratul aplicației (marcat cu roșu) este cu mult mai mic, în intervalul kilobyte, comparativ cu ceea ce am obținut folosind formatul JAR gros:

Crearea unei imagini optimizate a containerului pentru aplicația Spring Boot cu ajutorul Docker
În loc să folosim pluginul Maven sau Gradle, putem crea, de asemenea, o imagine Docker multistrat JAR cu un fișier Docker.
Când folosim Docker, trebuie să executăm două pași suplimentari pentru a extrage straturile și a le copia în imaginea finală.
Conținutul JAR-ului rezultat după compilarea cu Maven activat cu funcția de suprapunere va arăta astfel:
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În ieșirea se afișează un JAR suplimentar numit spring-boot-jarmode-layertoolsși layersfle.idxfișier. Acest fișier JAR suplimentar oferă capacitatea de procesare multistrat, așa cum este descris în următoarea secțiune.
Extracția dependențelor pe straturi separate
Pentru a vizualiza și a extrage straturile din JAR-ul nostru multistrat, folosim proprietatea de sistem -Djarmode=layertoolspentru a rula spring-boot-jarmode-layertoolsJAR în loc de aplicație:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarExecutarea acestei comenzi produce o ieșire care conține opțiunile de comandă disponibile:
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 commandIeșirea arată comenzile list, extractși helpde helpsă fie implicit. Să rulăm comanda cu listopțiunea:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdependencies
spring-boot-loader
snapshot-dependencies
applicationVedem o listă de dependențe care pot fi adăugate ca straturi.
Straturile implicite:
Numele stratului
Cuprins
dependencies
orice dependență a cărei versiune nu conține SNAPSHOT
spring-boot-loader
Clasele încărcătorului JAR
snapshot-dependencies
orice dependență a cărei versiune conține SNAPSHOT
application
clasele aplicațiilor și resursele
Straturile sunt definite în layers.idxfișier în ordinea în care trebuie adăugate în imaginea Docker. Aceste straturi sunt stocate în cache pe gazdă după prima extragere, deoarece nu se schimbă. Numai stratul actualizat al aplicației este încărcat pe gazdă, ceea ce se întâmplă mai rapid datorită dimensiunii reduse .
Construirea imaginii cu dependențele extrase în straturi separate
Vom construi imaginea finală în două etape, folosind o metodă numită . În prima etapă, vom extrage dependențele, iar în a doua etapă vom copia dependențele extrase în imaginea finală.
Să modificăm fișierul nostru Docker pentru compilarea multimodală:
# 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"]Salvăm această configurație într-un fișier separat — Dockerfile2.
Construim imaginea Docker folosind comanda:
docker build -f Dockerfile2 -t usersignup:v1 .După ce executăm această comandă, obținem următorul output:
Sendind contextul de construcție către daemon-ul Docker 20.41MB
Step 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pulling from library/adoptopenjdk
.
.
Construit cu succes a9ebf6970841
Etichetat cu succes userssignup:v1Vedem că imaginea Docker este creată cu un ID de imagine, iar apoi este etichetată.
În cele din urmă, executăm comanda Dive, ca și înainte, pentru a verifica straturile din interiorul imaginii Docker generate. Putem specifica ID-ul imaginii sau eticheta ca input pentru comanda Dive:
dive userssignup:v1După cum se vede din output, stratul care conține aplicația acum ocupă doar 11 KB, iar dependențele sunt stocate în straturi separate.

Extracția dependențelor interne în straturi separate
Putem reduce suplimentar dimensiunea stratului aplicației, extrăgând orice dintre dependențele noastre personalizate într-un strat separat în loc să le împachetăm împreună cu aplicația, declarându-le în ymlun fișier asemănător numit 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/"În acest fișier layers.idxam adăugat o dependență personalizată cu numele, io.myorgcare conține dependențele organizației obținute dintr-un repository comun.
Ieșire
În acest articol, am examinat utilizarea Cloud-Native Buildpacks pentru a crea o imagine de container direct din codul sursă. Aceasta este o alternativă la utilizarea Docker pentru a crea o imagine de container în mod obișnuit: mai întâi se creează un fișier executabil JAR mare, iar apoi acesta este împachetat într-o imagine de container, specificând instrucțiunile în fișierul Docker.
De asemenea, am analizat optimizarea containerului nostru, inclusiv funcția de layering, care extrage dependențele în straturi separate, care sunt cache-uite pe gazdă, iar stratul subțire al aplicației este încărcat în timpul programării în mecanismele de execuție ale containerului.
Puteți găsi întregul cod sursă utilizat în articol pe .
Ghidul comenzilor
Iată un rezumat al comenzilor pe care le-am folosit în acest articol pentru o familiarizare rapidă.
Curățarea contextului:
docker system prune -aCrearea imaginii de container cu ajutorul unui fișier Docker:
docker build -f -t .Construim imaginea de container din codul sursă (fără Dockerfile):
mvn spring-boot:build-imageVizualizarea straturilor de dependență. Înainte de a construi fișierul JAR al aplicației, asigurați-vă că funcția de layering este activată în spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar listExtracția straturilor de dependență. Înainte de a construi fișierul JAR al aplicației, asigurați-vă că funcția de layering este activată în spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar extractVizualizarea listei de imagini de container
docker imagesVizualizarea straturilor din interiorul imaginii de container (asigurați-vă că instrumentul de dive este instalat):
diveSursa: habr.com
