Контейнеризация приложения на основе “скриптового” языка проста. Добавьте исходные тексты, загрузите зависимости, и все готово. Можно было бы сказать, что они WYSIWYG .
FROM python:3 ADD requirements.txt . # 1 RUN pip install # 2 ADD script.py . # 3 CMD ["python", "./script.py"] # 4
- Скопируйте описание зависимостей
- Загрузите зависимости
- Скопируйте основной сценарий
- Запустите сценарий
С компилируемыми языками в целом и Java в частности все обстоит немного иначе. В этом посте я хотел бы перечислить некоторые альтернативы для достижения этой цели.
Пример приложения
Чтобы описать эти альтернативы, нам нужен пример приложения. Мы будем использовать Spring Boot, который предлагает конечную точку REST и хранит данные в Hazelcast. Он построен с использованием Maven, с существующей оболочкой .
Конечная точка REST работает следующим образом:
curl -X PUT http://localhost:8080/John
{"who":"John","when":64244336297226}
curl http://localhost:8080/
[{"who":"John","when":64244336297226}]
По сравнению с языками сценариев приложения Java имеют два основных отличия:
- Они требуют дополнительного шага компиляции, который преобразует исходный код Java в байт-код
- Модуль развертывания, как правило, является самоисполняемым кувшин
Наивный подход
В качестве первого шага можно было бы создать приложение вне Docker, а затем добавить JAR к изображению.
./mvnw clean package -DskipTests
# docker build -t spring-in-docker:0.5 . FROM adoptopenjdk/openjdk11:alpine-jre COPY target/spring-in-docker-0.5.jar spring-in-docker.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "spring-in-docker.jar"]
Следующим логическим шагом является создание приложения внутри Dockerfile
:
# docker build -t spring-in-docker:1.0 . FROM adoptopenjdk/openjdk11:alpine-slim COPY .mvn .mvn COPY mvnw . COPY pom.xml . COPY src src RUN ./mvnw package -DskipTests EXPOSE 8080 ENTRYPOINT ["java", "-jar", "target/spring-in-docker-1.0.jar"]
Этот способ имеет несколько недостатков:
- В итоговое изображение встроен JDK . Во-первых, это увеличивает размер конечного изображения по сравнению с JRE . Что еще более важно, это позволяет образу компилировать Java-код: это может стать серьезной дырой в безопасности при производстве.
- Версия в Maven POM должна быть вручную синхронизирована с версией изображения.
- Для JAR есть один слой OCI.
Исполняемый файл dive
выделяет последнюю точку:
Layers ├───────────────────────────────────────────────────────────────────────────── Size Command 5.6 MB FROM 31609b718dd2bed 14 MB apk add --no-cache tzdata --virtual .build-deps curl binutils zstd && GL 17 kB #(nop) COPY multi:5542ba69976bc682acd7b679c22d8a0277609ba9f5b611fd518f87f209 235 MB set -eux; apk add --no-cache --virtual .fetch-deps curl; ARCH="$(apk 56 kB #(nop) COPY dir:20c328136da94aa01b2b6fd62c88ef506a15b545aefeb1a1e13473572aee 10 kB #(nop) COPY file:08c603013feae81d794c29c4c1f489cc58a32bd593154cc5e40c6afa522 1.8 kB #(nop) COPY file:1bb01c4e5b60aae391d2efc563ead23a959701863adcf408540f33b7e40 54 kB #(nop) COPY dir:d2bd6e2521e5d990b16efb81ae8823e23ed2de826e833b331718b2211d6a 108 MB ./mvnw package -DskipTests │ ● Current Layer Contents ├───────────────────────────────────────────────────────── Permission UID:GID Size Filetree drwx------ 0:0 80 MB ├─⊕ root drwxr-xr-x 0:0 28 MB └── target drwxr-xr-x 0:0 5.8 kB ├─⊕ classes drwxr-xr-x 0:0 0 B ├─⊕ generated-sources drwxr-xr-x 0:0 64 B ├─⊕ maven-archiver drwxr-xr-x 0:0 364 B ├─⊕ maven-status -rw-r--r-- 0:0 28 MB ├── spring-in-docker-1.0.jar -rw-r--r-- 0:0 5.4 kB └── spring-in-docker-1.0.jar.original
На первый взгляд однослойность не кажется такой уж большой проблемой, но она имеет огромные последствия. Каждое изменение в исходном коде требует замены всего уровня OCI.
Многоступенчатые сборки на помощь
Многоступенчатые сборки Docker позволяют объединять в цепочку несколько этапов сборки, при этом последующие этапы цепочки повторно используют артефакты, созданные на предыдущих этапах. Таким образом, мы можем использовать JDK для компиляции и JRE для выполнения:
# docker build -t spring-in-docker:1.1 . FROM adoptopenjdk/openjdk11:alpine-slim as build # 1 COPY .mvn .mvn COPY mvnw . COPY pom.xml . COPY src src RUN ./mvnw package -DskipTests FROM adoptopenjdk/openjdk11:alpine-jre # 2 COPY --from=build target/spring-in-docker-1.1.jar spring-in-docker.jar # 3 EXPOSE 8080 ENTRYPOINT ["java", "-jar", "spring-in-docker.jar"]
- На этапе сборки используется JDK
- Шаг выполнения использует JRE
- Скопируйте JAR, созданный на предыдущем шаге
build
Многоступенчатые сборки создают по одному изображению на каждом шаге. Все изображения, кроме последних, не помечены.
Чтобы улучшить многоуровневость, можно отделить загрузку зависимостей от компиляции и упаковки.
# docker build -t spring-in-docker:1.2 . FROM adoptopenjdk/openjdk11:alpine-slim as build COPY .mvn .mvn # 1 COPY mvnw . # 1 COPY pom.xml . # 1 RUN ./mvnw dependency:go-offline # 2 COPY src src # 3 RUN ./mvnw package -DskipTests FROM adoptopenjdk/openjdk11:alpine-jre COPY --from=build target/spring-in-docker-1.2.jar spring-in-docker.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "spring-in-docker.jar"]
- Скопируйте все необходимые файлы для загрузки зависимостей
- Загрузите зависимости – они будут частью выделенного слоя
- Теперь скопируйте исходные файлы; это добавит еще один слой
Давайте погрузимся
в построение
образа:
Layers ├──────────────────────────────────────────────────────────────────────────── Size Command 5.6 MB FROM 31609b718dd2bed 14 MB apk add --no-cache tzdata --virtual .build-deps curl binutils zstd && GL 17 kB #(nop) COPY multi:5542ba69976bc682acd7b679c22d8a0277609ba9f5b611fd518f87f209 235 MB set -eux; apk add --no-cache --virtual .fetch-deps curl; ARCH="$(apk 56 kB #(nop) COPY dir:20c328136da94aa01b2b6fd62c88ef506a15b545aefeb1a1e13473572aee 10 kB #(nop) COPY file:08c603013feae81d794c29c4c1f489cc58a32bd593154cc5e40c6afa522 1.8 kB #(nop) COPY file:f191db2f3a7fe2e434025c321ad8106112373b1aa0fa99f1a76c884bf61 100 MB ./mvnw dependency:go-offline 54 kB #(nop) COPY dir:d2bd6e2521e5d990b16efb81ae8823e23ed2de826e833b331718b2211d6a # 1 28 MB ./mvnw package -DskipTests
- Уровень зависимостей
С последним Dockerfile
нам удалось решить две проблемы: проблему безопасности, исходящую от JDK, и проблему многоуровневости. Тем не менее, нам нужно установить версию вручную, когда мы создаем изображение. Нет никакой синхронизации между стихами и изображением.
Более того, многоступенчатые сборки несовместимы с skaffold . Если вы привыкли автоматически запускать развертывания в (локальном) кластере Kubernetes при изменении исходного кода, забудьте о них.
Кливер
Jib – это плагин Maven (также доступный для Gradle), предоставляемый Google, который элегантно решает вышеуказанные проблемы.
Концепция, лежащая в основе Jib, проста, но умна. Java позволяет запускать JARS, но также и стандартные классы Java. За пределами мира контейнеров JAR является хорошим средством развертывания. Однако в мире контейнеров JAR – это просто дополнительная оболочка, поскольку контейнер является единицей развертывания.
Плагины Jib подключаются к системе сборки для компиляции исходных текстов Java, копирования соответствующих ресурсов в слои и создания образа, который запускает приложение в “развернутом” (не JAR) формате.
com.google.cloud.tools jib-maven-plugin 2.5.2 ${project.artifactId}:${project.version}
- Автоматическая синхронизация версии изображения с версией POM
Задание предлагает две цели: build
для загрузки образа в репозиторий Docker и docker Build
для сборки в Docker daemon . Давайте создадим изображение локально.
mvn compile com.google.cloud.tools:jib-maven-plugin:2.6.0
dive
выводит следующее:
Layers ├──────────────────────────────────────────────────────────────────────────── Size Command 1.8 MB FROM 7cfeac17984f4f4 15 MB bazel build ... 1.9 MB bazel build ... 8.4 MB bazel build ... 170 MB bazel build ... 16 MB jib-maven-plugin:2.5.2 12 MB jib-maven-plugin:2.5.2 1 B jib-maven-plugin:2.5.2 5.8 kB jib-maven-plugin:2.5.2
Кливер создает 4 слоя, от самого старого до самого последнего:
- Зависимости
- Зависимости моментальных снимков
- Ресурсы
- Скомпилированный код
Второй уровень обрабатывает случай SNAPSHOT
зависимости: это зависимости, содержимое которых может изменяться, несмотря на тот же номер версии. Это может иметь место во время разработки. Вы не должны развертывать зависимости моментальных снимков в рабочей среде.
Командная строка контейнера Docker похожа на:
java -cp /app/resources:/app/classes:/app/libs/* ch.frankel.blog.springindocker.SpringInDockerApplication
Кроме того, изменение родительского изображения созданного изображения является простым:
com.google.cloud.tools jib-maven-plugin 2.5.2 adoptopenjdk/openjdk11:alpine-jre ${project.artifactId}:${project.version}
- Измените родительское изображение на Alpine
Кливер, по-видимому, является лучшей альтернативой. Но давайте продолжим изучать другие варианты.
Пружинный ботинок многослойная БАНКА
С версией 2.3 Spring Boot позволяет создавать JAR с выделенной структурой папок. Вы можете сопоставить эти папки со слоями в Dockerfile
. По умолчанию это:
- Зависимости
- Зависимости моментальных снимков
- Время выполнения весенней загрузки
- Ресурсы и скомпилированный код
Можно настроить эти папки с помощью определенного layers.xml
файл.
Вот файл многоступенчатой сборки, в котором показано, как создать приложение Spring Boot со слоями по умолчанию:
FROM adoptopenjdk/openjdk11:alpine-slim as builder COPY .mvn .mvn COPY mvnw . COPY pom.xml . RUN ./mvnw dependency:go-offline COPY src src RUN ./mvnw package -DskipTests # 1 FROM adoptopenjdk/openjdk11:alpine-jre as layers COPY --from=builder target/spring-in-docker-3.0.jar spring-in-docker.jar RUN java -Djarmode=layertools -jar spring-in-docker.jar extract # 2 FROM adoptopenjdk/openjdk11:alpine-jre COPY --from=layers dependencies/ . # 3 COPY --from=layers snapshot-dependencies/ . # 3 COPY --from=layers spring-boot-loader/ . # 3 COPY --from=layers application/ . # 3 ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
- Создайте стандартный самоисполняемый JAR
- Извлеките структуру папок
- Скопируйте каждую папку в слой
Этот подход имеет все недостатки Dockerfile
, описанные выше: нет интеграции с scaffold и нет синхронизации с версией POM.
Кроме того, вы должны быть осторожны, чтобы создавать слои в правильном порядке.
Встроенные в Облако пакеты сборки
Встроенные в облако пакеты сборки исходят из пакетов сборки Heroku. Heroku – одна из первых платформ облачного хостинга. Он также предлагает репозитории Git. Чтобы развернуть на Heroku, вам просто нужно отправить исходный код в удаленный репозиторий Heroku.
Платформа понимает, как создать исполняемый файл из исходных текстов. Он проверяет наличие некоторых файлов в качестве подсказок. Например, если репозиторий содержит pom.xml
файл в корне, он активирует Maven buildpack; если он содержит package.json
, он активирует Node.js один; и т.д.
CNBs – это обновление пакетов Heroku buildpacks, предназначенных для контейнеров OCI. Heroku и VMware Tanzu – компания, стоящая за Spring Boot, возглавляют проект. Он размещен по адресу CNCF .
Чтобы использовать buildpack, просто вызовите команду pack
со ссылкой на builder и тегом изображения для сборки. Он создаст приложение и унаследует родительский образ по умолчанию. Например, вот командная строка для создания примера приложения:
pack build --builder gcr.io/paketo-buildpacks/builder:base-platform-api-0.3 spring-in-docker:4.0
Он запускает пакеты сборки, которые применяются к проекту:
===> DETECTING [detector] 6 of 17 buildpacks participating [detector] paketo-buildpacks/bellsoft-liberica 4.0.0 [detector] paketo-buildpacks/maven 3.1.1 [detector] paketo-buildpacks/executable-jar 3.1.1 [detector] paketo-buildpacks/apache-tomcat 2.3.0 [detector] paketo-buildpacks/dist-zip 2.2.0 [detector] paketo-buildpacks/spring-boot 3.2.1 [builder] Paketo BellSoft Liberica Buildpack 4.0.0 [builder] https://github.com/paketo-buildpacks/bellsoft-liberica [builder] Build Configuration: [builder] $BP_JVM_VERSION 11.* the Java version # 1 [builder] Launch Configuration: [builder] $BPL_JVM_HEAD_ROOM 0 the headroom in memory calculation [builder] $BPL_JVM_LOADED_CLASS_COUNT 35% of classes the number of loaded classes in memory calculation [builder] $BPL_JVM_THREAD_COUNT 250 the number of threads in memory calculation [builder] $JAVA_TOOL_OPTIONS the JVM launch flags
- Определите версию JVM. Buildpack загружает правильный JDK.
Я столкнулся с несколькими недостатками:
-
base-platform-api-0.3
не является |/неизменяемым . Сборка работала ранее; на момент написания этой статьи она завершилась ошибкой Go. Загрузка JDK происходит при каждом запуске, несмотря на то, что он был загружен в предыдущих запусках. - Мне было нелегко изменить родительский образ.
Кроме того, как и в случае с Dockerfile
, вам необходимо вручную пометить версию.
Плагин Spring Boot plugin
С Spring Boot нет необходимости вызывать внешнюю команду. Плагин предлагает build-image
target, который выполняет то же самое, что и pack
, вызывая соответствующий buildpack.
Давайте запустим его:
./mvnw spring-boot:build-image
[INFO] > Running creator [INFO] [creator] ===> DETECTING [INFO] [creator] 5 of 17 buildpacks participating [INFO] [creator] paketo-buildpacks/bellsoft-liberica 4.0.0 [INFO] [creator] paketo-buildpacks/executable-jar 3.1.1 [INFO] [creator] paketo-buildpacks/apache-tomcat 2.3.0 [INFO] [creator] paketo-buildpacks/dist-zip 2.2.0 [INFO] [creator] paketo-buildpacks/spring-boot 3.2.1
Этот вариант имеет массу преимуществ:
- Версия изображения автоматически считывается из версии POM.
- Повторные сборки без изменений выполняются быстро.
- С параметром конфигурации сборки конечный образ имеет 4 слоя загрузки Spring, описанные выше.
Более того, некоторые пакеты сборки позволяют легко настраивать процесс сборки. Например, чтобы сделать конечный артефакт собственным исполняемым файлом, достаточно просто добавить необходимую переменную среды:
org.springframework.boot spring-boot-maven-plugin true ${project.artifactId}:${project.version} true
Самая большая проблема заключается в том, что изменить родительский образ непросто.
Резюмировать
Вот окончательные изображения с их соответствующим размером. Я также пометил изображения, используемые в многоступенчатых сборках.
REPOSITORY TAG IMAGE ID CREATED SIZE spring-in-docker 0.5 ca380d4677f9 3 days ago 177MB spring-in-docker 1.0 f16667a974f4 3 days ago 363MB spring-in-docker/build 1.1 2f2a59f49486 3 days ago 363MB spring-in-docker 1.1 45ae57fab5ae 3 days ago 177MB spring-in-docker/build 1.2 b94b6a80a437 3 days ago 383MB spring-in-docker 1.2 cbddb2300b1a 3 days ago 177MB spring-in-docker 2.0 fb7d8501623a 50 years ago 225MB spring-in-docker 2.1 c3b60a214da2 50 years ago 177MB spring-in-docker/build 3.0-b 1eea78545af2 2 days ago 206MB spring-in-docker/build 3.0-a 52c180e9f3d1 2 days ago 383MB spring-in-docker 3.0 9e2240a4fc00 2 days ago 177MB spring-in-docker 5.0 4cbda769276f 40 years ago 264MB spring-in-docker 5.5 4ac2d37253ee 40 years ago 184MB
Многоступенчатый | Инструкция | Нет | |
Кливер | Да | Конфигурация | |
Buildpack | Детская коляска | Да | Нет |
Плагин Spring Boot Maven | Менее гибкая, чем многоступенчатые сборки, Мощная параметризация в зависимости от пакета сборки | Конфигурация | Да |
Чтобы идти дальше:
- Используйте многоступенчатые сборки
- Начало работы с облачными сборными пакетами
- Быстрый старт стрелы
- Упаковка изображений OCI с пружинным ботинком
Первоначально опубликовано на Фанат Java 11 октября th , 2020
Оригинал: “https://dev.to/nfrankel/a-hitchhiker-s-guide-to-containerizing-spring-boot-java-apps-3mb8”