Krijimi i imazheve të optimizuara të Docker për një aplikacion Spring Boot

Kontejnerët janë bërë mjeti i preferuar për paketimin e një aplikacioni me të gjitha varësitë e softuerit dhe sistemit operativ dhe më pas dërgimin e tyre në mjedise të ndryshme.

Ky artikull mbulon mënyra të ndryshme për të kontejneruar një aplikacion Spring Boot:

  • krijimi i një imazhi Docker duke përdorur një skedar Docker,
  • duke krijuar një imazh OCI nga burimi duke përdorur Cloud-Native Buildpack,
  • dhe optimizimi i imazhit në kohën e ekzekutimit duke ndarë pjesë të JAR në shtresa të ndryshme duke përdorur mjete me shumë nivele.

 Shembull kodi

Ky artikull shoqërohet me shembullin e kodit të punës në GitHub .

Terminologjia e kontejnerëve

Ne do të fillojmë me terminologjinë e kontejnerit të përdorur në artikull:

  • Imazhi i kontejnerit: skedar i një formati specifik. Ne do ta konvertojmë aplikacionin tonë në një imazh të kontejnerit duke ekzekutuar mjetin e ndërtimit.
  • Enë: Një shembull i ekzekutueshëm i imazhit të kontejnerit.
  • Motori i kontejnerit: Procesi demon përgjegjës për drejtimin e kontejnerit.
  • Pritësi i kontejnerit: Kompjuteri pritës në të cilin funksionon motori i kontejnerit.
  • Regjistri i kontejnerëve: Vendndodhja e përgjithshme e përdorur për të publikuar dhe shpërndarë imazhin e kontejnerit.
  • Standardi OCIIniciativa e kontejnerëve të hapur (OCI) është një strukturë e lehtë, e hapur e qeverisjes e formuar brenda Fondacionit Linux. Specifikimi i imazhit OCI përcakton standardet e industrisë për formatet e imazhit të kontejnerit dhe kohën e funksionimit për të siguruar që të gjithë motorët e kontejnerëve të mund të ekzekutojnë imazhet e kontejnerëve të krijuar nga çdo mjet ndërtimi.

Për të kontejneruar një aplikacion, ne e mbështjellim aplikacionin tonë në një imazh kontejneri dhe e publikojmë atë imazh në një regjistër të përbashkët. Koha e ekzekutimit të kontejnerit e merr këtë imazh nga regjistri, e shpaketon atë dhe ekzekuton aplikacionin brenda tij.

Versioni 2.3 i Spring Boot ofron shtojca për krijimin e imazheve OCI.

prerës është zbatimi më i zakonshëm i kontejnerëve dhe ne përdorim Docker në shembujt tanë, kështu që të gjitha referencat pasuese të kontejnerëve në këtë artikull do t'i referohen Docker.

Ndërtimi i një imazhi të kontejnerit në mënyrën tradicionale

Krijimi i imazheve Docker për aplikacionet Spring Boot është shumë i lehtë duke shtuar disa udhëzime në skedarin Docker.

Fillimisht krijojmë një skedar JAR të ekzekutueshëm dhe, si pjesë e udhëzimeve të skedarit Docker, kopjojmë skedarin JAR të ekzekutueshëm në krye të imazhit bazë JRE pasi të kemi aplikuar cilësimet e nevojshme.

Le të krijojmë aplikacionin tonë Pranverë Pranvera Initializr me varësi weblombokи actuator. Ne po shtojmë gjithashtu një kontrollues pushimi për të siguruar një API GETmetodë.

Krijimi i një skedari Docker

Më pas e kontejnerojmë këtë aplikacion duke shtuar Dockerfile:

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

Skedari ynë Docker përmban një imazh bazë nga adoptopenjdk, në krye të së cilës kopjojmë skedarin tonë JAR dhe më pas hapim portin, 8080të cilat do të dëgjojnë për kërkesat.

Ndërtimi i aplikacionit

Së pari ju duhet të krijoni një aplikacion duke përdorur Maven ose Gradle. Këtu përdorim Maven:

mvn clean package

Kjo krijon një skedar JAR të ekzekutueshëm për aplikacionin. Ne duhet ta konvertojmë këtë JAR të ekzekutueshëm në një imazh Docker për të ekzekutuar në motorin Docker.

Krijimi i një imazhi të kontejnerit

Më pas e vendosim këtë skedar JAR të ekzekutueshëm në imazhin Docker duke ekzekutuar komandën docker buildnga direktoria rrënjësore e projektit që përmban Dockerfile të krijuar më parë:

docker build  -t usersignup:v1 .

Ne mund ta shohim imazhin tonë në listë duke përdorur komandën:

docker images 

Prodhimi i komandës së mësipërme përfshin imazhin tonë usersignupsë bashku me imazhin bazë, adoptopenjdkspecifikuar në skedarin tonë Docker.

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

Shikoni shtresat brenda një imazhi të kontejnerit

Le të shohim grupin e shtresave brenda imazhit. ne do të përdorim mjet  pikiatë për të parë këto shtresa:

dive usersignup:v1

Këtu është një pjesë e daljes nga komanda Dive: 

Krijimi i imazheve të optimizuara të Docker për një aplikacion Spring Boot

Siç mund ta shohim, shtresa e aplikacionit përbën një pjesë të konsiderueshme të madhësisë së imazhit. Ne duam të zvogëlojmë madhësinë e kësaj shtrese në seksionet e mëposhtme si pjesë e optimizimit tonë.

Krijimi i një imazhi të kontejnerit duke përdorur Buildpack

Paketat e montimit (Buildpacks) është një term i përgjithshëm i përdorur nga oferta të ndryshme të Platformës si shërbim (PAAS) për të krijuar një imazh të kontejnerit nga kodi burimor. Ai u lançua nga Heroku në 2011 dhe që atëherë është miratuar nga Cloud Foundry, Google App Engine, Gitlab, Knative dhe disa të tjerë.

Krijimi i imazheve të optimizuara të Docker për një aplikacion Spring Boot

Avantazhi i paketave të ndërtimit të cloud

Një nga përfitimet kryesore të përdorimit të Buildpack për të krijuar imazhe është se Ndryshimet e konfigurimit të imazhit mund të menaxhohen në mënyrë qendrore (ndërtues) dhe të përhapen në të gjitha aplikacionet duke përdorur ndërtuesin.

Paketat e ndërtimit ishin të lidhura fort me platformën. Cloud-Native Buildpacks mundësojnë standardizimin nëpër platforma duke mbështetur formatin e imazhit OCI, i cili siguron që imazhi të mund të ekzekutohet nga motori Docker.

Duke përdorur shtojcën Spring Boot

Shtojca Spring Boot ndërton imazhe OCI nga burimi duke përdorur Buildpack. Imazhet krijohen duke përdorur bootBuildImagedetyrat (Gradle) ose spring-boot:build-imageobjektivat (Maven) dhe instalimi lokal Docker.

Ne mund të personalizojmë emrin e imazhit të nevojshëm për të shtyrë në regjistrin Docker duke specifikuar emrin 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>

Le të përdorim Maven për ta bërë atë build-imagesynimet për krijimin e një aplikacioni dhe krijimin e një imazhi të kontejnerit. Ne nuk po përdorim asnjë Dockerfiles për momentin.

mvn spring-boot:build-image

Rezultati do të jetë diçka e tillë:

[INFO] --- spring-boot-maven-plugin:2.3.3.RELEASE:build-image (default-cli) @ usersignup ---
[INFO] Building image 'docker.io/pratikdas/usersignup:v1'
[INFO] 
[INFO]  > Pulling builder image 'gcr.io/paketo-buildpacks/builder:base-platform-api-0.3' 0%
.
.
.. [creator]     Adding label 'org.springframework.boot.version'
.. [creator]     *** Images (c311fe74ec73):
.. [creator]           docker.io/pratikdas/usersignup:v1
[INFO] 
[INFO] Successfully built image 'docker.io/pratikdas/usersignup:v1'

Nga dalja shohim se paketo Cloud-Native buildpackpërdoret për të krijuar një imazh OCI të punës. Si më parë, ne mund ta shohim imazhin e renditur si një imazh Docker duke ekzekutuar komandën:

docker images 

Përfundim:

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

Krijimi i një imazhi të kontejnerit duke përdorur Jib

Jib është një shtojcë e krijimit të imazhit nga Google që ofron një metodë alternative për krijimin e një imazhi të kontejnerit nga kodi burimor.

Po konfiguron jib-maven-pluginnë pom.xml:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.5.2</version>
      </plugin>

Më pas, ne ekzekutojmë shtojcën Jib duke përdorur komandën Maven për të ndërtuar aplikacionin dhe për të krijuar një imazh të kontejnerit. Si më parë, ne nuk po përdorim asnjë skedar Docker këtu:

mvn compile jib:build -Dimage=<docker registry name>/usersignup:v1

Pas ekzekutimit të komandës së mësipërme Maven, marrim daljen e mëposhtme:

[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

Dalja tregon se imazhi i kontejnerit është krijuar dhe vendosur në regjistër.

Motivimet dhe teknikat për krijimin e imazheve të optimizuara

Ne kemi dy arsye kryesore për optimizim:

  • prodhimtari: Në një sistem orkestrimi të kontejnerit, një imazh i kontejnerit merret nga regjistri i imazheve te hosti që drejton motorin e kontejnerit. Ky proces quhet planifikim. Tërheqja e imazheve të mëdha nga regjistri rezulton në kohë të gjata planifikimi në sistemet e orkestrimit të kontejnerëve dhe kohë të gjata ndërtimi në tubacionet CI.
  • siguri: Imazhet më të mëdha kanë gjithashtu një zonë më të madhe për dobësitë.

Një imazh Docker përbëhet nga një pirg shtresash, secila prej të cilave përfaqëson një udhëzim në Dockerfile-n tonë. Çdo shtresë përfaqëson një delta të ndryshimeve në shtresën bazë. Kur nxjerrim një imazh të Docker nga regjistri, ai tërhiqet në shtresa dhe ruhet në strehë.

Përdorimet e çizmeve të pranverës "JAR yndyrë" në si formati i paracaktuar i paketimit. Kur shikojmë JAR-in e trashë, shohim se aplikacioni përbën një pjesë shumë të vogël të të gjithë JAR-it. Kjo është pjesa që ndryshon më shpesh. Pjesa e mbetur përbëhet nga varësitë e Kornizës së Pranverës.

Formula e optimizimit përqendrohet në izolimin e aplikacionit në një nivel të veçantë nga varësitë Spring Framework.

Shtresa e varësisë, e cila formon pjesën më të madhe të skedarit të trashë JAR, shkarkohet vetëm një herë dhe ruhet në sistemin pritës.

Vetëm një shtresë e hollë e aplikacionit tërhiqet gjatë përditësimeve të aplikacionit dhe planifikimit të kontejnerit. siç tregohet në këtë diagram:

Krijimi i imazheve të optimizuara të Docker për një aplikacion Spring Boot

Në seksionet e mëposhtme, ne do të shikojmë se si të krijojmë këto imazhe të optimizuara për një aplikacion Spring Boot.

Krijimi i një imazhi të optimizuar të kontejnerit për një aplikacion Spring Boot duke përdorur Buildpack

Spring Boot 2.3 mbështet shtresimin duke nxjerrë pjesë të një skedari të trashë JAR në shtresa të veçanta. Veçoria e shtresimit është e çaktivizuar si parazgjedhje dhe duhet të aktivizohet në mënyrë eksplicite duke përdorur shtojcën Spring Boot Maven:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration> 
</plugin>

Ne do ta përdorim këtë konfigurim për të ndërtuar imazhin tonë të kontejnerit fillimisht me Buildpack dhe më pas me Docker në seksionet vijuese.

Le të nisim build-imageObjektivi i Maven për krijimin e imazhit të kontejnerit:

mvn spring-boot:build-image

Nëse ekzekutojmë Dive për të parë shtresat në imazhin që rezulton, mund të shohim se shtresa e aplikimit (e përshkruar me të kuqe) është shumë më e vogël në intervalin kilobyte krahasuar me atë që kemi marrë duke përdorur formatin e trashë JAR:

Krijimi i imazheve të optimizuara të Docker për një aplikacion Spring Boot

Krijimi i një imazhi të optimizuar të kontejnerit për një aplikacion Spring Boot duke përdorur Docker

Në vend që të përdorim një shtojcë Maven ose Gradle, ne gjithashtu mund të krijojmë një imazh Docker JAR me shtresa me një skedar Docker.

Kur përdorim Docker, duhet të kryejmë dy hapa shtesë për të nxjerrë shtresat dhe për t'i kopjuar ato në imazhin përfundimtar.

Përmbajtja e JAR-it që rezulton pas ndërtimit duke përdorur Maven me shtresa të aktivizuara do të duket si kjo:

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

Dalja tregon një JAR shtesë të quajtur spring-boot-jarmode-layertoolsи layersfle.idxdosje. Ky skedar shtesë JAR ofron aftësi të përpunimit me shtresa, siç përshkruhet në seksionin vijues.

Nxjerrja e varësive nga shtresat individuale

Për të parë dhe nxjerrë shtresa nga JAR-i ynë me shtresa, ne përdorim vetitë e sistemit -Djarmode=layertoolspër fillim spring-boot-jarmode-layertoolsJAR në vend të aplikimit:

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

Ekzekutimi i kësaj komande prodhon një dalje që përmban opsionet e disponueshme të komandës:

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

Dalja tregon komandat listextractи helpс helptë jetë e paracaktuar. Le të ekzekutojmë komandën me listopsioni:

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

Ne shohim një listë të varësive që mund të shtohen si shtresa.

Shtresat e parazgjedhura:

Emri i shtresës

Përmbajtje

dependencies

çdo varësi, versioni i së cilës nuk përmban SNAPSHOT

spring-boot-loader

Klasat e ngarkuesit JAR

snapshot-dependencies

çdo varësi, versioni i së cilës përmban SNAPSHOT

application

klasat dhe burimet e aplikimit

Shtresat përcaktohen në layers.idxskedarin sipas renditjes që duhet të shtohen në imazhin Docker. Këto shtresa ruhen në strehë pas rikthimit të parë sepse nuk ndryshojnë. Vetëm shtresa e përditësuar e aplikacionit shkarkohet në host, e cila është më e shpejtë për shkak të madhësisë së zvogëluar .

Ndërtimi i një imazhi me varësi të nxjerra në shtresa të veçanta

Ne do të ndërtojmë imazhin përfundimtar në dy faza duke përdorur një metodë të quajtur montim me shumë faza . Në hapin e parë do të nxjerrim varësitë dhe në hapin e dytë do të kopjojmë varësitë e nxjerra në imazhin përfundimtar.

Le të modifikojmë Dockerfile-n tonë për një ndërtim me shumë faza:

# 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"]

Ne e ruajmë këtë konfigurim në një skedar të veçantë - Dockerfile2.

Ne ndërtojmë imazhin Docker duke përdorur komandën:

docker build -f Dockerfile2 -t usersignup:v1 .

Pas ekzekutimit të kësaj komande marrim daljen e mëposhtme:

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

Mund të shohim që një imazh Docker është krijuar me një ID imazhi dhe më pas etiketohet.

Më në fund, ne ekzekutojmë komandën Dive si më parë për të inspektuar shtresat brenda imazhit të gjeneruar të Docker. Ne mund të ofrojmë një ID imazhi ose etiketë si hyrje në komandën Dive:

dive userssignup:v1

Siç mund ta shihni në dalje, shtresa që përmban aplikacionin tani është vetëm 11 KB, dhe varësitë ruhen në shtresa të veçanta. 

Krijimi i imazheve të optimizuara të Docker për një aplikacion Spring Boot

Nxjerrja e varësive të brendshme në shtresat individuale

Ne mund të zvogëlojmë më tej madhësinë e nivelit të aplikacionit duke nxjerrë ndonjë nga varësitë tona të personalizuara në një nivel të veçantë në vend që t'i paketojmë ato me aplikacionin duke i deklaruar në ymlskedar i ngjashëm me emrin 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ë këtë skedar layers.idxne kemi shtuar një varësi të personalizuar të quajtur, io.myorgqë përmban varësi të organizatës të marra nga një depo e përbashkët.

Prodhim

Në këtë artikull, ne shikuam përdorimin e Cloud-Native Buildpacks për të ndërtuar një imazh të kontejnerit direkt nga kodi burimor. Kjo është një alternativë ndaj përdorimit të Docker për të krijuar një imazh të kontejnerit në mënyrën e zakonshme: fillimisht krijoni një skedar të trashë të ekzekutueshëm JAR dhe më pas paketoni atë në një imazh kontejneri duke specifikuar udhëzimet në skedarin Docker.

Ne shikuam gjithashtu optimizimin e kontejnerit tonë duke mundësuar një veçori shtresimi që tërheq varësitë në shtresa të veçanta që ruhen në memorien e fshehtë në host dhe një shtresë e hollë e aplikacionit ngarkohet në kohën e planifikimit në motorët e ekzekutimit të kontejnerit.

Ju mund të gjeni të gjithë kodin burimor të përdorur në artikull në Github .

Referenca e komandës

Këtu është një përmbledhje e shpejtë e komandave që kemi përdorur në këtë artikull.

Pastrimi i kontekstit:

docker system prune -a

Krijimi i një imazhi të kontejnerit duke përdorur një skedar Docker:

docker build -f <Docker file name> -t <tag> .

Ne ndërtojmë imazhin e kontejnerit nga kodi burimor (pa Dockerfile):

mvn spring-boot:build-image

Shikoni shtresat e varësisë. Përpara se të ndërtoni skedarin e aplikacionit JAR, sigurohuni që veçoria e shtresimit të jetë e aktivizuar në shtojcën Spring-boot-maven:

java -Djarmode=layertools -jar application.jar list

Nxjerrja e shtresave të varësisë. Përpara se të ndërtoni skedarin e aplikacionit JAR, sigurohuni që veçoria e shtresimit të jetë e aktivizuar në shtojcën Spring-boot-maven:

 java -Djarmode=layertools -jar application.jar extract

Shikoni një listë të imazheve të kontejnerëve

docker images

Shikoni në të majtë brenda imazhit të kontejnerit (sigurohuni që mjeti i zhytjes të jetë i instaluar):

dive <image ID or image tag>

Burimi: www.habr.com