Разрешить контейнеру использовать OpenJDK и библиотеки существующего контейнера - PullRequest
20 голосов
/ 28 мая 2020

Я провожу несколько экспериментов над своей диссертацией, связанной с проблемой холодного запуска, которая возникает с контейнерами. Мое тестовое приложение - это приложение для весенней загрузки, созданное на основе образа openjdk. Первое, что я хочу попытаться решить проблему холодного запуска, это следующее:

Подготовьте контейнер, в нем находится openjdk и библиотеки, которые использует приложение springboot. Я запускаю свой другой контейнер, используя ip c и networknamespace уже существующего контейнера, а затем могу использовать openjdk и библиотеки этого контейнера для запуска файла jar.

Я не совсем уверен, как этого добиться? Могу ли я добиться этого с помощью томов, или мне следует искать совершенно другой подход?

С другой стороны, если я хочу, чтобы запускались x контейнеров, я проверю, что запущено x уже существующих контейнеров. Это сделано для того, чтобы каждый контейнер имел свой собственный c контейнер для работы с библиотеками. Будет ли это нормально?

Короче говоря, я могу ускорить загрузку приложения Spring с помощью второго контейнера, подключенного через ipc / net; было бы полезно для моей проблемы.

Ответы [ 4 ]

5 голосов
/ 31 мая 2020

Spring boot - это чисто среда выполнения.

Если я правильно понял ваш вопрос, вы описываете следующую ситуацию:

Итак, допустим, у вас есть контейнер A с JDK и несколько банок. Это само по себе не означает, что у вас есть работающий процесс. Так что это больше похоже на том с файлами, готовыми к повторному использованию (или, может быть, на слой в виде docker изображений).

Вдобавок у вас есть еще один контейнер B с приложением весенней загрузки, которое нужно как-то запустить (возможно, с открытым jdk из контейнера A или его выделенного JDK).

Теперь, что именно хотелось бы "разогнаться"? Размер образа (например, меньшее изображение означает более быстрое развертывание в конвейере CI / CD)? Время запуска приложения весенней загрузки (временной интервал между моментом создания JVM до запуска и запуска приложения загрузки Spring)? Или, может быть, вы пытаетесь загрузить меньше классов во время выполнения?

Способы решения возникших проблем различны. Но в целом, я думаю, вам может понадобиться проверить интеграцию Graal VM, которая, помимо прочего, может создавать собственные образы и ускорять время запуска. Это довольно новый материал, я сам еще не пробовал. Я считаю, что его работа в процессе разработки, и весна приложит усилия, чтобы продвинуть это вперед (это всего лишь мои предположения, так что воспринимайте это с недоверием). * эта статья

Однако я сомневаюсь, что это как-то связано с вашим исследованием, как вы его описали.

Обновление 1

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

Итак, у нас в первую очередь есть приложение для весенней загрузки.

По умолчанию это JAR и его рекомендация Pivotal, есть также вариант WARs (как Jo sh Long, их защитник разработчика говорит: «Сделайте JAR не WAR»).

Этой весной Загрузочное приложение обычно включает в себя какой-либо веб-сервер - Tomcat для традиционных приложений Spring Web MVC по умолчанию, но вы можете переключить его на Jetty или поднять. Если вы используете «реактивное приложение» (Spring WebFlux поддерживается с весенней загрузки 2), по умолчанию вы выбираете Netty.

Одно замечание: не все приложения с весенней загрузкой должны включать какой-либо встроенный веб-сервер, но я отложу этот тонкий момент, поскольку вы, похоже, нацеливаетесь на случай с веб-серверами (вы упоминаете tomcat , более быстрая возможность обслуживать запросы и т. д. c, отсюда и мое предположение).

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

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

Хорошо, теперь пришло время загрузить внутреннюю часть tomcat классы. На современных серверах это опять же может занять пару секунд. Netty кажется быстрее, но вы можете попробовать загрузить стандартный дистрибутив tomcat и запустить его на своем компьютере или создать образец приложения без весенней загрузки, но со встроенным Tomcat, чтобы понять, о чем я говорю.

Пока все хорошо, наше приложение не работает. Как я сказал в начале, весенняя загрузка - это чисто среда выполнения. Итак, должны быть загружены классы самой загрузки spring / spring, а затем классы самого приложения. Если приложение использует некоторые библиотеки - они также будут загружены, а иногда даже пользовательский код будет выполняться во время запуска приложения: Hibernate может проверять схему и / или сканировать определения схемы db и даже обновлять базовую схему, Flyway / Liquidbase может выполнять схему миграции и тому подобное, Swagger может сканировать контроллеры и генерировать документацию и тому подобное.

Теперь этот процесс в «реальной жизни» может занять минуту и ​​даже больше, но это не из-за самой весенней загрузки, а скорее из bean-компонентов, созданных в приложении, у которых есть некоторый код в «конструкторе» / «пост-конструкции» - то, что происходит во время инициализации контекста приложения весенней загрузки. Еще одно замечание: я не буду вдаваться в подробности процесса запуска приложения весенней загрузки, весенняя загрузка - это чрезвычайно мощный фреймворк, в котором много вещей происходит под капотом, я предполагаю, что вы работали с весенней загрузкой одним способом или другое - если нет, не стесняйтесь задавать конкретные вопросы по этому поводу - я / мои коллеги постараюсь ответить.

Если вы с go по start.spring.io можете создать образец демонстрационного приложения - оно загрузится довольно быстро. Так что все зависит от компонентов вашего приложения.

В свете этого, что именно следует оптимизировать?

В комментариях вы упомянули, что может быть tomcat, работающий с некоторыми JAR-файлами, чтобы они не будет загружаться при запуске приложения весенней загрузки.

Ну, как упомянули наши коллеги, это действительно больше похоже на «традиционную» модель контейнера / сервера приложений веб-сервлетов, которую мы, люди в этой отрасли, «использовали на века »(примерно на 20 лет более или менее).

Этот тип развертывания действительно имеет «всегда работающий» процесс JVM, который «всегда» готов принимать файлы WAR - архив пакетов вашего приложения. Как только он обнаруживает WAR, брошенный в какую-либо папку, он «развертывает» приложение посредством создания иерархического загрузчика классов и загрузки JAR-файлов / классов приложения. Что интересно в вашем контексте, так это то, что можно было «совместно использовать» библиотеки между несколькими войнами, чтобы они были загружены только один раз. Например, если ваш tomcat размещает, скажем, 3 приложения (читайте 3 WAR) и все используют драйвер базы данных oracle, вы можете поместить jar этого драйвера в какую-нибудь общую папку libs, и он будет загружен только один раз загрузчик классов, который является «родительским» для загрузчиков классов, созданных с помощью «WAR». Эта иерархия загрузчика классов имеет решающее значение, но я считаю, что это выходит за рамки вопроса.

Раньше я работал с обеими моделями (весенняя загрузка со встроенным сервером, приложение без весенней загрузки со встроенным сервером Jetty и "олдскульные" развертывания tomcat / jboss).

Исходя из моего опыта и, как показывает время, многие из наших коллег соглашаются с этим, приложения с весенней загрузкой намного удобнее с точки зрения эксплуатации по многим причинам (опять же, эти причины выходят за рамки вопроса IMO , дайте мне знать, если вам нужно узнать больше об этом), поэтому это текущая «тенденция» и «традиционные» развертывания все еще в отрасли из-за или по многим не чисто техническим причинам (исторически система «определена как действующая» в режиме обслуживания у вас уже есть инфраструктура развертывания, команда «системных администраторов», которые «знают», как развертывать, вы называете это, но в итоге ничего чисто технического).

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

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

Эта модель допускает практически неограниченную масштабируемость «вычислительной» мощности (ЦП), а под капотом они «запускают» контейнеры и «уничтожают» их сразу же, как только они обнаруживают, что контейнер фактически ничего не делает. Для этого типа приложений простая загрузка Spring не подходит, но в основном это Java, опять же, потому что процесс JVM запускается относительно медленно, поэтому, как только они запустят контейнер таким образом, это займет слишком много времени, пока время его ввода в эксплуатацию.

Вы можете прочитать Здесь о том, что весенняя экосистема может предложить в этой области, но это не имеет отношения к вашему вопросу (я пытаюсь дать указания) .

Spring boot отлично работает, когда вам нужно приложение, запуск которого может занять некоторое время, но после запуска оно может выполнять свою работу довольно быстро. И да, можно остановить приложение (мы используем термин масштабирование / масштабирование), если оно не «занято» реальной работой, этот подход также является новым (~ 3-4 года) и лучше всего работает в «управляемые» среды развертывания, такие как kubernetes, amazon ECS и т. д. c.

4 голосов
/ 31 мая 2020

Итак, если вашей целью является ускорение запуска приложения, я думаю, вам понадобится другой подход, здесь краткое изложение того, почему я так думаю:

  • docker: контейнер - это запущенный экземпляр изображения, вы можете видеть изображение как файловую систему (на самом деле это нечто большее, но мы говорим о библиотеках). В контейнере у вас есть jdk (и я думаю, ваше изображение основано на tomcat). Движок Docker имеет очень хорошо спроектированную систему кеширования, поэтому контейнеры запускаются очень быстро, если в контейнер не вносятся изменения, docker нужно только получить некоторую информацию из кеша. Эти контейнеры изолированы и по уважительным причинам (безопасность, модульность и разговор об изоляции библиотек позволяют иметь больше версий библиотеки в разных контейнерах). Тома не то, что вы думаете, они не предназначены для совместного использования библиотек, они позволяют вам нарушить изоляцию, чтобы сделать некоторые вещи, например, вы можете создать том для своей кодовой базы, чтобы вам не приходилось перестраивать образ для каждого изменения на этапе программирования, но обычно вы не увидите их в производственной среде (возможно, для некоторых файлов конфигурации).

  • java / spring: spring - это фреймворк, основанный на java, java is на основе jdk и кода java работает на vm. Итак, чтобы запустить программу java, вы должны запустить эту виртуальную машину (другого способа сделать это нет), и, конечно, вы не можете сократить время запуска. Среда Java очень мощная, но именно поэтому многие люди предпочитают nodejs, особенно для небольших сервисов, код java медленно запускается (минуты против секунд). Spring, как было сказано ранее, основан на java, сервлетах и ​​контексте. Приложение Spring живет в этом контексте, поэтому для запуска приложения Spring вам необходимо инициализировать этот контекст.

Вы запускаете контейнер, поверх которого вы запускаете виртуальную машину, затем инициализируют контекст Spring и, наконец, вы инициализируете bean-компоненты своего приложения. Эти шаги являются последовательными по причинам зависимости. Вы не можете инициализировать docker, vm и контекст Spring и запустить в другом месте свое приложение, например, если вы в приложении Spring добавляете цепной фильтр, вам нужно будет перезапустить приложение, потому что вам нужно будет добавить сервлет в вашу систему. Если вы хотите ускорить процесс запуска, вам нужно будет изменить java vm или внести некоторые изменения в инициализацию Spring. Таким образом, вы пытаетесь решить эту проблему на высоком уровне, а не на низком.

4 голосов
/ 31 мая 2020

Чтобы ответить на ваш первый вопрос:

Я не совсем уверен, как этого добиться? Могу ли я добиться этого, используя тома, или мне следует искать совершенно другой подход?

Это должно быть сбалансировано с фактическими возможностями вашей инфраструктуры.

  1. Один может иметь полную волоконно-оптическую кабельную сеть, репозиторий Docker в этой сети и, таким образом, пропускную способность, превышающую допустимую для «больших» изображений
  2. У одного может быть плохое соединение с его репозиторием Docker, и т. нужны очень маленькие изображения, но быстрое соединение с репозиториями библиотек
  3. Можно использовать сине-зеленый метод развертывания и не заботиться ни о размере образов и слоя, ни о времени загрузки

Во-первых, если вам важны изображение и размер слоя, это хорошо, и это определенно хорошая практика, рекомендованная Docker, но все зависит от ваших потребностей. Рекомендация о том, чтобы изображения и слои оставались маленькими, если вы будете распространять изображения. Если это ваше собственное изображение для вашего собственного приложения, тогда вы должны действовать в соответствии с вашими потребностями.

Вот немного моего собственного опыта: в компании, над которой я работал, нам нужна база данных для быть синхронизированными с производственной среды до пользовательских приемочных испытаний и среды разработки. Из-за размера производственной среды импорт данных из файла SQL в entrypoint контейнера занял около двадцати минут. Это могло быть хорошо для среды UAT, но не для среды разработчика.

Итак, попробовав всевозможные незначительные улучшения в файле SQL (например, отключив проверки внешних ключей и т.п.), Я придумал совершенно новый подход: я создал большой жирный образ в ночной сборке, который уже содержал бы базу данных. Это действительно противоречит хорошей практике Docker, но пропускная способность в офисе позволила контейнеру запуститься в течение 5 минут и хуже, по сравнению с 20, которые были раньше.

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

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

Чтобы ответить на ваш второй вопрос:

С другой стороны, если я хочу, чтобы запускались x контейнеров, я проверю, что запущено x уже существующих контейнеров. Это сделано для того, чтобы каждый контейнер имел свои собственные спецификации c librarycontainer для работы. Было бы хорошо?

Я бы сказал, что ответ: нет. Даже в архитектуре микросервисов каждая служба должна уметь что-то делать. Насколько я понимаю, вы на самом деле not-library-container ничего не можете сделать, потому что они тесно связаны с предсуществованием другого контейнера.

Это говорит о двух вещах, которые могут вас заинтересовать:

Первый : помните, что вы всегда можете создать из другого уже существующего образа, даже вашего собственного. Учитывая, что это будет ваш library-container Dockerfile

FROM: openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Кредиты: https://spring.io/guides/topicals/spring-boot-docker/#_a_basic_dockerfile

И что вы собираете его через

docker build -t my/spring-boot .

Затем вы можете создать еще одну сборку контейнера поверх этого образа:

FROM: my/spring-boot
COPY some-specific-lib lib.jar

Во-вторых, : есть хороший метод в Docker для работы с библиотеками, который называется многоступенчатые сборки и который может быть использован именно для вашего случая.

FROM openjdk:8-jdk-alpine as build
WORKDIR /workspace/app

COPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src src

RUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

Кредиты : https://spring.io/guides/topicals/spring-boot-docker/#_multi_stage_build

И, как вы можете видеть в титрах этой многоступенчатой ​​сборки, есть даже ссылка на эту технику в путеводитель по сайту Spring .

1 голос
/ 07 июня 2020

То, как вы пытаетесь достичь своей цели, противоречит всей сути контейнеризации.

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

Рассматривали ли вы фактическую компиляцию Java приложения в собственный двоичный файл?

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

Ниже приведен пример Dockerfile, демонстрирующий создание образа Docker для скомпилированного Java приложения.

# Dockerfile

FROM oracle/graalvm-ce AS builder

LABEL maintainer="Igwe Kalu <igwe.kalu@live.com>"

COPY HelloWorld.java /app/HelloWorld.java

RUN \
    set -euxo pipefail \
    && gu install native-image \
    && cd /app \
    && javac HelloWorld.java \
    && native-image HelloWorld

FROM debian:10.4-slim

COPY --from=builder /app/helloworld /app/helloworld

CMD [ "/app/helloworld" ]
# .dockerignore

**/*

!HelloWorld.java
// HelloWorld.java

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Native Java World!");
    }
}

Создайте образ и запустите контейнер:

# Building...
docker build -t graalvm-demo-debian-v0 .

# Running...
docker run graalvm-demo-debian-v0:latest

## Prints
## Hello, Native Java World!

Подсказки Spring: Функция создания встроенных образов GraalVM - это статья демонстрирует создание приложения Spring Boot с помощью GraalVM.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...