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 standard: Inicijativa 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 web, lombokи actuator. Također dodajemo kontroler odmora za pružanje API-ja GETmetoda.
Kreiranje Dockerfile-a
Zatim kontejneriziramo ovu aplikaciju dodavanjem Dockerfile:
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:
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.
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:
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:
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:
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:
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:
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:
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:
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:
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 list, extractи helpс helpbiti zadani. Pokrenimo komandu sa listopcija:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar list
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.
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:
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: