Stvaranje optimiziranih Docker slika za aplikaciju Spring Boot

Kontejneri su postali preferirano sredstvo za pakiranje aplikacije sa svim njezinim ovisnostima o softveru i operativnom sustavu i zatim za njihovu isporuku u različita okruženja.

Ovaj članak pokriva različite načine spremnika Spring Boot aplikacije:

  • izgradnja docker slike pomoću docker datoteke,
  • stvaranje OCI slike iz izvora koristeći Cloud-Native Buildpack,
  • i optimizacija slike tijekom izvođenja odvajanjem dijelova JAR-a u različite slojeve pomoću višeslojnih alata.

 Primjer koda

Ovaj članak je popraćen primjerom radnog koda na GitHubu .

Kontejnerska terminologija

Počet ćemo s terminologijom spremnika koja se koristi u članku:

  • Slika spremnika: datoteka određenog formata. Pretvorit ćemo našu aplikaciju u sliku spremnika pokretanjem alata za izgradnju.
  • kontejner: Izvršna instanca slike spremnika.
  • Kontejnerski motor: Daemon proces odgovoran za pokretanje spremnika.
  • Domaćin spremnika: Glavno računalo na kojem radi spremnik.
  • Registar kontejnera: Opća lokacija koja se koristi za objavljivanje i distribuciju slike spremnika.
  • OCI standardInicijativa za otvoreni kontejner (OCI) je lagana, otvorena struktura upravljanja formirana unutar Linux Foundationa. OCI specifikacija slike definira industrijske standarde za formate slike spremnika i vremena izvođenja kako bi se osiguralo da svi pogoni spremnika mogu pokretati slike spremnika stvorene bilo kojim alatom za izgradnju.

Kako bismo aplikaciju pretvorili u spremnik, omotamo našu aplikaciju u sliku spremnika i tu sliku objavimo u zajedničkom registru. Vrijeme izvođenja spremnika dohvaća ovu sliku iz registra, raspakira je i pokreće aplikaciju unutar nje.

Verzija 2.3 programa Spring Boot pruža dodatke za stvaranje OCI slika.

Lučki radnik je najčešće korištena implementacija spremnika, a mi koristimo Docker u našim primjerima, tako da će se sve daljnje reference spremnika u ovom članku odnositi na Docker.

Izrada slike spremnika na tradicionalan način

Izrada Docker slika za Spring Boot aplikacije vrlo je jednostavna dodavanjem nekoliko uputa vašoj Docker datoteci.

Prvo stvaramo izvršnu JAR datoteku i, kao dio uputa za Docker datoteku, kopiramo izvršnu JAR datoteku na osnovnu JRE sliku nakon primjene potrebnih postavki.

Kreirajmo našu Spring aplikaciju na Opruga Initializr sa ovisnostima weblombokи actuator. Također dodajemo kontroler odmora za pružanje API-ja GETmetoda.

Stvaranje Dockerfilea

Tu aplikaciju zatim stavljamo u spremnik dodavanjem Dockerfile:

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

Naša Docker datoteka sadrži osnovnu sliku iz adoptopenjdk, na koji kopiramo našu JAR datoteku i zatim otvaramo port, 8080koji će osluškivati ​​zahtjeve.

Izrada aplikacije

Najprije morate izraditi aplikaciju koristeći Maven ili Gradle. Ovdje koristimo Maven:

mvn clean package

Ovo stvara izvršnu JAR datoteku za aplikaciju. Moramo pretvoriti ovaj izvršni JAR u Docker sliku za rad na Docker motoru.

Stvaranje slike spremnika

Zatim ovu izvršnu JAR datoteku stavljamo u Docker sliku pokretanjem naredbe docker buildiz korijenskog direktorija projekta koji sadrži ranije stvorenu Docker datoteku:

docker build  -t usersignup:v1 .

Svoju sliku možemo vidjeti na popisu pomoću naredbe:

docker images 

Izlaz gornje naredbe uključuje našu sliku usersignupzajedno s osnovnom slikom, adoptopenjdknavedeno u našoj Docker datoteci.

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

Pogledajte slojeve unutar slike spremnika

Pogledajmo hrpu slojeva unutar slike. Koristit ćemo se alat  ronjenje za pregled ovih slojeva:

dive usersignup:v1

Ovdje je dio izlaza iz naredbe Dive: 

Stvaranje optimiziranih Docker slika za aplikaciju Spring Boot

Kao što vidimo, aplikacijski sloj čini značajan dio veličine slike. Želimo smanjiti veličinu ovog sloja u sljedećim odjeljcima kao dio naše optimizacije.

Stvaranje slike spremnika pomoću Buildpacka

Montažni paketi (Buildpacks) opći je izraz koji koriste razne ponude platforme kao usluge (PAAS) za stvaranje slike spremnika iz izvornog koda. Pokrenuo ga je Heroku 2011., a od tada su ga prihvatili Cloud Foundry, Google App Engine, Gitlab, Knative i nekoliko drugih.

Stvaranje optimiziranih Docker slika za aplikaciju Spring Boot

Prednost paketa za izgradnju oblaka

Jedna od glavnih prednosti korištenja Buildpacka za izradu slika je ta Promjenama konfiguracije slike može se upravljati centralno (builder) i proširiti na sve aplikacije pomoću buildera.

Paketi za izradu bili su čvrsto povezani s platformom. Cloud-Native Buildpacks pružaju standardizaciju na svim platformama podržavajući OCI format slike, koji osigurava da sliku može pokrenuti Docker mehanizam.

Korištenje dodatka Spring Boot

Dodatak Spring Boot gradi OCI slike iz izvora koristeći Buildpack. Slike se stvaraju pomoću bootBuildImagezadataka (Gradle) ili spring-boot:build-imagetarget (Maven) i lokalna Docker instalacija.

Možemo prilagoditi naziv slike koju trebamo gurnuti u Docker registar navođenjem imena 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>

Upotrijebimo Maven za to build-imageciljevi za izradu aplikacije i stvaranje slike spremnika. Trenutačno ne koristimo nijedan Dockerfiles.

mvn spring-boot:build-image

Rezultat će biti nešto poput ovoga:

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

Iz izlaza vidimo da paketo Cloud-Native buildpackkoristi se za stvaranje radne OCI slike. Kao i prije, možemo vidjeti sliku navedenu kao Docker slika pokretanjem naredbe:

docker images 

Zaključak:

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

Stvaranje slike spremnika pomoću Jiba

Jib je Googleov dodatak za stvaranje slika koji pruža alternativnu metodu za stvaranje slike spremnika iz izvornog koda.

Konfiguriranje jib-maven-pluginu pom.xml:

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

Zatim pokrećemo dodatak Jib pomoću naredbe Maven za izradu aplikacije i stvaranje slike spremnika. Kao i prije, ovdje ne koristimo nikakve Docker datoteke:

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

Nakon izvršenja gornje Maven naredbe, dobivamo sljedeći izlaz:

[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

Izlaz pokazuje da je slika spremnika stvorena i postavljena u registar.

Motivacije i metode za stvaranje optimiziranih slika

Imamo dva glavna razloga za optimizaciju:

  • Performanse: U sustavu orkestracije spremnika, slika spremnika se povlači iz registra slika na glavno računalo koje pokreće pogon spremnika. Taj se proces naziva planiranje. Povlačenje velikih slika iz registra rezultira dugim vremenima raspoređivanja u sustavima orkestracije spremnika i dugim vremenima izgradnje u CI cjevovodima.
  • sigurnosti: velike slike također imaju veliko područje za ranjivosti.

Docker slika sastoji se od niza slojeva, od kojih svaki predstavlja instrukciju u našoj Docker datoteci. Svaki sloj predstavlja deltu promjena u donjem sloju. Kada povučemo Docker sliku iz registra, ona se povlači u slojevima i predmemorira na hostu.

Spring Boot koristi "debela JAR" u kao zadani format pakiranja. Kada pogledamo debeli JAR, vidimo da aplikacija čini vrlo mali dio cijelog JAR-a. Ovo je dio koji se najčešće mijenja. Ostatak se sastoji od zavisnosti Spring Frameworka.

Formula optimizacije usredotočena je na izoliranje aplikacije na zasebnoj razini od ovisnosti Spring Frameworka.

Sloj ovisnosti, koji čini najveći dio debele JAR datoteke, preuzima se samo jednom i sprema u predmemoriju na glavnom sustavu.

Samo se tanki sloj aplikacije povlači tijekom ažuriranja aplikacije i raspoređivanja spremnika. kao što je prikazano na ovom dijagramu:

Stvaranje optimiziranih Docker slika za aplikaciju Spring Boot

U sljedećim odjeljcima pogledat ćemo kako stvoriti ove optimizirane slike za Spring Boot aplikaciju.

Stvaranje optimizirane slike spremnika za proljetnu aplikaciju za pokretanje pomoću Buildpacka

Spring Boot 2.3 podržava slojevito izdvajanje dijelova debele JAR datoteke u zasebne slojeve. Značajka slojevitosti onemogućena je prema zadanim postavkama i mora se izričito omogućiti pomoću dodatka Spring Boot Maven:

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

Koristit ćemo ovu konfiguraciju za izgradnju naše slike spremnika prvo s Buildpackom, a zatim s Dockerom u sljedećim odjeljcima.

Idemo lansirati build-imageMaven cilj za stvaranje slike spremnika:

mvn spring-boot:build-image

Ako pokrenemo Dive da vidimo slojeve na rezultirajućoj slici, možemo vidjeti da je aplikacijski sloj (označen crvenom bojom) mnogo manji u rasponu kilobajta u usporedbi s onim što smo dobili korištenjem debelog JAR formata:

Stvaranje optimiziranih Docker slika za aplikaciju Spring Boot

Stvaranje optimizirane slike spremnika za proljetnu aplikaciju za pokretanje pomoću Dockera

Umjesto korištenja Maven ili Gradle dodatka, također možemo stvoriti slojevitu Docker JAR sliku s Docker datotekom.

Kada koristimo Docker, moramo izvršiti dva dodatna koraka kako bismo izdvojili slojeve i kopirali ih u konačnu sliku.

Sadržaj rezultirajućeg JAR-a nakon izgradnje pomoću Mavena s omogućenim slojevitim izgledom izgledat će ovako:

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

Izlaz prikazuje dodatni JAR pod nazivom spring-boot-jarmode-layertoolsи layersfle.idxdatoteka. Ova dodatna JAR datoteka pruža mogućnosti slojevite obrade, kao što je opisano u sljedećem odjeljku.

Izdvajanje ovisnosti o pojedinim slojevima

Za pregled i izdvajanje slojeva iz našeg slojevitog JAR-a koristimo svojstvo sustava -Djarmode=layertoolsza početak spring-boot-jarmode-layertoolsJAR umjesto aplikacije:

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

Pokretanje ove naredbe proizvodi izlaz koji sadrži dostupne opcije naredbe:

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

Izlaz prikazuje naredbe listextractи helpс helpbiti zadana. Pokrenimo naredbu s listopcija:

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

Vidimo popis ovisnosti koje se mogu dodati kao slojevi.

Zadani slojevi:

Naziv sloja

sadržaj

dependencies

bilo koja ovisnost čija verzija ne sadrži SNAPSHOT

spring-boot-loader

Klase učitavača JAR-a

snapshot-dependencies

bilo koja ovisnost čija verzija sadrži SNAPSHOT

application

aplikacijske klase i resurse

Slojevi su definirani u layers.idxdatoteku redoslijedom kojim bi trebali biti dodani Docker slici. Ti se slojevi pohranjuju u predmemoriju na hostu nakon prvog dohvaćanja jer se ne mijenjaju. Samo se ažurirani aplikacijski sloj preuzima na glavno računalo, što je brže zbog smanjene veličine .

Izrada slike s ovisnostima ekstrahiranim u zasebne slojeve

Konačnu sliku izgradit ćemo u dvije faze koristeći metodu tzv višestupanjska montaža . U prvom koraku ćemo ekstrahirati ovisnosti, au drugom koraku ćemo kopirati ekstrahirane ovisnosti u konačnu sliku.

Modificirajmo našu Dockerfile za izgradnju u više 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"]

Ovu konfiguraciju spremamo u zasebnu datoteku - Dockerfile2.

Docker sliku gradimo pomoću naredbe:

docker build -f Dockerfile2 -t usersignup:v1 .

Nakon pokretanja ove naredbe dobivamo sljedeći izlaz:

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

Vidimo da je Docker slika stvorena s ID-om slike i zatim označena.

Na kraju, pokrećemo naredbu Dive kao i prije kako bismo pregledali slojeve unutar generirane Docker slike. Možemo dati ID slike ili oznaku kao ulaz u naredbu Dive:

dive userssignup:v1

Kao što možete vidjeti iz rezultata, sloj koji sadrži aplikaciju sada ima samo 11 KB, a ovisnosti su predmemorirane u zasebnim slojevima. 

Stvaranje optimiziranih Docker slika za aplikaciju Spring Boot

Izdvajanje internih ovisnosti o pojedinačnim slojevima

Možemo dodatno smanjiti veličinu sloja aplikacije izdvajanjem bilo koje od naših prilagođenih ovisnosti u zasebnu razinu umjesto da ih pakiramo s aplikacijom tako što ćemo ih deklarirati u ymlsličan naziv datoteke 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/"

U ovoj datoteci layers.idxdodali smo prilagođenu ovisnost pod nazivom, io.myorgkoji sadrži organizacijske ovisnosti dohvaćene iz zajedničkog repozitorija.

Izlaz

U ovom smo članku pogledali korištenje Cloud-Native Buildpacks-a za izradu slike spremnika izravno iz izvornog koda. Ovo je alternativa korištenju Dockera za stvaranje slike spremnika na uobičajeni način: prvo se stvara debela izvršna JAR datoteka, a zatim se pakira u sliku spremnika navođenjem uputa u Docker datoteci.

Također smo razmotrili optimizaciju našeg spremnika omogućavanjem značajke slojevitosti koja povlači ovisnosti u zasebne slojeve koji se pohranjuju u predmemoriju na glavnom računalu, a tanki sloj aplikacije učitava se u vrijeme zakazivanja u izvršnim strojevima spremnika.

Sav izvorni kod korišten u članku možete pronaći na Github .

Referenca naredbe

Evo kratkog pregleda naredbi koje smo koristili u ovom članku.

Čišćenje konteksta:

docker system prune -a

Stvaranje slike spremnika pomoću Docker datoteke:

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

Izgradite sliku spremnika iz izvora (bez Dockerfilea):

mvn spring-boot:build-image

Pregledajte slojeve ovisnosti. Prije izgradnje JAR datoteke aplikacije, provjerite je li značajka slojevitosti omogućena u spring-boot-maven-pluginu:

java -Djarmode=layertools -jar application.jar list

Izdvajanje slojeva zavisnosti. Prije izrade jar datoteke aplikacije, provjerite je li značajka slojevitosti omogućena u spring-boot-maven-pluginu:

 java -Djarmode=layertools -jar application.jar extract

Pogledajte popis slika spremnika

docker images

Pogled s lijeve strane unutar slike spremnika (provjerite je li alat za ronjenje instaliran):

dive <image ID or image tag>

Izvor: www.habr.com