Kontejnerové technologie se staly preferovaným prostředkem pro balení aplikace se všemi jejími závislostmi na softwaru a operačním systému a následné doručení do různých prostředí.
V tomto článku se zabýváme různými způsoby kontejnerizace aplikace Spring Boot:
- vytvoření obrazu Docker pomocí Dockerfile,
- vytvoření obrazu OCI ze zdrojového kódu pomocí Cloud-Native Buildpack,
- a optimalizace obrazu za běhu rozdělením JAR částí na různé úrovně pomocí vícestupňových nástrojů.
Příklad kódu
Tento článek je doprovázen příkladem funkčního kódu. .
Terminologie kontejnerů
Začneme s terminologií kontejnerů, která se v článku používá:
- Obraz kontejneru (Container image): soubor určitého formátu. Převedeme naši aplikaci na obraz kontejneru spuštěním nástroje pro sestavení.
- Kontejner: spustitelný exemplář obrazu kontejneru.
- Motor kontejneru (Container engine): procesní démon zodpovědný za spuštění kontejneru.
- Hostitel kontejneru (Container host): hostitelský počítač, na kterém běží motor kontejneru.
- Registr kontejnerů (Container registry): společná lokalita používaná k publikování a distribuci obrazu kontejneru.
- Standard OCI: — je lehká otevřená struktura správy, která byla vytvořena v rámci Linux Foundation. Specifikace obrazů OCI definuje průmyslové standardy pro formáty obrazů kontejnerů a prostředí pro běh, aby bylo zajištěno, že všechny motory kontejnerů mohou spouštět obrazy kontejnerů vytvořené jakýmkoli nástrojem pro sestavení.
Abychom umístili aplikaci do kontejneru, zabalíme naši aplikaci do obrazu kontejneru a publikujeme tento obraz na společném registru. Prostředí běhu kontejneru vytáhne tento obraz z registru, rozbalí ho a spustí aplikaci uvnitř něj.
Verze 2.3 Spring Boot poskytuje pluginy pro vytváření obrazů OCI.
— nejčastěji používaná implementace kontejneru, přičemž v našich příkladech používáme Docker, takže všechny další odkazy na kontejner v tomto článku budou znamenat Docker.
Vytváření obrazu kontejneru tradičním způsobem
Vytvářet obrazy Docker pro aplikace Spring Boot je velmi snadné, přidáním několika příkazů do Dockerfile.
Nejprve vytvoříme spustitelný JAR soubor a jako součást pokynů pro Docker přeneseme spustitelný JAR soubor na základní obraz JRE po aplikaci potřebných nastavení.
Vytvořme naši aplikaci Spring na s závislostmi web, lomboka actuator. Přidáme také rest kontroler, aby poskytoval API s GETmetodou.
Vytvoření Docker souboru
Poté vložíme tuto aplikaci do kontejneru, přidáním Dockerfile:
FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/application.jar"]Náš Docker soubor obsahuje základní obraz, z adoptopenjdk, na který přenášíme náš JAR soubor, a pak otevřeme port, 8080který bude naslouchat požadavkům.
Sestavení aplikace
Nejprve musíme vytvořit aplikaci pomocí Maven nebo Gradle. Zde používáme Maven:
mvn clean packageTo vytvoří spustitelný JAR soubor aplikace. Musíme tento spustitelný JAR převést na Docker obraz pro běh v Docker enginu.
Vytvoření kontejnerového obrazu
Poté přeneseme tento spustitelný JAR do Docker obrazu pomocí příkazu docker buildz kořenového adresáře projektu, který obsahuje dříve vytvořený Docker soubor:
docker build -t usersignup:v1 .Můžeme vidět náš obraz v seznamu pomocí příkazu:
docker images Výsledek provedení výše uvedeného příkazu zahrnuje náš obraz usersignupspolu se základním obrazem, adoptopenjdk, uvedeným v našem Docker souboru.
REPOSITORY TAG SIZE
usersignup v1 249MB
adoptopenjdk 11-jre-hotspot 229MBProhlížení vrstev uvnitř obrazu kontejneru
Podívejme se na zásobník vrstev uvnitř obrazu. Použijeme abychom prozkoumali tyto vrstvy:
dive usersignup:v1Zde je část výsledků provedení příkazu Dive:

Jak vidíme, aplikační úroveň tvoří značnou část velikosti obrazu. Chceme snížit velikost této vrstvy v následujících sekcích v rámci naší optimalizace.
Vytvoření kontejnerového obrazu pomocí Buildpack
() – je to obecný termín používaný různými nabídkami „Platforma jako služba“ (PAAS) k vytvoření kontejnerového obrazu z původního kódu. Byl spuštěn Heroku v roce 2011 a od té doby byl přijat Cloud Foundry, Google App Engine, Gitlab, Knative a některé další.

Výhoda cloudových buildpacků
Jednou z hlavních výhod použití Buildpack pro vytváření obrazů je to, že Změny konfigurace obrazu mohou být spravovány centrálně (builder) a distribuovány na všechny aplikace, které používají builder.
Sestavovací balíčky byly úzce spojeny s platformou. Cloud-Native Buildpacks poskytují standardizaci mezi platformami tím, že podporují formát obrazu OCI, který zaručuje, že obraz může být spuštěn na enginu Docker.
Použití pluginu Spring Boot
Plugin Spring Boot vytváří obrazy OCI z původního kódu pomocí Buildpacku. Obrazy jsou vytvářeny pomocí bootBuildImageúkolů (Gradle) nebo spring-boot:build-imagecílů (Maven) a lokální instalace Dockeru.
Můžeme nastavit název obrazu, který bude nutný pro odeslání do registru Docker, uvedením názvu v image tag:
org.springframework.boot
spring-boot-maven-plugin
docker.io/pratikdas/${project.artifactId}:v1Pojďme použít Maven pro provedení build-imagecíle pro vytváření aplikace a vytváření obrazu kontejneru. V tuto chvíli nepoužíváme žádné Docker soubory.
mvn spring-boot:build-imageVýsledek bude přibližně takovýto:
[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'Z výstupních dat vidíme, že paketo Cloud-Native buildpackje používán pro vytváření funkčního obrazu OCI. Jak bylo dříve, můžeme vidět obraz označený jako obraz Docker, provedením příkazu:
docker images Výstup:
REPOSITORY SIZE
paketobuildpacks/run 84.3MB
gcr.io/paketo-buildpacks/builder 652MB
pratikdas/usersignup 257MBVytváření obrazu kontejneru pomocí Jib
Jib je plugin pro vytváření obrazů od Google, který poskytuje alternativní metodu pro vytváření obrazu kontejneru z původního kódu.
Nastavujeme jib-maven-pluginv pom.xml:
com.google.cloud.tools
jib-maven-plugin
2.5.2Dále spouštíme plugin Jib pomocí příkazu Maven, abychom vytvořili aplikaci a vytvořili obraz kontejneru. Jak bylo dříve, zde nepoužíváme žádné Docker soubory:
mvn compile jib:build -Dimage=/usersignup:v1Po provedení uvedeného příkazu Maven dostáváme následující výstup:
[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% completeVýstup ukazuje, že obraz kontejneru byl vytvořen a nahrán do registru.
Motivace a metody pro vytváření optimalizovaných obrazů
Máme dva hlavní důvody pro optimalizaci:
- Výkon: v systému orchestrace kontejnerů se obraz kontejneru stahuje z registru obrazů na hostiteli, na kterém je spuštěn mechanismus kontejneru. Tento proces se nazývá plánování. Stahování velkých obrazů z registru vede k dlouhé době plánování v systémech orchestrace kontejnerů a dlouhé době sestavování v CI pipeline.
- Bezpečnost: velké obrazy také mají větší oblast pro zranitelnosti.
Obraz Docker se skládá z vrstvy, přičemž každá vrstva představuje instrukci v našem Dockerfile. Každá vrstva představuje deltu změn podřízené vrstvy. Když stahujeme obraz Docker z registru, stahuje se po vrstvách a ukládá se do cache na hostiteli.
Spring Boot používá Optimalizační formule se zaměřuje na izolaci aplikace na samostatné úrovni od závislostí Spring Framework.
Vrstva závislostí, která tvoří hlavní část tlustého JAR souboru, se načítá pouze jednou a ukládá se do cache v hostitelském systému.
Pouze tenká vrstva aplikace se stahuje během aktualizací aplikace a plánování kontejnerů,
jak je znázorněno na tomto diagramu: V následujících sekcích se podíváme na to, jak vytvářet tyto optimalizované obrazy pro aplikaci Spring Boot.

Vytváření optimalizovaného obrazu kontejneru pro aplikaci Spring Boot pomocí Buildpack
Spring Boot 2.3 podporuje víceúrovňovost tím, že extrahuje části tlustého JAR souboru do samostatných vrstev. Funkce vrstvení je ve výchozím nastavení deaktivována a musí být explicitně povolena pomocí pluginu Spring Boot Maven:
Spring Boot 2.3 podporuje vícestupňovost tím, že extrahuje části tlustého JAR souboru do samostatných vrstev. Funkce vrstvení je ve výchozím nastavení vypnuta a musí být výslovně zapnuta pomocí pluginu Spring Boot Maven:
org.springframework.boot
spring-boot-maven-plugin
trueTuto konfiguraci využijeme k vytvoření našeho kontejnerového obrazu nejprve pomocí Buildpack, a poté pomocí Dockeru v následujících kapitolách.
Spusťme build-imagecíl Maven pro vytvoření kontejnerového obrazu:
mvn spring-boot:build-imagePokud spustíme Dive, abychom viděli vrstvy v výsledném obrazu, zjistíme, že úroveň aplikace (označená červeně) je mnohem menší v řádu kilobajtů ve srovnání s tím, co jsme získali použitím tlustého formátu JAR:

Vytvoření optimalizovaného kontejnerového obrazu pro aplikaci Spring Boot pomocí Dockeru
Místo použití pluginu Maven nebo Gradle můžeme také vytvořit vícevrstvý obraz JAR Docker pomocí souboru Docker.
Když používáme Docker, musíme vykonat dva další kroky pro extrakci vrstev a jejich zkopírování do konečného obrazu.
Obsah získaného JAR po sestavení pomocí Maven se zapnutou funkcí vrstvení bude vypadat následovně:
META-INF/
.
BOOT-INF/lib/
.
BOOT-INF/lib/spring-boot-jarmode-layertools-2.3.3.RELEASE.jar
BOOT-INF/classpath.idx
BOOT-INF/layers.idxVe výstupu se zobrazuje další JAR s názvem spring-boot-jarmode-layertoolsa layersfle.idxsoubor. Tento dodatečný JAR soubor poskytuje možnost vícevrstvého zpracování, jak je popsáno v následující kapitole.
Extrahování závislostí na samostatných vrstvách
Abychom si prohlédli a extrahovali vrstvy z našeho vícevrstvého JAR, použijeme systémovou vlastnost -Djarmode=layertoolspro spuštění spring-boot-jarmode-layertoolsJAR místo aplikace:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jarSpuštění tohoto příkazu poskytne výstup obsahující dostupné příkazy:
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 commandVýstup ukazuje příkazy list, extracta helps helpbýt ve výchozím nastavení. Pojďme spustit příkaz s listmožností:
java -Djarmode=layertools -jar target/usersignup-0.0.1-SNAPSHOT.jar listdependencies
spring-boot-loader
snapshot-dependencies
applicationVidíme seznam závislostí, které lze přidat jako vrstvy.
Výchozí vrstvy:
Název vrstvy
Obsah
závislosti
jakákoli závislost, jejíž verze neobsahuje SNAPSHOT
spring-boot-loader
třídy načítače JAR
snapshot-dependencies
jakákoli závislost, jejíž verze obsahuje SNAPSHOT
application
třídy aplikací a zdroje
Vrstvy jsou definovány v layers.idxsouboru v pořadí, v jakém by měly být přidány do obrazu Docker. Tyto vrstvy jsou po prvním načtení kešovány na hostiteli, protože se nemění. Na hostitele se nahrává pouze aktualizovaná vrstva aplikace, což probíhá rychleji díky zmenšené velikosti. .
Sestavení obrazu se závislostmi extrahovanými do samostatných vrstev.
Sestavíme finální obraz ve dvou krocích pomocí metody zvané . V prvním kroku extrahujeme závislosti a ve druhém kroku zkopírujeme extrahované závislosti do finálního obrazu.
Upravíme náš soubor Docker pro vícefázové sestavení:
# 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"]Ukládáme tuto konfiguraci do samostatného souboru — Dockerfile2.
Sestavíme obraz Docker pomocí příkazu:
docker build -f Dockerfile2 -t usersignup:v1 .Po provedení tohoto příkazu dostaneme následující výstup:
Odesílání kontextu sestavení do Daemon Docker 20,41 MB
Krok 1/12 : Z FROM adoptopenjdk:14-jre-hotspot jako builder
14-jre-hotspot: Stahování z knihovny/adoptopenjdk
.
.
Úspěšně postaveno a9ebf6970841
Úspěšně označeno userssignup:v1Vidíme, že obraz Docker je vytvořen s ID obrazu a poté označen.
Nakonec spustíme příkaz Dive, jako dříve, abychom zkontrolovali vrstvy uvnitř vygenerovaného obrazu Docker. Můžeme specifikovat ID obrazu nebo tag jako vstup pro příkaz Dive:
dive userssignup:v1Jak je patrné z výstupu, vrstva obsahující aplikaci nyní zabírá pouze 11 KB a závislosti jsou uloženy v samostatných vrstvách.

Extrakce vnitřních závislostí do samostatných vrstev.
Dále můžeme zmenšit velikost vrstvy aplikace extrakčními jakýmikoliv našimi uživatelskými závislostmi do samostatné vrstvy místo toho, abychom je balili spolu s aplikací, když je deklarujeme v ymlpodobném souboru s názvem 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/"V tomto souboru layers.idxjsme přidali přizpůsobenou závislost s názvem, io.myorgkterý obsahuje závislosti organizace získané z veřejného repozitáře.
Výstup
V tomto článku jsme prozkoumali použití Cloud-Native Buildpacks k vytvoření kontejnerového obrazu přímo ze zdrojového kódu. Je to alternativa k použití Dockeru pro obvyklé vytváření kontejnerového obrazu: nejprve se vytvoří silný spustitelný soubor JAR a poté se zabalí do kontejnerového obrazu, přičemž se pokyny uvedou v souboru Docker.
Také jsme se zabývali optimalizací našeho kontejneru, včetně funkce vrstvení, která extrahuje závislosti do samostatných úrovní, jež jsou cachovány na hostiteli, zatímco tenká vrstvá aplikace je načítána během plánování v mechanismech provádění kontejnerů.
Celý zdrojový kód použitý v článku můžete najít na .
Příručka příkazů
Zde je stručný přehled příkazů, které jsme použili v tomto článku pro rychlou orientaci.
Úklid kontextu:
docker system prune -aVytvoření kontejnerového obrazu pomocí souboru Docker:
docker build -f -t .Sestavení kontejnerového obrazu ze zdrojového kódu (bez Dockerfile):
mvn spring-boot:build-imageZobrazení vrstev závislostí. Před sestavením JAR-souboru aplikace se ujistěte, že funkce vrstvení je povolena v spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar listExtrakce vrstev závislostí. Před sestavením JAR-souboru aplikace se ujistěte, že funkce vrstvení je povolena v spring-boot-maven-plugin:
java -Djarmode=layertools -jar application.jar extractZobrazení seznamu kontejnerových obrazů
docker imagesZobrazení vrstev uvnitř kontejnerového obrazu (ujistěte se, že máte nainstalovaný nástroj pro ponoření):
diveZdroj: habr.com
