Статьи

Докер и JVM

Несмотря на то, что Docker был в 2016 году, он все еще актуален сегодня. Это основа Kubernetes , самой популярной платформы Orchestration, которая стала готовым решением для облачного развертывания .

Docker — это стандартное решение по умолчанию для контейнерных приложений / (микро) сервисов. Если вы запускаете Java-приложение, вам следует остерегаться некоторых хитростей и хитростей. Если вы этого не сделаете, этот пост все равно должен помочь вам.

Почему я должен поместить Java в контейнер в любом случае?

Это хороший вопрос. Разве Java не была создана с лозунгом « Пиши один раз, беги куда угодно »? Хотя это и верно, это утверждение охватывает только двоичные файлы Java.

Ваш байт-код (файл ieJar) будет нормально работать на всех возможных версиях JVM. Однако как насчет драйверов базы данных? Доступ к файловой системе? Сеть? Доступна энтропия? Стороннее приложение, на которое вы полагаетесь? Все эти вещи будут отличаться в разных операционных системах.

Зачастую ваше приложение требует точного баланса между вашими двоичными файлами Java и любыми из этих сторонних зависимостей . Если вы когда-либо поддерживали приложения Java, установленные вашими клиентами, вы поймете, что я имею в виду.

Сначала появились виртуальные машины

До появления контейнеров универсальным решением было использование виртуальных машин . Создайте новый пустой виртуальный компьютер с выбранной вами операционной системой, установите все сторонние зависимости, скопируйте двоичные файлы Java, сделайте снимок и, наконец, отправьте его.

С виртуальной машиной вы уверены, что то, что вы отправляете, работает точно так, как предполагается, а также постоянно каждый раз. Там нет места для проблем конфигурации среды.

Они также обеспечивают сильную инкапсуляцию . Если вы запустите свое приложение в облаке, все виртуальные машины будут сильно изолированы. Существует очень ограниченное пространство для повреждения между виртуальными машинами, работающими на одном и том же оборудовании.

Но всегда есть но, они тяжелые . Если вы обнаружили ошибку в своем приложении и вам пришлось изменить одну строку кода, вы должны перекомпилировать файл Jar, переустановить виртуальную машину и отправить все это. Одна строка кода превращается в несколько файлов в ГБ для загрузки в облако или загрузки на ваших клиентах. Файлы операционной системы тяжелые , вероятно, намного тяжелее, чем ваши двоичные файлы Java, и вы должны отправлять их каждый раз, даже если они действительно не меняются.

Контейнеры на помощь

Как объяснялось выше, у VM есть своя собственная копия ОС, тогда как контейнеры сделаны меньше и содержат только то, что вы хотите отправить :

Докер JVM

В контейнере ОС (точнее, это Ядро, которое используется совместно, вы можете создавать образы из разных дистрибутивов, таких как Ubuntu, Debian, Alpine и т. Д.) Предоставляется движком (например, Docker), и вы не не нужно отправлять с вашим заявлением.

С помощью Docker вы отправляете изображения , построенные слоями . Инструкции по созданию изображения помещаются в Dockerfile .

Концептуально Dockerfile может быть что-то вроде:

  1. Начните с чистого дистрибутива Ubuntu
  2. Установить Java
  3. Установить зависимость A
  4. Установить зависимость B
  5. Копировать файл jar

(фактический пример Dockerfile доступен ниже)

Каждая инструкция в Dockerfile создает неизменный слой . Это умная и отличная оптимизация. Если вы сохраняете инструкцию, которая копирует ваши двоичные файлы Java в конце (и вы должны это сделать), вам нужно будет только изменить последний слой, когда вы сделаете изменение кода. Это означает, что если вы отправляете изменения для конечных пользователей, вам нужно только загрузить последний слой , предыдущие неизмененные слои кэшируются; Конечным пользователям нужно будет только загрузить измененные слои из нижней части изображения. В Docker изменение в одной строке кода означает загрузку / выгрузку всего лишь нескольких МБ (хотя, если бы это была виртуальная машина, изменения были бы в ГБ, а не в МБ).

Предупреждение

Помните, что контейнеры НЕ обеспечивают такой же уровень инкапсуляции, как виртуальные машины. Контейнеры Docker — это просто процессы, запущенные на хост-компьютере. Существуют функции ядра Linux (а именно пространства имен и группы управления ), которые помогают снизить уровень доступа к контейнеру Docker, но это далеко не так гибко, как изоляция виртуальных машин . Это может или не может быть проблемой для вашего бизнеса, но вы должны знать.

Java + Docker = ❤️ ???

Прежде чем мы рассмотрим, как упаковать Jar-файл в Docker-контейнер, нам необходимо рассмотреть некоторые важные ограничения. Java 1.0 была выпущена в 1996 году, тогда как контейнеры Linux появились примерно в 2008 году . Из этого следует, что JVM не должна была бы размещать контейнеры Linux.

Одной из ключевых особенностей Docker является возможность ограничивать использование памяти и процессора контейнером . Это одна из главных причин, почему экономически интересно запускать много контейнеров Docker в облаке. Решения для оркестровки, такие как Kubernetes (k8s), будут пытаться эффективно «упаковывать» контейнеры на нескольких узлах . Здесь «pack» относится к памяти и процессору ( посмотрите, как k8s играет в тетрис для вас ). Если вы дадите разумные границы для памяти и процессора для ваших контейнеров Docker, K8s сможет эффективно расположить их на нескольких узлах .

К сожалению, это именно то, где Java работает недостаточно. Давайте использовать пример, чтобы понять проблему.

Представьте, что у вас есть узел с 32 ГБ памяти и вы хотите запустить приложение Java с ограничением в 1 ГБ . Помните, что контейнеры Docker — это не более, чем прославленный процесс на хост-компьютере . Если вы не укажете параметр -Xmx, JVM будет использовать конфигурацию по умолчанию:

    1. JVM проверит общий объем доступной памяти. Поскольку JVM не знает о контейнере Linux (в частности, о группе управления, которая ограничивает память), она считает, что она работает на хост-машине и имеет доступ ко всем 32 ГБ доступной памяти.
  1. По умолчанию JVM будет использовать MaxMemory / 4, который в этом случае составляет 8 ГБ (32 ГБ / 4).
  2. Когда размер кучи увеличивается и выходит за пределы 1 ГБ, контейнер будет уничтожен Docker .

Раньше пользователи Java на Docker весело проводили время, пытаясь понять, почему их JVM продолжала падать без каких-либо сообщений об ошибках. Чтобы понять, что произошло, вам нужно проверить мертвый контейнер Docker, и в этом случае вы увидите сообщение « OOM kill» (OutOf Memory).

Конечно, очевидным решением является исправление размера кучи JVM с помощью параметра Xmx, но это означает, что вам нужно управлять памятью дважды, один раз в Docker и один раз в JVM. Каждый раз, когда вы хотите внести изменения, вы должны сделать это дважды. Не идеально.

Первый обходной путь для этой проблемы был выпущен с Java 8u131 и Java 9 . Я говорю обходной путь, потому что вам пришлось использовать любимые параметры -XX: + UnlockExperimentalVMOptions . Если вы работаете в Финансовых службах, я уверен, что вы любите объяснять своим клиентам или своему боссу, что это разумное решение.

Затем вы должны были использовать -XX: + UseCGroupMemoryLimitForHeap, который сообщал бы JVM проверять ограничение памяти группы управления для установки максимального размера кучи.

Наконец, вам нужно будет использовать -XX: MaxRAMFraction, чтобы определить часть максимальной памяти, которая может быть выделена для JVM. К сожалению, этот параметр является натуральным числом. Например, если ограничение памяти Docker установлено в 1 ГБ, у вас будет следующее:

  • -XX: MaxRAMFraction = 1 Максимальный размер кучи будет 1 ГБ. Это не очень хорошо, так как вы не можете дать JVM 100% разрешенной памяти. В этом контейнере могут работать другие компоненты
  • -XX: MaxRAMFraction = 2 Максимальный размер кучи будет 500 МБ. Это лучше, но сейчас кажется, что мы тратим много памяти.
  • -XX: MaxRAMFraction = 3 Максимальный размер кучи будет 250 МБ. Вы платите за 1 ГБ памяти, а ваше Java-приложение использует 250 МБ. Это немного смешно
  • -XX: MaxRAMFraction = 4 Даже не ходи туда.

По сути, флаги JVM для управления максимальной доступной оперативной памятью были заданы в виде доли, а не процента, что затрудняло установку значений, которые позволили бы эффективно использовать доступную (разрешенную) оперативную память.

Мы сосредоточились на памяти, но то же самое относится и к процессору. Вам нужно будет использовать такие параметры, как

-Djava.util.concurrent.ForkJoinPool.common.parallelism = 2

контролировать размер различных пулов потоков в ваших приложениях. 2 означает два потока (максимальное значение будет ограничено количеством гиперпотоков, доступных на вашем хост-компьютере).

Подводя итог, с Java 8u131 и Java 9 у вас будет что-то вроде:

1
2
3
4
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">-XX:+UnlockExperimentalVMOptions</span> -XX: + UnlockExperimentalVMOptions</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">-XX:+UseCGroupMemoryLimitForHeap</span> -XX: + UseCGroupMemoryLimitForHeap</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">-XX:MaxRAMFraction=2</span> -XX: MaxRAMFraction = 2</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">-Djava.util.concurrent.ForkJoinPool.common.parallelism=2</span> -Djava.util.concurrent.ForkJoinPool.common.parallelism = 2</span>

Докер JVM

К счастью, Java 10 пришла на помощь. Во-первых, вам не нужно использовать флаг страшной экспериментальной функции. Если вы запустите приложение Java в контейнере Linux, JVM автоматически определит ограничение памяти группы управления. В противном случае вам просто нужно добавить -XX: -UseContainerSupport .

Затем вы можете управлять памятью с помощью -XX: InitialRAMPercentage , -XX: MaxRAMPercentage и -XX: MinRAMPercentage . Например с

  • Ограничение памяти докера: 1 ГБ
  • -XX: InitialRAMPercentage = 50
  • -XX: MaxRAMPercentage = 70

Ваша JVM начнется с размера кучи 500 МБ (50%) и увеличится до 700 МБ (70%), в контейнере максимальная доступная память составляет 1 ГБ.

Докер JVM

Java2Docker

Есть разные способы превратить Java-приложение в образ Docker.

Вы можете использовать плагин Maven ( fabric8 или Spotify ) или плагин Graddle . Но, возможно, самый простой и более семантический подход — это написать Dockerfile самостоятельно. Этот подход также позволяет использовать Jlink JDK, представленный в JDK9. Используя jlink, вы можете создать собственный двоичный файл JDK, содержащий только модули, необходимые для вашего приложения.

Давайте посмотрим на пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">FROM adoptopenjdk/openjdk11 AS jdkBuilder</span> ОТ Усыновления / openjdk11 AS jdkBuilder</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">RUN $JAVA_HOME/bin/jlink \</span> RUN $ JAVA_HOME / bin / jlink \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--module-path /opt/jdk/jmods \</span> --module-path / opt / jdk / jmods \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--verbose \</span> --подробный \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--**add**-modules java.base \</span> - ** добавить ** - модули java.base \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--output /opt/jdk-minimal \</span> --output / opt / jdk-minimal \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--compress 2 \</span> --компресс 2 \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--no-header-files</span> --no-заголовок-файлы</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">FROM debian:9-slim</span> ОТ Debian: 9-тонкий</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">COPY --from=jdkBuilder /opt/jdk-minimal /opt/jdk-minimal</span> COPY --from = jdkBuilder / opt / jdk-minimal / opt / jdk-minimal</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ENV JAVA_HOME=/opt/jdk-minimal</span> ENV JAVA_HOME = / opt / jdk-minimal</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">COPY target/*.jar /opt/</span> COPY target / *. Jar / opt /</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">CMD $JAVA_HOME/bin/java $JAVA_OPTS -jar /opt/*.jar</span> CMD $ JAVA_HOME / bin / java $ JAVA_OPTS -jar /opt/*.jar</span>

Давайте пройдемся по этой строке построчно

1
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">FROM adoptopenjdk/openjdk11 AS jdkBuilder</span> ОТ Усыновления / openjdk11 AS jdkBuilder</span>

Мы начнем с существующего образа Docker, который содержит полный JDK 11 . Здесь мы использовали сборку, предоставленную AdoptOpenJDK, но вы можете использовать любой другой дистрибутив (например, недавно анонсированный AWS Corretto ). AS jdkBuilderinstruction — это специальная инструкция, которая сообщает Docker, что мы хотим запустить «стадию» под названием jdkBuilder. Это будет полезно позже.

1
2
3
4
5
6
7
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">RUN $JAVA_HOME/bin/jlink \</span> RUN $ JAVA_HOME / bin / jlink \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--module-path /opt/jdk/jmods \</span> --module-path / opt / jdk / jmods \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--verbose \</span> --подробный \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--add-modules java.base \</span> --add-modules java.base \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--output /opt/jdk-minimal \</span> --output / opt / jdk-minimal \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--compress 2 \</span> --компресс 2 \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--no-header-files</span> --no-заголовок-файлы</span>

Мы запускаем jlink для создания наших пользовательских двоичных файлов JDK. В этом примере мы используем только модуль java.base, которого может быть недостаточно для запуска вашего приложения. Если вы все еще пишете старое приложение типа classpath, вам придется вручную добавить все модули, необходимые для вашего приложения. Например, для одного из моих приложений Spring я использую следующие модули:

1
2
3
4
5
6
7
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--add-modules java.base,java.logging,java.xml,</span> --add-modules java.base, java.logging, java.xml,</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">java.xml.bind,java.sql,jdk.unsupported,</span> java.xml.bind, java.sql, jdk.unsupported,</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">java.naming,java.desktop,java.management,</span> java.naming, java.desktop, java.management,</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">java.security.jgss,java.security.sasl,</span> java.security.jgss, java.security.sasl,</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">jdk.crypto.cryptoki,jdk.crypto.ec,</span> jdk.crypto.cryptoki, jdk.crypto.ec,</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">java.instrument,jdk.management.agent,</span> java.instrument, jdk.management.agent,</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">jdk.localedata</span> jdk.localedata</span>

Если вы пишете Java-приложение с модулями, вы можете позволить jlink определить, какие модули требуются . Для этого вам нужно добавить свои модули к аргументу module-path (список путей, разделенных «:» в MacOS / Linux и «;» в Windows). Но поскольку этот процесс происходит в образе Docker, вам необходимо перенести его с помощью команды COPY . Затем вам нужно только добавить свой собственный модуль в команду -add-modules, и необходимые модули будут добавлены автоматически. Так что это будет что-то вроде:

1
2
3
4
5
6
7
8
9
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">FROM adoptopenjdk/openjdk11 AS jdkBuilder</span> ОТ Усыновления / openjdk11 AS jdkBuilder</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">COPY path/to/module-info.class /opt/myModules</span> Путь COPY / к / module-info.class / opt / myModules</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">RUN $JAVA_HOME/bin/jlink \</span> RUN $ JAVA_HOME / bin / jlink \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--module-path /opt/jdk/jmods:/opt/myModules \</span> --module-path / opt / jdk / jmods: / opt / myModules \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--verbose \</span> --подробный \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--**add**-modules my-module \</span> - ** добавить ** - модули my-module \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--output /opt/jdk-minimal \</span> --output / opt / jdk-minimal \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--compress 2 \</span> --компресс 2 \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">--no-header-files</span> --no-заголовок-файлы</span>
1
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">FROM debian:9-slim</span> ОТ Debian: 9-тонкий</span>

Поскольку мы используем другое ключевое слово FROM , Docker откажется от всего, что мы уже сделали, и создаст новый образ . Здесь мы начнем с образа Docker, на котором установлен Debian 9 и установлены минимальные зависимости (тонкий тег). Этот образ Debian даже не имеет Java, так что это то, что мы установим дальше.

1
2
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">COPY --from=jdkBuilder /opt/jdk-minimal /opt/jdk-minimal</span> COPY --from = jdkBuilder / opt / jdk-minimal / opt / jdk-minimal</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ENV JAVA_HOME=/opt/jdk-minimal</span> ENV JAVA_HOME = / opt / jdk-minimal</span>

Это где сценическое имя становится важным. Мы можем сказать Docker скопировать определенную папку с более ранней стадии , в этом случае со стадии jdkBuilder . Это интересно, потому что на этом первом этапе мы могли бы загрузить много промежуточных библиотек, которые в конце не нужны.

В данном случае мы начали с полного дистрибутива JDK 11, который весит 200+ МБ, но нам нужно только скопировать наши пользовательские двоичные файлы JDK, размер которых обычно составляет ~ 50/60 МБ; в зависимости от того, какие модули JDK вам пришлось импортировать. Затем мы устанавливаем переменную среды JAVA_HOME, чтобы она указывала на наши новые встроенные двоичные файлы JDK.

Эта техника называется многоэтапной сборкой Docker и может быть очень полезной. Это позволяет эффективно использовать созданные слои и помогает создавать более тонкие изображения докеров . Если вы просмотрели типичный Dockerfile, вы могли увидеть инструкции, подобные приведенным ниже:

1
2
3
4
5
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">rm -rf /var/lib/apt/lists/* \</span> rm -rf / var / lib / apt / lists / * \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">apt-get clean && apt-get update && apt-get upgrade -y \</span> apt-get clean && apt-get update && apt-get upgrade -y \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">apt-get install -y --no-install-recommends curl ca-certificates \</span> apt-get install -y --no-install-рекомендует curl ca-сертификаты \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">rm -rf /var/lib/apt/lists/* \</span> rm -rf / var / lib / apt / lists / * \</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">...</span> ...</span>

Этот метод, позволяющий сгруппировать как можно больше команд в одной инструкции Dockerfile, был полезен для минимизации количества слоев в изображении. Большое количество слоев может повлиять на производительность во время выполнения. Однако у этого подхода есть и недостаток. Наличие одного слоя на инструкцию означает создание большого количества кэшированных контрольных точек.

Если вы допустили ошибку в своем Dockerfile по инструкции 15, Docker не должен повторно запускать предыдущие 14, он может просто восстановить их из кэша. Если одним из ваших шагов является загрузка файла размером 400 МБ с кэшем инструкций, это сэкономит вам много времени.

Хорошей новостью является то, что многоэтапный подход сделал этот подход устаревшим ! Вы можете создать первый «выбрасывающий» этап, на котором вы можете создать столько слоев, сколько захотите . Затем вы создаете новый «финальный» этап, на котором вы копируете только необходимый файл из первого этапа «выбрасывания».

Многие слои с первого этапа будут полностью проигнорированы!

1
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">COPY target/*.jar /opt/</span> COPY target / *. Jar / opt /</span>

Теперь, когда у нас установлена ​​Java, нам нужно скопировать ваше приложение. Это скопирует любой файл jar из целевого каталога и поместит их в папку opt

1
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">CMD $JAVA_HOME/bin/java $JAVA_OPTS -jar /opt/*.jar</span> CMD $ JAVA_HOME / bin / java $ JAVA_OPTS -jar /opt/*.jar</span>

Наконец, это сообщает Docker, какую команду выполнять при запуске контейнера. Здесь мы просто запускаем java и допускаем передачу переменной JAVA_OPTS во время выполнения.

Обратите внимание, что действительно важно оставить инструкцию, которая копирует ваше приложение в конце . Таким образом, если вам нужно изменить код, вы аннулируете только последний слой, и вам не нужно будет повторять предыдущие шаги.

Вывод

Вот и все, ребята, мы прошли через ловушки запуска Java на Docker и как написать универсальный Dockerfile, который будет поставлять только то, что нужно вашему приложению!

Если вы хотите узнать о том, как запускать Docker-контейнеры в облаке, проверьте мою виртуальную среду Cloud Ready JVM с Kubernetes .

Спасибо, Мани Саркар ( @ theNeomatrix369 ) за помощь в написании этой статьи!

Ресурсы

Опубликовано на Java Code Geeks с разрешения Cesar Tron-Lozai, партнера нашей программы JCG. Смотрите оригинальную статью здесь: Docker и JVM

Мнения, высказанные участниками Java Code Geeks, являются их собственными.