Стварэнне аптымізаваных вобразаў Docker для прыкладання Spring Boot

Кантэйнеры сталі пераважным сродкам упакоўкі прыкладання з усімі залежнасцямі праграмнага забеспячэння і аперацыйнай сістэмы, а затым дастаўкі іх у розныя асяроддзі.

У гэтым артыкуле разглядаюцца розныя спосабы кантэйнерызацыі прыкладання Spring Boot:

  • стварэнне выявы Docker з дапамогай файла Docker,
  • стварэнне выявы OCI з зыходнага кода з дапамогай Cloud-Native Buildpack,
  • і аптымізацыя выявы падчас выканання шляхам падзелу частак JAR на розныя ўзроўні з дапамогай шматузроўневых інструментаў.

 Прыклад кода

Гэты артыкул суправаджаецца прыкладам працоўнага кода на GitHub .

Тэрміналогія кантэйнераў

Мы пачнем з тэрміналогіі кантэйнераў, якая выкарыстоўваецца ў артыкуле:

  • Выява кантэйнера (Container image): файл вызначанага фармату. Мы канвертуем наша дадатак у вобраз кантэйнера, запусціўшы інструмент зборкі.
  • кантэйнер: выкананы экзэмпляр вобраза кантэйнера.
  • Рухавічок кантэйнера (Container engine): працэс-дэман, які адказвае за запуск кантэйнера.
  • Хост кантэйнера (Container host): хост-кампутар, на якім працуе механізм кантэйнера.
  • Рэестр кантэйнераў (Container registry): агульнае размяшчэнне, якое выкарыстоўваецца для публікацыі і распаўсюджвання выявы кантэйнера.
  • Стандарт OCIІніцыятыва адкрытага кантэйнера (OCI) – гэта аблегчаная адкрытая структура кіравання, сфармаваная ў рамках Linux Foundation. Спецыфікацыя выяў OCI вызначае галіновыя стандарты для фарматаў выяў кантэйнераў і асяроддзі выканання, каб гарантаваць, што ўсе механізмы кантэйнераў могуць запускаць выявы кантэйнераў, створаныя любой прыладай зборкі.

Каб змясціць прыкладанне ў кантэйнер, мы заключаем наша дадатак у вобраз кантэйнера і публікуем гэты вобраз у агульны рэестр. Асяроддзе выканання кантэйнера здабывае гэтую выяву з рэестра, распакоўвае яго і запускае прыкладанне ўсярэдзіне яго.

Версія 2.3 Spring Boot дае плагіны для стварэння вобразаў OCI.

Докер - Найбольш часта выкарыстоўваецца рэалізацыя кантэйнера, і мы выкарыстоўваем Docker ў нашых прыкладах, таму ўсе наступныя спасылкі на кантэйнер у гэтым артыкуле будуць азначаць Docker.

Пабудова выявы кантэйнера традыцыйным спосабам

Ствараць выявы Docker для прыкладанняў Spring Boot вельмі лёгка, дадаўшы некалькі інструкцый у файл Docker.

Спачатку мы ствараем выкананы файл JAR і, як частка інструкцый файла Docker, капіяваны выкананы файл JAR па-над базавай выявай JRE пасля ўжывання неабходных налад.

Давайце створым наша дадатак Spring на Spring Initializr з залежнасцямі weblombokи actuator. Мы таксама дадаем rest кантролер, каб даць API з GETметадам.

Стварэнне файла Docker

Затым мы змяшчае гэта дадатак у кантэйнер, дадаючы Dockerfile:

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

Наш файл Docker змяшчае базавую выяву, з adoptopenjdk, па-над якім мы капіюем наш файл JAR, а затым адчыняны порт, 8080які будзе праслухоўваць запыты.

Зборка прыкладання

Спачатку трэба стварыць дадатак з дапамогай Maven ці Gradle. Тут мы выкарыстоўваем Maven:

mvn clean package

Гэта стварае выкананы JAR-файл прыкладання. Нам трэба пераўтварыць гэты выкананы JAR у выяву Docker для працы ў рухавічку Docker.

Стварэнне выявы кантэйнера

Затым мы змяшчаем гэты выкананы файл JAR у выяву Docker, выканаўшы каманду docker buildз каранёвага каталога праекта, які змяшчае файл Docker, створаны раней:

docker build  -t usersignup:v1 .

Мы можам убачыць наша выява ў спісе з дапамогай каманды:

docker images 

Вынік выканання вышэйпаказанай каманды ўключае ў сябе наш лад usersignupразам з базавай выявай, adoptopenjdk, указаным у нашым файле Docker.

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

Прагляд пластоў ўнутры выявы кантэйнера

Давайце паглядзім на чарку пластоў ўнутры выявы. Мы будзем выкарыстоўваць інструмент  dive, каб прагледзець гэтыя пласты:

dive usersignup:v1

Вось частка вынікаў выканання каманды Dive: 

Стварэнне аптымізаваных вобразаў Docker для прыкладання Spring Boot

Як мы бачым, прыкладны ўзровень складае значную частку памеру выявы. Мы хочам зменшыць памер гэтага пласта ў наступных раздзелах у рамках нашай аптымізацыі.

Стварэнне выявы кантэйнера з дапамогай Buildpack

Зборачныя пакеты (Buildpacks) — гэта агульны тэрмін, які выкарыстоўваецца рознымі прапановамі «Платформа як паслуга» (PAAS) для стварэння выявы кантэйнера з зыходнага кода. Ён быў запушчаны Heroku ў 2011 годзе і з таго часу быў прыняты Cloud Foundry, Google App Engine, Gitlab, Knative і некаторымі іншымі.

Стварэнне аптымізаваных вобразаў Docker для прыкладання Spring Boot

Перавага хмарных зборачных пакетаў

Адным з асноўных пераваг выкарыстання Buildpack для стварэння вобразаў з'яўляецца тое, што зменамі канфігурацыі выявы можна кіраваць цэнтралізавана (builder) і распаўсюджваць на ўсе прыкладанні, якія выкарыстоўваюць builder.

Зборачныя пакеты былі цесна звязаны з платформай. Cloud-Native Buildpacks забяспечваюць стандартызацыю паміж платформамі, падтрымліваючы фармат выявы OCI, які гарантуе, што выява можа запускацца рухавічком Docker.

Выкарыстанне плагіна Spring Boot

Убудова Spring Boot стварае выявы OCI з зыходнага кода з дапамогай Buildpack. Выявы ствараюцца з выкарыстаннем bootBuildImageзадачы (Gradle) або spring-boot:build-imageмэты (Maven) і лакальнай усталёўкі Docker.

Мы можам наладзіць імя выявы, неабходнага для адпраўкі ў рэестр Docker, паказаўшы імя ў 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>

Давайце скарыстаемся Maven для выканання build-imageмэты па стварэнні прыкладання і стварэнню ладу кантэйнера. Цяпер мы не выкарыстоўваем ніякіх файлаў Docker.

mvn spring-boot:build-image

Вынік будзе прыкладна такім:

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

З выходных дадзеных мы бачым, што paketo Cloud-Native buildpackвыкарыстоўваецца для стварэння якая працуе выявы OCI. Як і раней, мы можам убачыць выяву, паказаны як выява Docker, выканаўшы каманду:

docker images 

Выснову:

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

Стварэнне выявы кантэйнера з дапамогай Jib

Jib - гэта плягін для стварэння малюнкаў ад Google, які дае альтэрнатыўны метад стварэння выявы кантэйнера з зыходнага кода.

Наладжваем jib-maven-pluginу pom.xml:

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

Далей мы запускаем убудова Jib з дапамогай каманды Maven, каб пабудаваць прыкладанне і стварыць вобраз кантэйнера. Як і раней, тут мы не выкарыстоўваем ніякіх файлаў Docker:

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

Пасля выканання паказанай вышэй каманды Maven мы атрымліваем наступную выснову:

[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

Выходныя дадзеныя паказваюць, што выява кантэйнера створаны і змешчаны ў рэестр.

Матывацыі і метады стварэння аптымізаваных малюнкаў

У нас ёсць дзве асноўныя прычыны для аптымізацыі:

  • Proizvoditelnost: у сістэме аркестроўкі кантэйнераў вобраз кантэйнера здабываецца з рэестра вобразаў на хост, на якім запушчаны механізм кантэйнера. Гэты працэс называецца планаваннем. Выманне вобразаў вялікага памеру з рэестра прыводзіць да працяглага часу планавання ў сістэмах аркестроўкі кантэйнераў і працяглага часу зборкі ў канвеерах CI.
  • бяспеку: выявы вялікага памеру таксама маюць вялікую вобласць для ўразлівасцяў.

Выява Docker складаецца з стэка пластоў, кожны з якіх уяўляе інструкцыю ў нашым Dockerfile. Кожны пласт уяўляе сабой дэльту змен ніжэйлеглага пласта. Калі мы здабываем выяву Docker з рэестра, ён здабываецца пластамі і кэшуецца на хасце.

Spring Boot выкарыстоўвае «тоўсты JAR» у якасці фармату упакоўкі па змаўчанні. Калі мы праглядаем тоўсты JAR, мы бачым, што прыкладанне складае вельмі маленькую частку ўсяго JAR. Гэта частка, якая мяняецца часцей за ўсё. Пакінутая частка складаецца з залежнасцяў Spring Framework.

Формула аптымізацыі сканцэнтравана вакол ізаляцыі дадатку на асобным узроўні ад залежнасцяў Spring Framework.

Пласт залежнасцяў, які фармуе асноўную частку тоўстага JAR-файла, загружаецца толькі адзін раз і кэшуецца ў хост-сістэме.

Толькі тонкі пласт прыкладання выцягваецца падчас абнаўленняў прыкладання і планавання кантэйнераў, як паказана на гэтай дыяграме:

Стварэнне аптымізаваных вобразаў Docker для прыкладання Spring Boot

У наступных раздзелах мы разгледзім, як ствараць гэтыя аптымізаваныя вобразы для прыкладання Spring Boot.

Стварэнне аптымізаванай выявы кантэйнера для прыкладання Spring Boot з дапамогай Buildpack

Spring Boot 2.3 падтрымлівае шматузроўневасць шляхам вымання частак тоўстага JAR-файла ў асобныя пласты. Функцыя напластавання па змаўчанні адключаная, і яе неабходна відавочна ўлучыць з дапамогай убудовы Spring Boot Maven:

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

Мы будзем выкарыстоўваць гэтую канфігурацыю для стварэння нашай выявы кантэйнера спачатку з дапамогай Buildpack, а затым з дапамогай Docker ў наступных раздзелах.

Давайце запусцім build-imageмэта Maven для стварэння выявы кантэйнера:

mvn spring-boot:build-image

Калі мы запусцім Dive, каб убачыць пласты ў выніковым адлюстраванні, мы ўбачым, што ўзровень прыкладання (абведзены чырвоным) нашмат менш у дыяпазоне кілабайт у параўнанні з тым, што мы атрымалі з выкарыстаннем тоўстага фармату JAR:

Стварэнне аптымізаваных вобразаў Docker для прыкладання Spring Boot

Стварэнне аптымізаванай выявы кантэйнера для прыкладання Spring Boot з дапамогай Docker

Замест выкарыстання плагіна Maven або Gradle мы таксама можам стварыць шматузроўневую выяву JAR Docker з файлам Docker.

Калі мы выкарыстоўваем Docker, нам трэба выканаць два дадатковых кроку для вымання пластоў і капіяванні іх у канчатковую выяву.

Змесціва атрыманага JAR пасля зборкі з дапамогай Maven з уключанай функцыяй напластавання будзе выглядаць наступным чынам:

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

У выходных дадзеных адлюстроўваецца дадатковы JAR з імем spring-boot-jarmode-layertoolsи layersfle.idxфайл. Гэты дадатковы JAR-файл дае магчымасць шматузроўневай апрацоўкі, як апісана ў наступным раздзеле.

Выманне залежнасцяў на асобных пластах

Каб прагледзець і атрымаць пласты з нашага шматузроўневага JAR, мы выкарыстоўваем сістэмнае ўласцівасць -Djarmode=layertoolsдля запуску spring-boot-jarmode-layertoolsJAR замест прыкладання:

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

Выкананне гэтай каманды дае выснову, якая змяшчае даступныя параметры каманды:

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

Выснова паказвае каманды listextractи helpс helpбыць па змаўчанні. Давайце запусцім каманду з listопцыяй:

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

Мы бачым спіс залежнасцяў, якія можна дадаць як пласты.

Пласты па змаўчанні:

Імя пласта

Змест

dependencies

любая залежнасць, версія якой не змяшчае SNAPSHOT

spring-boot-loader

Класы загрузніка JAR

snapshot-dependencies

любая залежнасць, версія якой змяшчае SNAPSHOT

application

класы прыкладанняў і рэсурсы

Пласты вызначаны ў layers.idxфайле ў тым парадку, у якім яны павінны быць дададзены ў вобраз Docker. Гэтыя пласты кэшуюцца ў хасце пасля першага вымання, паколькі яны не змяняюцца. На хост загружаецца толькі абноўлены ўзровень прыкладання, што адбываецца хутчэй з-за паменшанага памеру .

Пабудова выявы з залежнасцямі, вынятымі ў асобныя пласты

Мы пабудуем фінальную выяву ў два этапы, выкарыстоўваючы метад, званы шматэтапнай зборкай . На першым этапе мы атрымаем залежнасці, а на другім этапе мы скапіюем вынятыя залежнасці ў канчатковы вобраз .

Давайце мадыфікуем наш файл Docker для шматэтапнай зборкі:

# 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"]

Захоўваем гэтую канфігурацыю ў асобным файле - Dockerfile2.

Збіраны выява Docker з дапамогай каманды:

docker build -f Dockerfile2 -t usersignup:v1 .

Пасля выканання гэтай каманды мы атрымліваем такую ​​выснову:

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

Мы бачым, што выява Docker ствараецца з ідэнтыфікатарам малюнка, а затым тэгуецца.

Нарэшце, мы запускаем каманду Dive, як і раней, каб праверыць пласты ўнутры згенераванай выявы Docker. Мы можам паказаць ідэнтыфікатар выявы або тэг у якасці ўваходных дадзеных для каманды Dive:

dive userssignup:v1

Як відаць з выходных дадзеных, узровень, які змяшчае прыкладанне, зараз займае ўсяго 11 КБ, а залежнасці кэшуюцца ў асобных пластах. 

Стварэнне аптымізаваных вобразаў Docker для прыкладання Spring Boot

Выманне ўнутраных залежнасцяў на асобных пластах

Мы можам дадаткова паменшыць памер ўзроўню прыкладання, здабываючы любыя з нашых карыстацкіх залежнасцяў у асобны ўзровень замест таго, каб пакаваць іх разам з дадаткам, абвясціўшы іх у ymlпадобным файле з імем 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/"

У гэтым файле layers.idxмы дадалі наладжваемую залежнасць з імем, io.myorgякія змяшчаюць залежнасці арганізацыі, атрыманыя з агульнага рэпазітара.

Выснова

У гэтым артыкуле мы разгледзелі выкарыстанне Cloud-Native Buildpacks для стварэння выявы кантэйнера непасрэдна з зыходнага кода. Гэта альтэрнатыва выкарыстанню Docker для стварэння выявы кантэйнера звычайным спосабам: спачатку ствараецца тоўсты выкананы файл JAR, а затым пакуецца яго ў выяву кантэйнера, паказаўшы інструкцыі ў файле Docker.

Мы таксама разгледзелі аптымізацыю нашага кантэйнера, уключыўшы функцыю напластавання, якая здабывае залежнасці ў асобныя ўзроўні, якія кэшуюцца на хасце, а тонкі пласт прыкладання загружаецца падчас планавання ў механізмах выканання кантэйнера.

Вы можаце знайсці ўвесь зыходны код, выкарыстаны ў артыкуле на Github .

Даведнік каманд

Вось кароткі выклад каманд, якія мы выкарыстоўвалі ў гэтым артыкуле для хуткага азнаямлення.

Ачыстка кантэксту:

docker system prune -a

Стварэнне выявы кантэйнера з дапамогай файла Docker:

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

Збіраны выява кантэйнера з зыходнага кода (без Dockerfile):

mvn spring-boot:build-image

Прагляд пластоў залежнасцяў. Перад зборкай JAR-файла прыкладання пераканайцеся, што функцыя напластавання ўключана ў spring-boot-maven-plugin:

java -Djarmode=layertools -jar application.jar list

Выманне пластоў залежнасцяў. Перад зборкай JAR-файла прыкладання пераканайцеся, што функцыя напластавання ўключана ў spring-boot-maven-plugin:

 java -Djarmode=layertools -jar application.jar extract

Прагляд спісу вобразаў кантэйнераў

docker images

Прагляд злеў ўнутры выявы кантэйнера (пераканайцеся, што ўсталяваны інструмент для апускання):

dive <image ID or image tag>

Крыніца: habr.com