Izrada optimiziranih Docker slika za Spring Boot aplikaciju

Kontejneri su postali preferirano sredstvo za pakovanje aplikacije sa svim zavisnostima od softvera i operativnog sistema, a zatim njihovo isporuku u različita okruženja.

Ovaj članak pokriva različite načine skladištenja Spring Boot aplikacije:

  • kreiranje Docker slike koristeći Docker fajl,
  • kreiranje OCI slike iz izvora koristeći Cloud-Native Buildpack,
  • i optimizacija slike u toku rada odvajanjem delova JAR-a u različite slojeve pomoću višeslojnih alata.

 Primjer koda

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

Terminologija kontejnera

Počet ćemo s terminologijom kontejnera korištenom u članku:

  • Slika kontejnera: datoteka određenog formata. Konvertovaćemo našu aplikaciju u sliku kontejnera pokretanjem alata za izgradnju.
  • Kontejner: Izvršna instanca slike kontejnera.
  • Kontejnerski motor: Daemon proces odgovoran za pokretanje kontejnera.
  • Container host: Glavni računar na kojem radi motor kontejnera.
  • Registar kontejnera: Opća lokacija koja se koristi za objavljivanje i distribuciju slike spremnika.
  • OCI standardInicijativa za otvorene kontejnere (OCI) je lagana, otvorena struktura upravljanja formirana u okviru Linux fondacije. OCI specifikacija slike definira industrijske standarde za formate slike kontejnera i runtime kako bi se osiguralo da svi kontejnerski strojevi mogu pokrenuti slike kontejnera kreirane bilo kojim alatom za pravljenje.

Da bismo kontejnerizirali aplikaciju, umotavamo našu aplikaciju u sliku kontejnera i objavljujemo tu sliku u zajedničkom registru. Vrijeme izvođenja kontejnera preuzima ovu sliku iz registra, raspakuje je i pokreće aplikaciju unutar nje.

Verzija 2.3 Spring Boot-a pruža dodatke za kreiranje OCI slika.

doker je najčešće korištena implementacija kontejnera, a mi koristimo Docker u našim primjerima, tako da će se sve naknadne reference kontejnera u ovom članku odnositi na Docker.

Izgradnja imidža kontejnera na tradicionalan način

Kreiranje Docker slika za Spring Boot aplikacije je vrlo jednostavno dodavanjem nekoliko uputstava u Docker datoteku.

Prvo kreiramo izvršnu JAR datoteku i, kao dio uputstava za Docker datoteku, kopiramo izvršnu JAR datoteku na vrh osnovne JRE slike nakon primjene potrebnih postavki.

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

Kreiranje Dockerfile-a

Zatim kontejneriziramo ovu aplikaciju 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š Docker fajl sadrži osnovnu sliku iz adoptopenjdk, na koji kopiramo našu JAR datoteku i zatim otvaramo port, 8080koji će saslušati zahtjeve.

Izgradnja aplikacije

Prvo morate kreirati aplikaciju koristeći Maven ili Gradle. Ovdje koristimo Maven:

mvn clean package

Ovo kreira izvršnu JAR datoteku za aplikaciju. Moramo da konvertujemo ovaj izvršni JAR u Docker sliku za pokretanje na Docker engine-u.

Kreiranje slike kontejnera

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

docker build  -t usersignup:v1 .

Našu sliku možemo vidjeti na listi pomoću naredbe:

docker images 

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

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

Pregledajte slojeve unutar slike kontejnera

Pogledajmo hrpu slojeva unutar slike. Koristićemo alatka  roniti da vidite ove slojeve:

dive usersignup:v1

Evo dijela izlaza iz komande Dive: 

Izrada optimiziranih Docker slika za Spring Boot aplikaciju

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

Kreiranje slike kontejnera pomoću Buildpack-a

Montažni paketi (Buildpacks) je opšti termin koji koriste različite ponude Platforme kao usluge (PAAS) za kreiranje slike kontejnera iz izvornog koda. Pokrenuo ga je Heroku 2011. godine i od tada su ga usvojili Cloud Foundry, Google App Engine, Gitlab, Knative i nekoliko drugih.

Izrada optimiziranih Docker slika za Spring Boot aplikaciju

Prednost paketa za izgradnju oblaka

Jedna od glavnih prednosti korištenja Buildpack-a za kreiranje slika je to Promjene konfiguracije slike mogu se upravljati centralno (builder) i prenositi na sve aplikacije koristeći builder.

Paketi izrade bili su čvrsto povezani sa platformom. Cloud-Native Buildpacks obezbjeđuju standardizaciju među platformama podržavajući OCI format slike, koji osigurava da sliku može pokrenuti Docker engine.

Korištenje Spring Boot dodatka

Spring Boot dodatak gradi OCI slike iz izvora koristeći Buildpack. Slike se kreiraju pomoću bootBuildImagezadaci (Gradle) ili spring-boot:build-imagetargets (Maven) i lokalna Docker instalacija.

Možemo prilagoditi ime slike koja je potrebna za guranje u Docker registar tako što ćemo navesti ime u 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>

Koristimo Maven da to uradimo build-imageciljevi za kreiranje aplikacije i kreiranje slike kontejnera. Trenutno ne koristimo nijedan Dockerfiles.

mvn spring-boot:build-image

Rezultat će biti otprilike ovako:

[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 kreiranje radne OCI slike. Kao i ranije, 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

Kreiranje slike kontejnera pomoću Jib

Jib je Google dodatak za kreiranje slike koji pruža alternativni metod za kreiranje slike kontejnera iz izvornog koda.

Konfigurisanje 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 koristeći naredbu Maven da napravimo aplikaciju i kreiramo sliku kontejnera. Kao i prije, ovdje ne koristimo nikakve Docker fajlove:

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

Nakon izvršenja gornje naredbe Maven, dobijamo 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 kontejnera kreirana i stavljena u registar.

Motivacije i tehnike za kreiranje optimiziranih slika

Imamo dva glavna razloga za optimizaciju:

  • Produktivnost: U sistemu orkestracije kontejnera, slika kontejnera se preuzima iz registra slika do hosta koji pokreće kontejnerski mehanizam. Ovaj proces se zove planiranje. Povlačenje velikih slika iz registra rezultira dugim vremenom planiranja u sistemima orkestracije kontejnera i dugim vremenom izgradnje u CI cjevovodima.
  • Sigurnost: Veće slike takođe imaju veće područje za ranjivosti.

Docker slika se sastoji od hrpe slojeva, od kojih svaki predstavlja instrukciju u našem Dockerfileu. Svaki sloj predstavlja deltu promjena u donjem sloju. Kada izvučemo Docker sliku iz registra, ona se povlači u slojevima i kešira na hostu.

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

Formula optimizacije se fokusira na izolaciju aplikacije na zasebnom nivou od zavisnosti od Spring Framework-a.

Sloj zavisnosti, koji čini najveći deo debele JAR datoteke, preuzima se samo jednom i kešira na host sistemu.

Samo tanak sloj aplikacije se povlači tokom ažuriranja aplikacije i planiranja kontejnera. kao što je prikazano na ovom dijagramu:

Izrada optimiziranih Docker slika za Spring Boot aplikaciju

U sljedećim odjeljcima ćemo pogledati kako kreirati ove optimizirane slike za Spring Boot aplikaciju.

Kreiranje optimizirane slike kontejnera za Spring Boot aplikaciju pomoću Buildpack-a

Spring Boot 2.3 podržava slojeve izdvajanjem delova debele JAR datoteke u zasebne slojeve. Funkcija slojevitosti je onemogućena prema zadanim postavkama i mora biti eksplicitno omogućena pomoću Spring Boot Maven dodatka:

<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 kontejnera prvo sa Buildpackom, a zatim s Dockerom u sljedećim odjeljcima.

Pokrenimo build-imageMaven cilj za kreiranje slike kontejnera:

mvn spring-boot:build-image

Ako pokrenemo Dive da vidimo slojeve na rezultujućoj slici, možemo vidjeti da je sloj aplikacije (ocrtan crvenom bojom) mnogo manji u rasponu kilobajta u poređenju sa onim što smo dobili koristeći debeli JAR format:

Izrada optimiziranih Docker slika za Spring Boot aplikaciju

Kreiranje optimizirane slike kontejnera za Spring Boot aplikaciju pomoću Dockera

Umjesto da koristimo Maven ili Gradle dodatak, također možemo kreirati slojevitu Docker JAR sliku s Docker datotekom.

Kada koristimo Docker, moramo izvršiti dva dodatna koraka da izdvojimo slojeve i kopiramo ih u konačnu sliku.

Sadržaj rezultirajućeg JAR-a nakon izgradnje koristeći Maven sa omogućenim slojevanjem će izgledati 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.idxfajl. Ovaj dodatni JAR fajl pruža slojevite mogućnosti obrade, kao što je opisano u sledećem odeljku.

Izdvajanje zavisnosti od pojedinačnih slojeva

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

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

Izvođenje 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 komande listextractи helpс helpbiti zadani. Pokrenimo komandu sa listopcija:

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

Vidimo listu zavisnosti koje se mogu dodati kao slojevi.

Zadani slojevi:

Ime sloja

Sadržaj

dependencies

bilo koju zavisnost čija verzija ne sadrži SNAPSHOT

spring-boot-loader

JAR klase utovarivača

snapshot-dependencies

bilo koju zavisnost čija verzija sadrži SNAPSHOT

application

klase aplikacija i resursi

Slojevi su definisani u layers.idxdatoteku redoslijedom kojim bi se trebali dodati u Docker sliku. Ovi slojevi se keširaju u hostu nakon prvog preuzimanja jer se ne mijenjaju. Na host se preuzima samo ažurirani sloj aplikacije, što je brže zbog smanjene veličine .

Izgradnja slike sa zavisnostima izdvojenim u zasebne slojeve

Konačnu sliku ćemo izgraditi u dvije faze koristeći metodu tzv višestepena montaža . U prvom koraku ćemo izdvojiti ovisnosti, au drugom ćemo kopirati izvučene ovisnosti u konačnu sliku.

Modificirajmo naš Dockerfile za višestepenu gradnju:

# 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 čuvamo u zasebnoj datoteci - Dockerfile2.

Docker sliku gradimo pomoću naredbe:

docker build -f Dockerfile2 -t usersignup:v1 .

Nakon pokretanja ove naredbe dobijamo 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

Možemo vidjeti da je Docker slika kreirana s ID-om slike, a zatim označena.

Konačno, pokrećemo naredbu Dive kao i prije da pregledamo slojeve unutar generirane Docker slike. Možemo dati ID slike ili oznaku kao ulaz za komandu Dive:

dive userssignup:v1

Kao što možete vidjeti u izlazu, sloj koji sadrži aplikaciju sada ima samo 11 KB, a ovisnosti su keširane u odvojenim slojevima. 

Izrada optimiziranih Docker slika za Spring Boot aplikaciju

Izdvajanje internih zavisnosti od pojedinačnih slojeva

Možemo dodatno smanjiti veličinu sloja aplikacije izdvajanjem bilo koje naše prilagođene ovisnosti u poseban sloj umjesto da ih pakujemo s aplikacijom tako što ćemo ih deklarirati u ymlsličan fajl pod nazivom 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 ovom fajlu layers.idxdodali smo prilagođenu zavisnost pod nazivom, io.myorgkoji sadrži ovisnosti organizacije preuzete iz zajedničkog spremišta.

zaključak

U ovom članku smo se osvrnuli na korištenje Cloud-Native Buildpacks za izgradnju slike kontejnera direktno iz izvornog koda. Ovo je alternativa korišćenju Dockera za kreiranje slike kontejnera na uobičajen način: prvo kreiranje debele izvršne JAR datoteke, a zatim je pakiranje u sliku kontejnera navođenjem uputstava u Docker datoteci.

Takođe smo razmotrili optimizaciju našeg kontejnera omogućavanjem funkcije slojevitosti koja povlači zavisnosti u zasebne slojeve koji se keširaju na hostu, a tanak sloj aplikacije se učitava u vreme planiranja u izvršnim mašinama kontejnera.

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.

Brisanje konteksta:

docker system prune -a

Kreiranje slike kontejnera pomoću Docker datoteke:

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

Gradimo sliku kontejnera iz izvornog koda (bez Dockerfile-a):

mvn spring-boot:build-image

Pogledajte slojeve zavisnosti. Prije izrade JAR datoteke aplikacije, uvjerite se da je funkcija slojevitosti omogućena u spring-boot-maven-plugin-u:

java -Djarmode=layertools -jar application.jar list

Ekstrahiranje slojeva zavisnosti. Prije izrade JAR datoteke aplikacije, uvjerite se da je funkcija slojevitosti omogućena u spring-boot-maven-plugin-u:

 java -Djarmode=layertools -jar application.jar extract

Pogledajte listu slika kontejnera

docker images

Pogledajte s lijeve strane unutar slike kontejnera (provjerite je li instaliran alat za ronjenje):

dive <image ID or image tag>

izvor: www.habr.com