ProHoster > BLOG > administrare > Crearea de imagini Docker optimizate pentru o aplicație Spring Boot
Crearea de imagini Docker optimizate pentru o aplicație Spring Boot
Containerele au devenit mijlocul preferat de a împacheta o aplicație cu toate dependențele sale de software și sisteme de operare și apoi de a le livra în diferite medii.
Acest articol acoperă diferite moduri de a containeriza o aplicație Spring Boot:
crearea unei imagini Docker folosind un fișier Docker,
crearea unei imagini OCI din sursă folosind Cloud-Native Buildpack,
și optimizarea imaginii în timpul execuției prin separarea părților JAR în straturi diferite folosind instrumente cu mai multe niveluri.
Cod simplu
Acest articol este însoțit de un exemplu de cod de lucru pe GitHub .
Terminologia containerelor
Vom începe cu terminologia containerului folosită în articol:
Imaginea containerului: fișier cu un anumit format. Vom converti aplicația noastră într-o imagine de container rulând instrumentul de compilare.
Recipient: o instanță executabilă a imaginii containerului.
Motor container: Procesul demon responsabil pentru rularea containerului.
Gazdă container: computerul gazdă pe care rulează motorul containerului.
Registrul containerelor: Locația generală folosită pentru publicarea și distribuirea imaginii containerului.
Standardul OCI: Inițiativa Open Container (OCI) este o structură de guvernanță ușoară, deschisă, formată în cadrul Fundației Linux. Specificația de imagine OCI definește standardele din industrie pentru imaginea containerului și formatele de rulare pentru a se asigura că toate motoarele de containere pot rula imagini de container create de orice instrument de construire.
Pentru a containeriza o aplicație, împachetăm aplicația noastră într-o imagine container și publicăm acea imagine într-un registru partajat. Durata de rulare a containerului preia această imagine din registry, o despachetează și rulează aplicația în interiorul acesteia.
Versiunea 2.3 a Spring Boot oferă pluginuri pentru crearea de imagini OCI.
Docher este implementarea containerului cel mai frecvent utilizată și folosim Docker în exemplele noastre, astfel încât toate referințele ulterioare ale containerului din acest articol se vor referi la Docker.
Construirea unei imagini de container în mod tradițional
Crearea imaginilor Docker pentru aplicațiile Spring Boot este foarte ușoară, adăugând câteva instrucțiuni în fișierul Docker.
Mai întâi creăm un fișier JAR executabil și, ca parte a instrucțiunilor fișierului Docker, copiem fișierul JAR executabil deasupra imaginii JRE de bază după aplicarea setărilor necesare.
Să creăm aplicația noastră Spring pe Spring Initializr cu dependențe web, lombokи actuator. De asemenea, adăugăm un controler de odihnă pentru a oferi un API GETmetodă.
Crearea unui fișier Docker
Apoi containerizam această aplicație prin adăugare Dockerfile:
Fișierul nostru Docker conține o imagine de bază din adoptopenjdk, deasupra căruia copiem fișierul nostru JAR și apoi deschidem portul, 8080care va asculta cererile.
Construirea aplicației
Mai întâi trebuie să creați o aplicație folosind Maven sau Gradle. Aici folosim Maven:
mvn clean package
Aceasta creează un fișier JAR executabil pentru aplicație. Trebuie să convertim acest JAR executabil într-o imagine Docker pentru a rula pe motorul Docker.
Crearea unei imagini de container
Apoi punem acest fișier JAR executabil în imaginea Docker executând comanda docker builddin directorul rădăcină al proiectului care conține fișierul Dockerfile creat mai devreme:
docker build -t usersignup:v1 .
Putem vedea imaginea noastră în listă folosind comanda:
docker images
Ieșirea comenzii de mai sus include imaginea noastră usersignupîmpreună cu imaginea de bază, adoptopenjdkspecificat în fișierul nostru Docker.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MB
Vizualizați straturi în interiorul unei imagini de container
Să ne uităm la teancul de straturi din interiorul imaginii. Noi vom folosi instrument picaj pentru a vizualiza aceste straturi:
dive usersignup:v1
Iată o parte din rezultatul comenzii Dive:
După cum putem vedea, stratul de aplicație reprezintă o parte semnificativă din dimensiunea imaginii. Dorim să reducem dimensiunea acestui strat în secțiunile următoare, ca parte a optimizării noastre.
Crearea unei imagini de container folosind Buildpack
Pachete de asamblare (Pachete de construcție) este un termen general utilizat de diverse oferte Platform as a Service (PAAS) pentru a crea o imagine container din codul sursă. A fost lansat de Heroku în 2011 și de atunci a fost adoptat de Cloud Foundry, Google App Engine, Gitlab, Knative și alte câteva.
Avantajul pachetelor cloud build
Unul dintre principalele beneficii ale utilizării Buildpack pentru a crea imagini este că Modificările configurației imaginii pot fi gestionate central (builder) și propagate la toate aplicațiile folosind builder.
Pachetele de compilare au fost strâns cuplate la platformă. Cloud-Native Buildpacks oferă standardizare între platforme prin sprijinirea formatului de imagine OCI, care asigură că imaginea poate fi rulată de motorul Docker.
Folosind pluginul Spring Boot
Pluginul Spring Boot construiește imagini OCI din sursă folosind Buildpack. Imaginile sunt create folosind bootBuildImagesarcini (Gradle) sau spring-boot:build-imageținte (Maven) și instalarea locală a Docker.
Putem personaliza numele imaginii necesare pentru a trimite în registrul Docker specificând numele în image tag:
Să folosim Maven pentru a face asta build-imageobiective pentru crearea unei aplicații și crearea unei imagini container. Nu folosim niciun Dockerfile în acest moment.
Din rezultat vedem că paketo Cloud-Native buildpackfolosit pentru a crea o imagine OCI de lucru. Ca și înainte, putem vedea imaginea listată ca imagine Docker, rulând comanda:
Apoi, rulăm pluginul Jib folosind comanda Maven pentru a construi aplicația și a crea o imagine de container. Ca și înainte, nu folosim niciun fișier Docker aici:
După executarea comenzii Maven de mai sus, obținem următoarea ieșire:
[INFO] Containerizing application to pratikdas/usersignup:v1...
.
.
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, io.pratik.users.UsersignupApplication]
[INFO]
[INFO] Built and pushed image as pratikdas/usersignup:v1
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete
Rezultatul arată că imaginea containerului a fost creată și plasată în registru.
Motivații și tehnici pentru crearea de imagini optimizate
Avem două motive principale pentru optimizare:
productivitate: Într-un sistem de orchestrare a containerului, o imagine a containerului este preluată din registrul de imagini către gazda care rulează motorul containerului. Acest proces se numește planificare. Extragerea imaginilor mari din registry are ca rezultat timpi lungi de programare în sistemele de orchestrare a containerelor și timpi lungi de construire în conductele CI.
Безопасность: Imaginile mai mari au și o zonă mai mare pentru vulnerabilități.
O imagine Docker constă dintr-un teanc de straturi, fiecare dintre acestea reprezentând o instrucțiune în fișierul nostru Docker. Fiecare strat reprezintă o deltă a modificărilor din stratul de bază. Când extragem o imagine Docker din registry, aceasta este extrasă în straturi și stocată în cache pe gazdă.
Utilizări Spring Boot „BORCAN pentru grăsime” în ca format implicit de ambalare. Când ne uităm la JAR-ul gros, vedem că aplicația reprezintă o parte foarte mică din întregul JAR. Aceasta este partea care se schimbă cel mai des. Restul constă din dependențele Spring Framework.
Formula de optimizare se concentrează pe izolarea aplicației la un nivel separat de dependențele Spring Framework.
Stratul de dependență, care formează cea mai mare parte a fișierului JAR gros, este descărcat o singură dată și stocat în cache pe sistemul gazdă.
Doar un strat subțire al aplicației este extras în timpul actualizărilor aplicației și al programării containerelor. așa cum se arată în această diagramă:
În secțiunile următoare, vom analiza cum să creați aceste imagini optimizate pentru o aplicație Spring Boot.
Crearea unei imagini de container optimizate pentru o aplicație Spring Boot folosind Buildpack
Spring Boot 2.3 acceptă stratificarea prin extragerea unor părți dintr-un fișier JAR gros în straturi separate. Funcția de stratificare este dezactivată în mod implicit și trebuie să fie activată în mod explicit folosind pluginul Spring Boot Maven:
Vom folosi această configurație pentru a construi mai întâi imaginea containerului nostru cu Buildpack și apoi cu Docker în secțiunile următoare.
Hai să lansăm build-imageȚinta Maven pentru crearea imaginii containerului:
mvn spring-boot:build-image
Dacă rulăm Dive pentru a vedea straturile din imaginea rezultată, putem vedea că stratul de aplicație (subliniat cu roșu) este mult mai mic în intervalul de kiloocteți în comparație cu ceea ce am obținut folosind formatul JAR gros:
Crearea unei imagini de container optimizate pentru o aplicație Spring Boot folosind Docker
În loc să folosim un plugin Maven sau Gradle, putem crea și o imagine Docker JAR stratificată cu un fișier Docker.
Când folosim Docker, trebuie să facem doi pași suplimentari pentru a extrage straturile și a le copia în imaginea finală.
Conținutul JAR rezultat după construirea folosind Maven cu stratificarea activată va arăta astfel:
Ieșirea arată un JAR suplimentar numit spring-boot-jarmode-layertoolsи layersfle.idxfişier. Acest fișier JAR suplimentar oferă capabilități de procesare stratificată, așa cum este descris în secțiunea următoare.
Extragerea dependențelor de pe straturi individuale
Pentru a vizualiza și extrage straturi din JAR-ul nostru stratificat, folosim proprietatea sistemului -Djarmode=layertoolspentru început spring-boot-jarmode-layertoolsJAR în loc de aplicare:
Rularea acestei comenzi produce rezultate care conțin 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 command
Ieșirea arată comenzile list, extractи helpс helpfi implicit. Să rulăm comanda cu listopțiune:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
Vedem o listă de dependențe care pot fi adăugate ca straturi.
Straturi implicite:
Numele stratului
Conținut
dependencies
orice dependență a cărei versiune nu conține SNAPSHOT
spring-boot-loader
Clasele de încărcare JAR
snapshot-dependencies
orice dependență a cărei versiune conține SNAPSHOT
application
clase de aplicații și resurse
Straturile sunt definite în layers.idxfișierul în ordinea în care ar trebui să fie adăugat la imaginea Docker. Aceste straturi sunt stocate în cache în gazdă după prima recuperare, deoarece nu se schimbă. Doar stratul de aplicație actualizat este descărcat pe gazdă, ceea ce este mai rapid datorită dimensiunii reduse .
Construirea unei imagini cu dependențe extrase în straturi separate
Vom construi imaginea finală în două etape folosind o metodă numită asamblare în mai multe etape . În primul pas vom extrage dependențele și în al doilea pas vom copia dependențele extrase în imaginea finală.
Să modificăm fișierul Docker pentru o construcție în mai multe etape:
# 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ă rularea acestei comenzi, obținem următoarea ieșire:
Sending build context to Docker daemon 20.41MB
Step 1/12 : FROM adoptopenjdk:14-jre-hotspot as builder
14-jre-hotspot: Pulling from library/adoptopenjdk
.
.
Successfully built a9ebf6970841
Successfully tagged userssignup:v1
Putem vedea că o imagine Docker este creată cu un ID de imagine și apoi etichetată.
În cele din urmă, rulăm comanda Dive ca înainte pentru a inspecta straturile din interiorul imaginii Docker generate. Putem furniza un ID de imagine sau o etichetă ca intrare la comanda Dive:
dive userssignup:v1
După cum puteți vedea în rezultat, stratul care conține aplicația are acum doar 11 KB, iar dependențele sunt stocate în cache în straturi separate.
Extragerea dependențelor interne pe straturi individuale
Putem reduce și mai mult dimensiunea nivelului de aplicație prin extragerea oricăreia dintre dependențele noastre personalizate într-un nivel separat, în loc să le împachetăm cu aplicația declarându-le în ymlfișier similar numit layers.idx:
În acest dosar layers.idxam adăugat o dependență personalizată numită, io.myorgcare conțin dependențe de organizație preluate dintr-un depozit partajat.
Producție
În acest articol, ne-am uitat la utilizarea Cloud-Native Buildpacks pentru a construi 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 creând un fișier JAR executabil gros și apoi împachetați-l într-o imagine de container prin specificarea instrucțiunilor în fișierul Docker.
De asemenea, ne-am uitat la optimizarea containerului nostru prin activarea unei funcții de stratificare care trage dependențele în straturi separate care sunt stocate în cache pe gazdă și un strat subțire al aplicației este încărcat la momentul programării în motoarele de execuție ale containerului.
Puteți găsi tot codul sursă folosit în articol la Github .
Referință de comandă
Iată o scurtă descriere a comenzilor pe care le-am folosit în acest articol.
Curățarea contextului:
docker system prune -a
Crearea unei imagini de container folosind un fișier Docker:
docker build -f <Docker file name> -t <tag> .
Construim imaginea containerului din codul sursă (fără Dockerfile):
mvn spring-boot:build-image
Vizualizați straturile de dependență. Înainte de a construi fișierul JAR al aplicației, asigurați-vă că funcția de stratificare este activată în spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar list
Extragerea straturilor de dependență. Înainte de a construi fișierul JAR al aplicației, asigurați-vă că funcția de stratificare este activată în spring-boot-maven-plugin: