Paglikha ng Na-optimize na Docker Images para sa Spring Boot Application

Ang mga lalagyan ay naging mas gustong paraan ng pag-iimpake ng isang application kasama ang lahat ng mga dependency ng software at operating system nito at pagkatapos ay ihahatid ang mga ito sa iba't ibang kapaligiran.

Sinasaklaw ng artikulong ito ang iba't ibang paraan upang i-containize ang isang Spring Boot application:

  • paglikha ng isang Docker na imahe gamit ang isang Docker file,
  • paglikha ng isang OCI na imahe mula sa pinagmulan gamit ang Cloud-Native Buildpack,
  • at run-time na pag-optimize ng imahe sa pamamagitan ng paghihiwalay ng mga bahagi ng JAR sa iba't ibang mga layer gamit ang mga multi-tier na tool.

 Halimbawa ng code

Ang artikulong ito ay sinamahan ng working code na halimbawa sa GitHub .

Terminolohiya ng lalagyan

Magsisimula tayo sa terminolohiya ng lalagyan na ginamit sa artikulo:

  • Larawan ng lalagyan: file ng isang tiyak na format. Iko-convert namin ang aming application sa isang imahe ng lalagyan sa pamamagitan ng pagpapatakbo ng build tool.
  • Lalagyan: Isang executable na instance ng imahe ng container.
  • Container engine: Ang proseso ng daemon na responsable sa pagpapatakbo ng lalagyan.
  • Host ng container: Ang host computer kung saan tumatakbo ang container engine.
  • Rehistro ng lalagyan: Ang pangkalahatang lokasyong ginamit upang i-publish at ipamahagi ang larawan ng container.
  • OCI standardOpen Container Initiative (OCI) ay isang magaan, bukas na istraktura ng pamamahala na nabuo sa loob ng Linux Foundation. Tinutukoy ng OCI Image Specification ang mga pamantayan ng industriya para sa container image at mga format ng runtime upang matiyak na ang lahat ng container engine ay maaaring magpatakbo ng mga container na imahe na nilikha ng anumang build tool.

Upang i-container ang isang application, binabalot namin ang aming application sa isang container na larawan at ini-publish ang larawang iyon sa isang shared registry. Kinukuha ng container runtime ang larawang ito mula sa registry, i-unpack ito, at pinapatakbo ang application sa loob nito.

Ang Bersyon 2.3 ng Spring Boot ay nagbibigay ng mga plugin para sa paglikha ng mga OCI na imahe.

Manggagawa sa pantalan ay ang pinakakaraniwang ginagamit na pagpapatupad ng container, at ginagamit namin ang Docker sa aming mga halimbawa, kaya lahat ng kasunod na reference ng container sa artikulong ito ay tumutukoy sa Docker.

Pagbuo ng imahe ng lalagyan sa tradisyonal na paraan

Ang paggawa ng mga imahe ng Docker para sa mga application ng Spring Boot ay napakadali sa pamamagitan ng pagdaragdag ng ilang mga tagubilin sa Docker file.

Gumawa muna kami ng executable JAR file at, bilang bahagi ng Docker file instructions, kopyahin ang executable JAR file sa ibabaw ng base JRE image pagkatapos ilapat ang mga kinakailangang setting.

Gawin natin ang aming Spring application sa Spring Initializr may mga dependencies weblombokΠΈ actuator. Nagdaragdag din kami ng rest controller para magbigay ng API GETparaan.

Paglikha ng Dockerfile

Pagkatapos ay lalagyan namin ang application na ito sa pamamagitan ng pagdaragdag Dockerfile:

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

Ang aming Docker file ay naglalaman ng isang batayang larawan mula sa adoptopenjdk, sa ibabaw nito ay kinopya namin ang aming JAR file at pagkatapos ay buksan ang port, 8080na makikinig sa mga kahilingan.

Pagbuo ng application

Una kailangan mong lumikha ng isang application gamit ang Maven o Gradle. Dito ginagamit namin ang Maven:

mvn clean package

Lumilikha ito ng executable JAR file para sa application. Kailangan nating i-convert itong executable JAR sa isang Docker image para tumakbo sa Docker engine.

Paglikha ng imahe ng lalagyan

Pagkatapos ay inilagay namin ang executable na JAR file na ito sa imahe ng Docker sa pamamagitan ng pagpapatakbo ng command docker buildmula sa direktoryo ng ugat ng proyekto na naglalaman ng Dockerfile na nilikha nang mas maaga:

docker build  -t usersignup:v1 .

Makikita natin ang ating imahe sa listahan gamit ang command:

docker images 

Kasama sa output ng command sa itaas ang aming imahe usersignupkasama ang batayang imahe, adoptopenjdktinukoy sa aming Docker file.

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

Tingnan ang mga layer sa loob ng larawan ng lalagyan

Tingnan natin ang stack ng mga layer sa loob ng imahe. Gagamitin natin kasangkapan  sumisid upang tingnan ang mga layer na ito:

dive usersignup:v1

Narito ang bahagi ng output mula sa Dive command: 

Paglikha ng Na-optimize na Docker Images para sa Spring Boot Application

Tulad ng nakikita natin, ang layer ng application ay bumubuo ng isang makabuluhang bahagi ng laki ng imahe. Gusto naming bawasan ang laki ng layer na ito sa mga sumusunod na seksyon bilang bahagi ng aming pag-optimize.

Paglikha ng imahe ng lalagyan gamit ang Buildpack

Mga pakete ng pagpupulong (Mga Buildpack) ay isang pangkalahatang termino na ginagamit ng iba't ibang Platform bilang Serbisyo (PAAS) na mga alok upang lumikha ng isang lalagyan na imahe mula sa source code. Inilunsad ito ni Heroku noong 2011 at mula noon ay pinagtibay ng Cloud Foundry, Google App Engine, Gitlab, Knative at marami pang iba.

Paglikha ng Na-optimize na Docker Images para sa Spring Boot Application

Ang bentahe ng cloud build packages

Isa sa mga pangunahing benepisyo ng paggamit ng Buildpack upang lumikha ng mga imahe ay iyon Ang mga pagbabago sa configuration ng imahe ay maaaring pamahalaan sa gitna (tagabuo) at ipalaganap sa lahat ng mga application gamit ang tagabuo.

Ang mga build package ay mahigpit na pinagsama sa platform. Ang Cloud-Native Buildpacks ay nagbibigay ng standardisasyon sa mga platform sa pamamagitan ng pagsuporta sa OCI image format, na nagsisiguro na ang imahe ay maaaring patakbuhin ng Docker engine.

Gamit ang plugin ng Spring Boot

Ang Spring Boot plugin ay bumubuo ng mga OCI na imahe mula sa pinagmulan gamit ang Buildpack. Ang mga imahe ay nilikha gamit ang bootBuildImagemga gawain (Gradle) o spring-boot:build-imagemga target (Maven) at lokal na pag-install ng Docker.

Maaari naming i-customize ang pangalan ng imahe na kailangan upang itulak sa registry ng Docker sa pamamagitan ng pagtukoy sa pangalan sa 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>

Gamitin natin si Maven para gawin ito build-imagemga layunin para sa paglikha ng isang application at paglikha ng isang imahe ng lalagyan. Hindi kami gumagamit ng anumang Dockerfiles sa ngayon.

mvn spring-boot:build-image

Ang resulta ay magiging ganito:

[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'

Mula sa output ay nakikita natin iyon paketo Cloud-Native buildpackginamit upang lumikha ng gumaganang OCI na imahe. Tulad ng dati, makikita natin ang imahe na nakalista bilang isang imahe ng Docker sa pamamagitan ng pagpapatakbo ng command:

docker images 

Konklusyon:

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

Paglikha ng imahe ng lalagyan gamit ang Jib

Ang Jib ay isang plugin ng paggawa ng larawan mula sa Google na nagbibigay ng alternatibong paraan para sa paggawa ng lalagyang larawan mula sa source code.

Kino-configure jib-maven-pluginsa pom.xml:

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

Susunod, pinapatakbo namin ang Jib plugin gamit ang Maven command upang buuin ang application at lumikha ng isang imahe ng lalagyan. Tulad ng dati, hindi kami gumagamit ng anumang mga file ng Docker dito:

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

Matapos isagawa ang utos sa itaas ng Maven, nakukuha namin ang sumusunod na output:

[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

Ipinapakita ng output na ang imahe ng lalagyan ay nilikha at inilagay sa pagpapatala.

Mga motibasyon at pamamaraan para sa paglikha ng mga na-optimize na larawan

Mayroon kaming dalawang pangunahing dahilan para sa pag-optimize:

  • Pagiging Produktibo: Sa isang container orchestration system, ang isang container na imahe ay kinukuha mula sa image registry patungo sa host na nagpapatakbo ng container engine. Ang prosesong ito ay tinatawag na pagpaplano. Ang paghila ng malalaking larawan mula sa registry ay nagreresulta sa mahabang oras ng pag-iiskedyul sa mga container orchestration system at mahabang oras ng pagbuo sa mga pipeline ng CI.
  • katiwasayan: Ang mas malalaking larawan ay mayroon ding mas malaking lugar para sa mga kahinaan.

Ang isang imahe ng Docker ay binubuo ng isang stack ng mga layer, ang bawat isa ay kumakatawan sa isang pagtuturo sa aming Dockerfile. Ang bawat layer ay kumakatawan sa isang delta ng mga pagbabago sa pinagbabatayan na layer. Kapag kinuha namin ang isang Docker na imahe mula sa registry, ito ay nakuha sa mga layer at naka-cache sa host.

Gumagamit ang Spring Boot "fat JAR" sa bilang default na format ng packaging. Kung titingnan natin ang makapal na JAR, makikita natin na ang application ay bumubuo ng napakaliit na bahagi ng buong JAR. Ito ang bahagi na madalas na nagbabago. Ang natitira ay binubuo ng mga dependency ng Spring Framework.

Ang formula sa pag-optimize ay nakasentro sa paghihiwalay ng application sa isang hiwalay na antas mula sa mga dependency ng Spring Framework.

Ang dependency layer, na bumubuo sa karamihan ng makapal na JAR file, ay nai-download nang isang beses lamang at naka-cache sa host system.

Isang manipis na layer lang ng application ang kinukuha sa panahon ng pag-update ng application at pag-iiskedyul ng container. tulad ng ipinapakita sa diagram na ito:

Paglikha ng Na-optimize na Docker Images para sa Spring Boot Application

Sa mga sumusunod na seksyon, titingnan natin kung paano gawin ang mga na-optimize na larawang ito para sa isang Spring Boot na application.

Paggawa ng Optimized na Container Image para sa Spring Boot Application Gamit ang Buildpack

Sinusuportahan ng Spring Boot 2.3 ang layering sa pamamagitan ng pagkuha ng mga bahagi ng isang makapal na JAR file sa magkahiwalay na mga layer. Ang tampok na layering ay hindi pinagana bilang default at dapat na tahasang pinagana gamit ang Spring Boot Maven plugin:

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

Gagamitin namin ang configuration na ito para buuin muna ang aming container image gamit ang Buildpack at pagkatapos ay sa Docker sa mga sumusunod na seksyon.

Ilunsad natin build-imageMaven target para sa paglikha ng imahe ng lalagyan:

mvn spring-boot:build-image

Kung patakbuhin namin ang Dive upang makita ang mga layer sa nagresultang imahe, makikita namin na ang layer ng application (nakabalangkas sa pula) ay mas maliit sa hanay ng kilobyte kumpara sa nakuha namin gamit ang fat JAR na format:

Paglikha ng Na-optimize na Docker Images para sa Spring Boot Application

Paggawa ng Optimized na Container Image para sa Spring Boot Application Gamit ang Docker

Sa halip na gumamit ng Maven o Gradle na plugin, maaari din kaming gumawa ng layered na Docker JAR na imahe na may Docker file.

Kapag ginamit namin ang Docker, kailangan naming magsagawa ng dalawang karagdagang hakbang upang kunin ang mga layer at kopyahin ang mga ito sa panghuling larawan.

Ang mga nilalaman ng nagresultang JAR pagkatapos ng pagbuo gamit ang Maven na pinagana ang layering ay magiging ganito:

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

Ang output ay nagpapakita ng karagdagang JAR na pinangalanan spring-boot-jarmode-layertoolsΠΈ layersfle.idxfile. Ang karagdagang JAR file na ito ay nagbibigay ng mga layered na kakayahan sa pagproseso, tulad ng inilarawan sa susunod na seksyon.

Pagkuha ng mga dependency sa mga indibidwal na layer

Upang tingnan at i-extract ang mga layer mula sa aming naka-layer na JAR, ginagamit namin ang system property -Djarmode=layertoolspara sa simula spring-boot-jarmode-layertoolsJAR sa halip na application:

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

Ang pagpapatakbo ng utos na ito ay gumagawa ng output na naglalaman ng magagamit na mga opsyon sa command:

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

Ang output ay nagpapakita ng mga utos listextractΠΈ helpс helpmaging default. Patakbuhin natin ang utos gamit ang listopsyon:

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

Nakikita namin ang isang listahan ng mga dependency na maaaring idagdag bilang mga layer.

Default na mga layer:

Pangalan ng layer

nilalaman

dependencies

anumang dependency na ang bersyon ay hindi naglalaman ng SNAPSHOT

spring-boot-loader

Mga Klase ng JAR Loader

snapshot-dependencies

anumang dependency na ang bersyon ay naglalaman ng SNAPSHOT

application

mga klase at mapagkukunan ng aplikasyon

Ang mga layer ay tinukoy sa layers.idxfile sa pagkakasunud-sunod na dapat nilang idagdag sa imahe ng Docker. Ang mga layer na ito ay naka-cache sa host pagkatapos ng unang pagkuha dahil hindi sila nagbabago. Tanging ang na-update na layer ng application ang na-download sa host, na mas mabilis dahil sa pinaliit na laki .

Pagbuo ng isang imahe na may mga dependency na nakuha sa magkahiwalay na mga layer

Bubuo kami ng huling imahe sa dalawang yugto gamit ang isang pamamaraan na tinatawag multi-stage na pagpupulong . Sa unang hakbang ay kukunin natin ang mga dependency at sa pangalawang hakbang ay kokopyahin natin ang mga na-extract na dependencies sa huling imahe.

Baguhin natin ang ating Dockerfile para sa isang multi-stage na build:

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

Ise-save namin ang configuration na ito sa isang hiwalay na file - Dockerfile2.

Binubuo namin ang imahe ng Docker gamit ang command:

docker build -f Dockerfile2 -t usersignup:v1 .

Matapos patakbuhin ang utos na ito makuha namin ang sumusunod na output:

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

Makikita natin na ang isang imahe ng Docker ay nilikha gamit ang isang ID ng imahe at pagkatapos ay na-tag.

Sa wakas, pinapatakbo namin ang Dive command tulad ng dati upang siyasatin ang mga layer sa loob ng nabuong imahe ng Docker. Maaari kaming magbigay ng image ID o tag bilang input sa Dive command:

dive userssignup:v1

Tulad ng makikita mo sa output, ang layer na naglalaman ng application ay 11 KB na lang, at ang mga dependency ay naka-cache sa magkahiwalay na mga layer. 

Paglikha ng Na-optimize na Docker Images para sa Spring Boot Application

Pagkuha ng mga panloob na dependency sa mga indibidwal na layer

Maaari pa naming bawasan ang laki ng tier ng application sa pamamagitan ng pag-extract ng alinman sa aming mga custom na dependency sa isang hiwalay na tier sa halip na i-package ang mga ito ng application sa pamamagitan ng pagdedeklara sa mga ito sa ymlkatulad na file na pinangalanan 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/"

Sa file na ito layers.idxnagdagdag kami ng custom na dependency na pinangalanan, io.myorgnaglalaman ng mga dependency ng organisasyon na nakuha mula sa isang shared repository.

Pagbubuhos

Sa artikulong ito, tiningnan namin ang paggamit ng Cloud-Native Buildpacks upang bumuo ng imahe ng lalagyan nang direkta mula sa source code. Ito ay isang alternatibo sa paggamit ng Docker upang lumikha ng isang imahe ng lalagyan sa karaniwang paraan: gumawa muna ng isang makapal na executable na JAR file at pagkatapos ay i-package ito sa isang imahe ng lalagyan sa pamamagitan ng pagtukoy ng mga tagubilin sa Docker file.

Tiningnan din namin ang pag-optimize sa aming container sa pamamagitan ng pagpapagana ng feature na layering na kumukuha ng mga dependency sa magkakahiwalay na layer na naka-cache sa host at nilo-load ang manipis na layer ng application sa oras ng pag-iiskedyul sa mga execution engine ng container.

Mahahanap mo ang lahat ng source code na ginamit sa artikulo sa Github .

Sanggunian ng command

Narito ang isang mabilis na rundown ng mga utos na ginamit namin sa artikulong ito.

Pag-clear ng konteksto:

docker system prune -a

Paglikha ng isang imahe ng lalagyan gamit ang isang Docker file:

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

Binubuo namin ang imahe ng lalagyan mula sa source code (nang walang Dockerfile):

mvn spring-boot:build-image

Tingnan ang mga layer ng dependency. Bago buuin ang application na JAR file, siguraduhin na ang layering feature ay pinagana sa spring-boot-maven-plugin:

java -Djarmode=layertools -jar application.jar list

Pag-extract ng mga layer ng dependency. Bago buuin ang application na JAR file, siguraduhin na ang layering feature ay pinagana sa spring-boot-maven-plugin:

 java -Djarmode=layertools -jar application.jar extract

Tingnan ang isang listahan ng mga larawan ng lalagyan

docker images

Tingnan sa kaliwa sa loob ng larawan ng lalagyan (tiyaking naka-install ang dive tool):

dive <image ID or image tag>

Pinagmulan: www.habr.com