Как докеризовать проект maven? и сколько способов добиться этого?

87

Я новичок в Docker и не знаю, как запустить java-проект с maven, хотя я прочитал много документов и попробовал много методов.

  1. Должен ли я создавать образ с помощью Dockerfile?
  2. Какие команды похожи на запуск проекта maven на хосте Dockerfile?
Яшон Лин
источник

Ответы:

122

Рабочий пример.

Это не учебник по весенней загрузке. Это обновленный ответ на вопрос о том, как запустить сборку Maven в контейнере Docker.

Вопрос был опубликован 4 года назад.

1. Создайте заявку

Используйте инициализатор Spring для создания демонстрационного приложения

https://start.spring.io/

введите описание изображения здесь

Распакуйте zip-архив локально

2. Создайте Dockerfile.

#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean package

#
# Package stage
#
FROM openjdk:11-jre-slim
COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/local/lib/demo.jar"]

Заметка

  • В этом примере используется многоступенчатая сборка . Первый этап используется для построения кода. Второй этап содержит только построенный jar и JRE для его запуска (обратите внимание, как jar копируется между этапами).

3. Создайте образ.

docker build -t demo .

4. Запускаем образ

$ docker run --rm -it demo:latest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

2019-02-22 17:18:57.835  INFO 1 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication v0.0.1-SNAPSHOT on f4e67677c9a9 with PID 1 (/usr/local/bin/demo.jar started by root in /)
2019-02-22 17:18:57.837  INFO 1 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2019-02-22 17:18:58.294  INFO 1 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.711 seconds (JVM running for 1.035)

Разное

Прочтите документацию Docker Hub о том, как можно оптимизировать сборку Maven для использования локального репозитория для кеширования jar-файлов.

Обновление (2019-02-07)

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

Вариант 1: многоступенчатая сборка

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

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

FROM maven:3.5-jdk-8 AS build  
COPY src /usr/src/app/src  
COPY pom.xml /usr/src/app  
RUN mvn -f /usr/src/app/pom.xml clean package

FROM gcr.io/distroless/java  
COPY --from=build /usr/src/app/target/helloworld-1.0.0-SNAPSHOT.jar /usr/app/helloworld-1.0.0-SNAPSHOT.jar  
EXPOSE 8080  
ENTRYPOINT ["java","-jar","/usr/app/helloworld-1.0.0-SNAPSHOT.jar"]  

Заметка:

  • Я использую базовый образ Google без дистрибутива , который стремится обеспечить достаточно времени выполнения для Java-приложения.

Вариант 2: удлинитель

Я не использовал этот подход, но кажется заслуживающим исследования, поскольку он позволяет создавать образы без необходимости создавать такие неприятные вещи, как файлы Docker :-)

https://github.com/GoogleContainerTools/jib

В проекте есть плагин Maven, который интегрирует упаковку вашего кода непосредственно в рабочий процесс Maven.


Исходный ответ (включен для полноты, но написан много лет назад)

Попробуйте использовать новые официальные изображения, есть один для Maven

https://registry.hub.docker.com/_/maven/

Образ можно использовать для запуска Maven во время сборки для создания скомпилированного приложения или, как в следующих примерах, для запуска сборки Maven в контейнере.

Пример 1 - Maven работает в контейнере

Следующая команда запускает вашу сборку Maven внутри контейнера:

docker run -it --rm \
       -v "$(pwd)":/opt/maven \
       -w /opt/maven \
       maven:3.2-jdk-7 \
       mvn clean install

Примечания:

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

Пример 2 - Использование Nexus для кеширования файлов

Запустите контейнер Nexus

docker run -d -p 8081:8081 --name nexus sonatype/nexus

Создайте файл "settings.xml":

<settings>
  <mirrors>
    <mirror>
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>http://nexus:8081/content/groups/public/</url>
    </mirror>
  </mirrors>
</settings>

Теперь запустите Maven, связывающийся с контейнером нексуса, чтобы зависимости были кэшированы.

docker run -it --rm \
       -v "$(pwd)":/opt/maven \
       -w /opt/maven \
       --link nexus:nexus \
       maven:3.2-jdk-7 \
       mvn -s settings.xml clean install

Примечания:

  • Преимущество работы Nexus в фоновом режиме заключается в том, что другими сторонними репозиториями можно управлять через URL-адрес администратора прозрачно для сборок Maven, работающих в локальных контейнерах.
Марк О'Коннор
источник
можно ли это использовать для замены центра maven для сборки gradle? как указано в support.sonatype.com/entries/… Я заменил mavenCentral()в своих зависимостях gradle на maven {url "http://nexus:8081..."и теперь просто получаю проблемы с разрешением.
mohamnag
@mohamnag Правильно, вышеприведенный файл "настроек" Maven делает именно это, перенаправляя все запросы Maven Central в локальный репозиторий nexus. Вам необходимо указать, с какими проблемами вы сталкиваетесь. Может быть что угодно ... Например, вы настроили ссылку Docker, чтобы хост "nexus" был правильно разрешен?
Марк О'Коннор,
Оказалось, что все в порядке, но нексусу либо требовалось время для создания индекса, либо что-то еще вызывало проблемы. Я также пытался запустить обновление индекса, поэтому не уверен, в каком случае проблема была решена. Однако спасибо.
mohamnag
Nexus 3 теперь также доступен в виде док-контейнера. Используйте «sonatype / nexus3»
Торбьёрн Равн Андерсен
1
@avandeursen Вы правы, что параметр --link устарел (ответ более 3 лет). Однако более правильным решением (на мой взгляд) является создание сети докеров и запуск на ней обоих контейнеров. Таким образом, вы можете использовать встроенную функцию DNS в Docker и продолжать обращаться к контейнеру nexus по имени. Обновим пример позже
Марк О'Коннор
47

Может быть много способов .. Но я реализовал двумя способами

Данный пример относится к проекту maven.

1. Использование Dockerfile в проекте maven

Используйте следующую файловую структуру:

Demo
└── src
|    ├── main
|    │   ├── java
|    │       └── org
|    │           └── demo
|    │               └── Application.java
||    └── test
|
├──── Dockerfile
├──── pom.xml

И обновите Dockerfile как:

FROM java:8
EXPOSE 8080
ADD /target/demo.jar demo.jar
ENTRYPOINT ["java","-jar","demo.jar"]

Перейдите в папку проекта и введите следующую команду, с помощью которой вы сможете создать образ и запустить этот образ:

$ mvn clean
$ mvn install
$ docker build -f Dockerfile -t springdemo .
$ docker run -p 8080:8080 -t springdemo

Получите видео на Spring Boot с Docker

2. Использование плагинов Maven

Добавить данный плагин maven в pom.xml

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.4.5</version>
        <configuration>
            <imageName>springdocker</imageName>
            <baseImage>java</baseImage>
            <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
            <resources>
                <resource>
                    <targetPath>/</targetPath>
                    <directory>${project.build.directory}</directory>
                    <include>${project.build.finalName}.jar</include>
                </resource>
            </resources>
        </configuration>
    </plugin>

Перейдите в папку проекта и введите следующую команду, с помощью которой вы сможете создать образ и запустить этот образ:

$ mvn clean package docker:build
$ docker images
$ docker run -p 8080:8080 -t <image name>

В первом примере мы создаем Dockerfile и предоставляем базовый образ и добавляем jar и так, после этого мы запустим команду docker для создания образа с определенным именем, а затем запустим этот образ.

В то время как во втором примере мы используем плагин maven, в котором мы предоставляем, baseImageи imageNameпоэтому нам не нужно создавать здесь Dockerfile ... после упаковки проекта maven мы получим образ докера, и нам просто нужно запустить этот образ ..

Риддхи Гохил
источник
Вместо того, чтобы изменять точку входа для указания имени артефакта, вы можете использовать подход, подобный приведенному здесь: alooma.com/blog/building-dockers - используйте maven-dependency-plugin для использования общего имени. Нет необходимости помещать версию jar-файла в контейнер докеров, поскольку сам контейнер является версионным.
kboom
14

Как правило, вы должны создавать толстый JAR с использованием Maven (JAR, который содержит ваш код и все зависимости).

Затем вы можете написать Dockerfile , соответствующий вашим требованиям (если вы можете создать толстый JAR, вам понадобится только базовая ОС, такая как CentOS, и JVM).

Это то, что я использую для приложения Scala (основанного на Java).

FROM centos:centos7

# Prerequisites.

RUN yum -y update
RUN yum -y install wget tar

# Oracle Java 7

WORKDIR /opt

RUN wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u71-b14/server-jre-7u71-linux-x64.tar.gz
RUN tar xzf server-jre-7u71-linux-x64.tar.gz
RUN rm -rf server-jre-7u71-linux-x64.tar.gz
RUN alternatives --install /usr/bin/java java /opt/jdk1.7.0_71/bin/java 1

# App

USER daemon

# This copies to local fat jar inside the image
ADD /local/path/to/packaged/app/appname.jar /app/appname.jar

# What to run when the container starts
ENTRYPOINT [ "java", "-jar", "/app/appname.jar" ]

# Ports used by the app
EXPOSE 5000

Это создает образ на основе CentOS с Java7. При запуске он выполнит jar-файл вашего приложения.

Лучший способ развернуть его - через реестр Docker, это как Github для образов Docker.

Вы можете построить такой образ:

# current dir must contain the Dockerfile
docker build -t username/projectname:tagname .

Затем вы можете отправить изображение следующим образом:

docker push username/projectname # this pushes all tags

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

См. Руководство пользователя Docker для получения дополнительной информации.

Что нужно иметь в виду :

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

Создание толстой банки решает эту проблему.

Маттео Пачини
источник
Привет, я использовал ваш пример в своем файле докеров, чтобы скопировать толстую банку в образ, но сборка не выполняется из-за невозможности найти толстую банку по локальному пути. Это что-то вроде target / app.jar?
user_mda 08
Привет, есть ли способ загрузить артефакт из нексуса во время выполнения? Чтобы указать загружаемый артефакт, используя свойства, а не фактическую ссылку на саму банку? В файле докеров: RUN wget -O {project.build.finalname}.jar но я хочу загрузить указанную выше банку из nexus.
Pramod Setlur
1
Включение FAT JAR в репозиторий кода для меня является медленным. Есть ли способ использовать maven для создания образа?
WoLfPwNeR
1
У толстых банок тоже есть проблемы, особенно с банками с подписью.
Торбьёрн Равн Андерсен
1
Как правило, никогда не используйте толстую банку, пакеты, которые полагаются на манифест или другие файлы данных в своей банке, могут потерпеть неудачу, поскольку нет безопасного способа объединить эти данные. Файлы манифеста с совпадающими путями внутри jar-конфликта и первым или последним выигрышем (не могу вспомнить, какой). Единственный случай, когда вам следует подумать об использовании толстой банки, - это если у вас очень ограниченный набор зависимостей и вы очень хорошо знаете, что их банки не имеют противоречивых данных манифеста. В противном случае оставайтесь в безопасности, пользуйтесь банками, поскольку они предназначены для использования (т. Е. Отдельно).
PiersyP
3

Вот мой вклад.
Я не буду пытаться перечислять все инструменты / библиотеки / плагины, которые существуют для использования Docker с Maven. Некоторые ответы уже сделали это.
вместо этого я сосредоточусь на типологии приложений и способе работы с Dockerfile.
Dockerfileдействительно простая и важная концепция Docker (все известные / общедоступные образы полагаются на это), и я думаю, что попытка избежать понимания и использования Dockerfiles - не обязательно лучший способ войти в мир Docker.

Докеризация приложения зависит от самого приложения и цели.

1) Для приложений, которые мы хотим продолжить, запускать их на установленном / автономном сервере Java (Tomcat, JBoss и т. Д.)

Дорога сложнее, и это не идеальная цель, потому что это увеличивает сложность (мы должны управлять / поддерживать сервер), а также он менее масштабируем и менее быстр, чем встроенные серверы, с точки зрения сборки / развертывания / удаления.
Но для устаревших приложений это можно рассматривать как первый шаг.
Как правило, идея здесь состоит в том, чтобы определить образ Docker для сервера и определить образ для развертывания каждого приложения.
Образы докеров для приложений создают ожидаемые WAR / EAR, но они не выполняются как контейнер, а образ для серверного приложения развертывает компоненты, созданные этими образами, как развернутые приложения.
Это действительно хорошее улучшение для огромных приложений (миллионы строк кода) с большим количеством устаревших вещей, которые так сложно перенести на встроенное решение с полной загрузкой с пружинной загрузкой.
Я не буду подробно описывать этот подход, поскольку он предназначен для незначительных случаев использования Docker, но я хотел раскрыть общую идею этого подхода, потому что я думаю, что разработчикам, которые сталкиваются с этими сложными случаями, приятно знать, что некоторые двери открыты для интегрировать Docker.

2) Для приложений, которые встраивают / загружают сам сервер (Spring Boot со встроенным сервером: Tomcat, Netty, Jetty ...)

Это идеальная цель для Docker . Я указал Spring Boot, потому что это действительно хороший фреймворк для этого и который также имеет очень высокий уровень ремонтопригодности, но теоретически мы могли бы использовать любой другой способ Java для достижения этого.
Как правило, идея здесь состоит в том, чтобы определить образ Docker для каждого развертываемого приложения.
Образы докеров для приложений создают JAR или набор файлов JAR / классов / конфигурации, и они запускают JVM с приложением (команда java), когда мы создаем и запускаем контейнер из этих образов.
Для новых приложений или приложений, которые не слишком сложны для миграции, этот способ должен быть предпочтительнее автономных серверов, потому что это стандартный и наиболее эффективный способ использования контейнеров.
Я подробно расскажу об этом подходе.

Докеризация приложения maven

1) Без Spring Boot

Идея состоит в том, чтобы создать толстую банку с Maven (плагин сборки maven и помощь плагина maven shade для этого), которая содержит как скомпилированные классы приложения, так и необходимые зависимости maven.
Тогда мы можем выделить два случая:

  • если приложение является настольным или автономным приложением (которое не нужно развертывать на сервере): мы могли бы указать, как CMD/ENTRYPOINTв Dockerfilejava-исполнении приложения:java -cp .:/fooPath/* -jar myJar

  • если приложение является серверным приложением, например Tomcat, идея такая же: получить толстую банку приложения и запустить JVM в CMD/ENTRYPOINT. Но здесь есть важное отличие: нам нужно включить некоторую логику и определенные библиотеки ( org.apache.tomcat.embedбиблиотеки и некоторые другие), которые запускают встроенный сервер при запуске основного приложения.
    У нас есть подробное руководство на сайте heroku .
    В первом случае (автономное приложение) это простой и эффективный способ использования Docker.
    Во втором случае (серверное приложение), которое работает, но не является прямым, может быть подвержено ошибкам и не является очень расширяемой моделью, потому что вы не помещаете свое приложение в рамки зрелой структуры, такой как Spring Boot, которая делает много этих вещей для вас, а также обеспечивает высокий уровень расширения.
    Но у этого есть преимущество: у вас есть высокий уровень свободы, потому что вы напрямую используете встроенный Tomcat API.

2) С Spring Boot

Наконец, мы идем.
Это одновременно просто, эффективно и очень хорошо документировано.
На самом деле существует несколько подходов к запуску приложения Maven / Spring Boot в Docker.
Разоблачать их всех было бы долго и, может быть, скучно.
Лучший выбор зависит от ваших требований.
Но как бы то ни было, стратегия сборки с точки зрения слоев докеров выглядит одинаково.
Мы хотим использовать многоступенчатую сборку: одна полагается на Maven для разрешения зависимостей и для сборки, а другая полагается на JDK или JRE для запуска приложения.

Этап сборки (образ Maven):

  • Пом копировать к изображению
  • зависимости и загрузки плагинов.
    Об этом, mvn dependency:resolve-pluginsприкованный к нему mvn dependency:resolveможет работать, но не всегда.
    Зачем ? Поскольку эти плагины и packageвыполнение для упаковки толстой банки могут полагаться на разные артефакты / плагины и даже для одного и того же артефакта / плагина, они все равно могут получить другую версию. Таким образом, более безопасный подход, хотя и потенциально более медленный, заключается в разрешении зависимостей путем выполнения именно той mvnкоманды, которая используется для упаковки приложения (которая будет извлекать именно те зависимости, которые вам нужны), но путем пропуска исходной компиляции и удаления целевой папки, чтобы ускорить обработку и предотвратить обнаружение нежелательного изменения слоя на этом этапе.
  • копия исходного кода на изображение
  • упаковать приложение

Этап выполнения (JDK или образ JRE):

  • скопируйте банку с предыдущего этапа

Вот два примера.

а) Простой способ без кеша для загруженных зависимостей maven

Dockerfile:

########Maven build stage########
FROM maven:3.6-jdk-11 as maven_build
WORKDIR /app

#copy pom
COPY pom.xml .

#resolve maven dependencies
RUN mvn clean package -Dmaven.test.skip -Dmaven.main.skip -Dspring-boot.repackage.skip && rm -r target/

#copy source
COPY src ./src

# build the app (no dependency download here)
RUN mvn clean package  -Dmaven.test.skip

# split the built app into multiple layers to improve layer rebuild
RUN mkdir -p target/docker-packaging && cd target/docker-packaging && jar -xf ../my-app*.jar

########JRE run stage########
FROM openjdk:11.0-jre
WORKDIR /app

#copy built app layer by layer
ARG DOCKER_PACKAGING_DIR=/app/target/docker-packaging
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/lib /app/lib
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/classes /app/classes
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/META-INF /app/META-INF

#run the app
CMD java -cp .:classes:lib/* \
         -Djava.security.egd=file:/dev/./urandom \
         foo.bar.MySpringBootApplication

Недостаток этого решения? Любые изменения в pom.xml означают воссоздание всего уровня, который загружает и сохраняет зависимости maven. Как правило, это неприемлемо для приложений со многими зависимостями (а Spring Boot извлекает много зависимостей), в целом, если вы не используете диспетчер репозитория maven во время сборки образа.

б) Более эффективный способ с кешем для загруженных зависимостей maven

Подход здесь тот же, но загрузки зависимостей maven кешируются в кеше построителя докеров.
Операция с кешем зависит от buildkit (экспериментального API докера).
Чтобы включить buildkit, необходимо установить переменную env DOCKER_BUILDKIT = 1 (вы можете сделать это там, где хотите: .bashrc, командная строка, json-файл демона докеров ...).

Dockerfile:

# syntax=docker/dockerfile:experimental

########Maven build stage########
FROM maven:3.6-jdk-11 as maven_build
WORKDIR /app

#copy pom
COPY pom.xml .

#copy source
COPY src ./src

# build the app (no dependency download here)
RUN --mount=type=cache,target=/root/.m2  mvn clean package -Dmaven.test.skip

# split the built app into multiple layers to improve layer rebuild
RUN mkdir -p target/docker-packaging && cd target/docker-packaging && jar -xf ../my-app*.jar

########JRE run stage########
FROM openjdk:11.0-jre
WORKDIR /app

#copy built app layer by layer
ARG DOCKER_PACKAGING_DIR=/app/target/docker-packaging
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/lib /app/lib
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/classes /app/classes
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/META-INF /app/META-INF

#run the app
CMD java -cp .:classes:lib/* \
         -Djava.security.egd=file:/dev/./urandom \
         foo.bar.MySpringBootApplication
davidxxx
источник