Vytváření optimalizovaných obrázků dockeru pro aplikaci Spring Boot

Kontejnery se staly preferovaným prostředkem pro zabalení aplikace se všemi jejich závislostmi na softwaru a operačním systému a jejich následné dodání do různých prostředí.

Tento článek popisuje různé způsoby kontejnerizace aplikace Spring Boot:

  • vytvoření obrazu dockeru pomocí dockerfile,
  • vytvoření obrazu OCI ze zdroje pomocí Cloud-Native Buildpack,
  • a optimalizace obrazu za běhu oddělením částí JAR do různých úrovní pomocí vrstvených nástrojů.

 Příklad kódu

Tento článek je doplněn příkladem pracovního kódu na GitHub .

Kontejnerová terminologie

Začneme terminologií kontejnerů používanou v celém článku:

  • Obrázek kontejneru: soubor určitého formátu. Spuštěním nástroje sestavení převedeme naši aplikaci na obrázek kontejneru.
  • kontejner: Spustitelná instance obrazu kontejneru.
  • Kontejnerový motor: Proces démona zodpovědný za spuštění kontejneru.
  • Hostitel kontejneru: Hostitelský počítač, na kterém běží kontejnerový stroj.
  • Registr kontejnerů: Obecné umístění používané k publikování a distribuci obrazu kontejneru.
  • standard OCIOpen Container Initiative (OCI) je lehký, open-source rámec pro správu vytvořený Linux Foundation. OCI Image Specification definuje průmyslové standardy pro formáty obrázků kontejnerů a běhové prostředí, aby bylo zajištěno, že všechny kontejnerové stroje mohou spouštět obrázky kontejnerů generované jakýmkoli nástrojem pro vytváření.

Abychom aplikaci kontejnerizovali, zabalíme naši aplikaci do obrázku kontejneru a publikujeme tento obrázek do veřejného registru. Runtime kontejneru načte tento obraz z registru, rozbalí jej a spustí v něm aplikaci.

Verze 2.3 Spring Boot poskytuje zásuvné moduly pro vytváření obrazů OCI.

přístavní dělník je nejběžněji používaná implementace kontejneru a v našich příkladech používáme Docker, takže všechny následující odkazy na kontejnery v tomto článku budou odkazovat na Docker.

Vytvoření image kontejneru tradičním způsobem

Vytváření obrazů Dockeru pro aplikace Spring Boot je velmi snadné přidáním několika pokynů do souboru Dockerfile.

Nejprve vytvoříme spustitelný JAR a jako součást instrukcí Dockerfile zkopírujeme spustitelný JAR na základní obraz JRE po použití nezbytných úprav.

Pojďme vytvořit naši jarní aplikaci na Spring Initializr se závislostmi weblombokи actuator. Přidáváme také řadič odpočinku, který poskytuje API GETmetoda.

Vytvoření souboru Dockerfile

Tuto aplikaci pak přidáváním umístíme do kontejneru Dockerfile:

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

Náš soubor Dockerfile obsahuje základní obrázek, od adoptopenjdk, na který zkopírujeme náš soubor JAR a poté otevřeme port, 8080který bude naslouchat žádostem.

Sestavení aplikace

Nejprve musíte vytvořit aplikaci pomocí Maven nebo Gradle. Zde používáme Maven:

mvn clean package

Tím se vytvoří spustitelný soubor JAR pro aplikaci. Potřebujeme převést tento spustitelný JAR na obraz Dockeru, aby mohl běžet na enginu Docker.

Vytvořte obrázek kontejneru

Tento spustitelný soubor JAR pak vložíme do obrazu Dockeru spuštěním příkazu docker buildz kořenového adresáře projektu obsahujícího dříve vytvořený soubor Docker:

docker build  -t usersignup:v1 .

Náš obrázek můžeme vidět v seznamu příkazem:

docker images 

Výstup výše uvedeného příkazu obsahuje náš obrázek usersignupspolu se základním obrázkem, adoptopenjdkspecifikované v našem Dockerfile.

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

Zobrazení vrstev uvnitř obrázku kontejneru

Podívejme se na hromadu vrstev uvnitř obrázku. budeme používat инструмент  potápět se, pro zobrazení těchto vrstev:

dive usersignup:v1

Zde je část výstupu příkazu Dive: 

Vytváření optimalizovaných obrázků dockeru pro aplikaci Spring Boot

Jak vidíme, aplikační vrstva tvoří významnou část velikosti obrázku. V rámci naší optimalizace chceme v následujících částech zmenšit velikost této vrstvy.

Vytvoření obrázku kontejneru pomocí Buildpack

Montážní balíčky (Buildpacks) je obecný termín používaný různými nabídkami Platform as a Service (PAAS) k vytvoření image kontejneru ze zdrojového kódu. Byl spuštěn společností Heroku v roce 2011 a od té doby byl přijat společnostmi Cloud Foundry, Google App Engine, Gitlab, Knative a několika dalšími.

Vytváření optimalizovaných obrázků dockeru pro aplikaci Spring Boot

Výhoda balíčků Cloud Build Packages

Jednou z hlavních výhod použití Buildpack k vytváření obrázků je to změny konfigurace obrazu mohou být spravovány centrálně (builder) a šířeny do všech aplikací pomocí builderu.

Sestavovací balíčky byly úzce svázány s platformou. Cloud-Native Buildpacks poskytují standardizaci napříč platformami tím, že podporují obrazový formát OCI, což zajišťuje, že obraz lze spustit pomocí modulu Docker.

Použití modulu Spring Boot Plugin

Plugin Spring Boot vytváří obrazy OCI ze zdroje pomocí Buildpack. Obrázky jsou vytvořeny pomocí bootBuildImageúkoly (Gradle) popř spring-boot:build-imagecíl (Maven) a místní instalace Dockeru.

Můžeme upravit název obrázku, který potřebujeme odeslat do registru Docker, zadáním názvu 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>

Použijme Maven k provedení build-imagecíle pro vytvoření aplikace a vytvoření image kontejneru. Momentálně nepoužíváme žádné Dockerfiles.

mvn spring-boot:build-image

Výsledek bude něco takového:

[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ýstupu to vidíme paketo Cloud-Native buildpackslouží k vytvoření pracovního obrazu OCI. Stejně jako dříve můžeme vidět obrázek uvedený jako obrázek Docker spuštěním příkazu:

docker images 

Závěr:

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

Vytvoření obrázku kontejneru pomocí Jib

Jib je plugin pro tvorbu obrázků od společnosti Google, který poskytuje alternativní metodu vytváření obrázku kontejneru ze zdroje.

Konfigurace jib-maven-pluginv pom.xml:

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

Dále spustíme plugin Jib pomocí příkazu Maven k sestavení aplikace a vytvoření bitové kopie kontejneru. Stejně jako dříve zde nepoužíváme žádné soubory Docker:

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

Po provedení výše uvedeného příkazu Maven získá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% complete

Výstup ukazuje, že obraz kontejneru byl vytvořen a umístěn do registru.

Motivace a metody tvorby optimalizovaných obrázků

Pro optimalizaci máme dva hlavní důvody:

  • Производительность: V systému orchestrace kontejnerů je obraz kontejneru načten z registru obrazů do hostitele, na kterém je spuštěn kontejnerový stroj. Tento proces se nazývá plánování. Vytahování velkých obrazů z registru má za následek dlouhé doby plánování v systémech orchestrace kontejnerů a dlouhé doby sestavení v kanálech CI.
  • zabezpečení: velké obrázky mají také velkou oblast pro zranitelnosti.

Obraz Dockeru se skládá z hromady vrstev, z nichž každá představuje prohlášení v našem souboru Docker. Každá vrstva představuje deltu změn v podkladové vrstvě. Když vytáhneme obrázek Dockeru z registru, je stažen ve vrstvách a uložen do mezipaměti na hostiteli.

Použití Spring Boot "tlustý JAR" v jako výchozí formát balení. Když se podíváme na tlustý JAR, vidíme, že aplikace je velmi malá část celého JAR. Toto je část, která se nejvíce mění. Zbytek tvoří závislosti Spring Framework.

Optimalizační vzorec je zaměřen na izolaci aplikace na úrovni oddělené od závislostí Spring Framework.

Závislá vrstva, která tvoří většinu tlustého souboru JAR, se stáhne pouze jednou a uloží do mezipaměti hostitelského systému.

Během aktualizací aplikace a plánování kontejneru se stáhne pouze tenká vrstva aplikace, jak ukazuje tento diagram:

Vytváření optimalizovaných obrázků dockeru pro aplikaci Spring Boot

V následujících částech se podíváme na to, jak vytvořit tyto optimalizované obrazy pro aplikaci Spring Boot.

Vytvoření optimalizovaného obrazu kontejneru pro aplikaci Spring Boot pomocí Buildpack

Spring Boot 2.3 podporuje vrstvení extrahováním částí tlustého souboru JAR do samostatných vrstev. Funkce vrstvení je ve výchozím nastavení zakázána a je třeba ji explicitně povolit pomocí pluginu Spring Boot Maven:

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

Tuto konfiguraci použijeme k vytvoření bitové kopie kontejneru nejprve pomocí Buildpack a poté pomocí Dockeru v následujících částech.

Utíkejme build-imageMaven cíl pro vytvoření obrázku kontejneru:

mvn spring-boot:build-image

Pokud spustíme Dive, abychom viděli vrstvy ve výsledném obrázku, můžeme vidět, že aplikační vrstva (zakroužkovaná červeně) je mnohem menší v rozsahu kilobajtů ve srovnání s tím, co jsme získali pomocí tlustého formátu JAR:

Vytváření optimalizovaných obrázků dockeru pro aplikaci Spring Boot

Vytvoření optimalizovaného obrazu kontejneru pro aplikaci Spring Boot pomocí Dockeru

Místo použití pluginu Maven nebo Gradle můžeme také vytvořit vrstvený obraz Docker JAR se souborem Docker.

Když používáme Docker, musíme udělat dva další kroky k extrahování vrstev a jejich zkopírování do konečného obrázku.

Obsah výsledného JAR po sestavení pomocí Maven se zapnutým vrstvením bude vypadat takto:

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

Výstup zobrazuje další JAR s názvem spring-boot-jarmode-layertoolsи layersfle.idxsoubor. Tento dodatečný soubor JAR poskytuje možnosti vrstvení, jak je popsáno v další části.

Extrahujte závislosti na samostatných vrstvách

K zobrazení a extrahování vrstev z našeho vrstveného JAR používáme vlastnost system -Djarmode=layertoolspro začátek spring-boot-jarmode-layertoolsJAR místo aplikace:

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

Spuštěním tohoto příkazu se vytvoří výstup obsahující dostupné možnosti příkazu:

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

Výstup zobrazuje příkazy listextractи helpс helpbýt výchozí. Spusťte příkaz s listvolba:

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

Vidíme seznam závislostí, které lze přidat jako vrstvy.

Vrstvy ve výchozím nastavení:

Název vrstvy

Obsah

dependencies

jakákoliv závislost, jejíž verze neobsahuje SNAPSHOT

spring-boot-loader

Třídy nakladačů JAR

snapshot-dependencies

jakákoliv závislost, jejíž verze obsahuje SNAPSHOT

application

aplikační třídy a prostředky

Vrstvy jsou definovány v layers.idxsoubor v pořadí, ve kterém by měly být přidány do obrazu Docker. Tyto vrstvy jsou po prvním načtení uloženy do mezipaměti hostitele, protože se nemění. Na hostitele se stáhne pouze aktualizovaná aplikační vrstva, což je rychlejší díky zmenšené velikosti .

Vytvoření obrazu se závislostmi extrahovanými do samostatných vrstev

Výsledný obraz vytvoříme ve dvou krocích pomocí metody tzv vícestupňová montáž . V prvním kroku vyextrahujeme závislosti a ve druhém kroku extrahované závislosti zkopírujeme do výsledného .

Upravme náš Dockerfile 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"]

Tuto konfiguraci uložíme do samostatného souboru - Dockerfile2.

Obraz Dockeru vytvoříme pomocí příkazu:

docker build -f Dockerfile2 -t usersignup:v1 .

Po provedení tohoto příkazu dostaneme následující výstup:

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

Můžeme vidět, že obrázek Docker je vytvořen s ID obrázku a poté je označen.

Nakonec spustíme příkaz Dive jako dříve, abychom zkontrolovali vrstvy uvnitř vygenerovaného obrázku Docker. Můžeme poskytnout ID obrázku nebo značku jako vstup do příkazu Dive:

dive userssignup:v1

Jak můžete vidět z výstupu, vrstva obsahující aplikaci má nyní pouze 11 KB a závislosti jsou ukládány do mezipaměti v samostatných vrstvách. 

Vytváření optimalizovaných obrázků dockeru pro aplikaci Spring Boot

Extrahujte vnitřní závislosti na samostatných vrstvách

Velikost aplikační vrstvy můžeme dále zmenšit extrahováním jakékoli z našich vlastních závislostí do samostatné vrstvy místo toho, abychom je sbalili s aplikací tak, že je deklarujeme v ymlpodobný soubor 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.idxpřidali jsme vlastní závislost s názvem, io.myorgobsahující závislosti organizace načtené ze sdíleného úložiště.

Výkon

V tomto článku jsme se podívali na použití Cloud-Native Buildpacks k vytvoření image kontejneru přímo ze zdroje. Toto je alternativa k použití Dockeru k vytvoření obrazu kontejneru obvyklým způsobem: nejprve se vytvoří tlustý spustitelný soubor JAR a poté se zabalí do obrazu kontejneru zadáním pokynů v souboru Dockerfile.

Také jsme se podívali na optimalizaci našeho kontejneru zahrnutím funkce vrstvení, která extrahuje závislosti do samostatných vrstev, které jsou uloženy v mezipaměti hostitele, a tenká aplikační vrstva je načtena v době plánování v prováděcích motorech kontejneru.

Veškerý použitý zdrojový kód najdete v článku na adrese GitHub .

Reference příkazů

Zde je souhrn příkazů, které jsme použili v tomto článku pro rychlou orientaci.

Vyčištění kontextu:

docker system prune -a

Vytvoření obrazu kontejneru pomocí souboru Dockerfile:

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

Sestavení obrázku kontejneru ze zdroje (bez Dockerfile):

mvn spring-boot:build-image

Zobrazit vrstvy závislostí. Před vytvořením souboru jar aplikace se ujistěte, že je v modulu spring-boot-maven-plugin povolena funkce vrstvení:

java -Djarmode=layertools -jar application.jar list

Extrahujte vrstvy závislostí. Před vytvořením souboru jar aplikace se ujistěte, že je v modulu spring-boot-maven-plugin povolena funkce vrstvení:

 java -Djarmode=layertools -jar application.jar extract

Zobrazení seznamu obrázků kontejnerů

docker images

Zobrazit vlevo uvnitř obrázku kontejneru (ujistěte se, že je nainstalován potápěčský nástroj):

dive <image ID or image tag>

Zdroj: www.habr.com