我是Docker的新手,即使我已阅读许多文档并尝试了许多方法,也不知道如何使用maven运行Java项目。
- 我应该使用构建图像
Dockerfile
吗? - 使用宿主在主机中运行Maven项目时的命令是
Dockerfile
什么?
Answers:
这不是Spring Boot教程。这是关于如何在Docker容器中运行Maven构建的问题的最新答案。
问题最初于4年前发布。
使用spring初始值设定项来生成演示应用
本地提取zip存档
#
# 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"]
注意
docker build -t demo .
$ 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中心文档,了解如何优化Maven构建以使用本地存储库来缓存jar。
这个问题现在已有4年历史了,可以说使用Docker构建应用程序已经发生了重大变化。
这种新样式使您可以创建更轻量的图像,而这些图像不会封装您的构建工具和源代码。
这里的示例再次使用正式的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"]
注意:
我没有使用过这种方法,但似乎值得研究,因为它使您无需构建Dockerfiles等讨厌的东西即可构建映像:-)
https://github.com/GoogleContainerTools/jib
该项目有一个Maven插件,可将您的代码打包直接集成到Maven工作流程中。
尝试使用新的官方图像,其中有一个用于Maven
https://registry.hub.docker.com/_/maven/
该映像可用于在构建时运行Maven以创建已编译的应用程序,或者如以下示例所示,在容器内运行Maven构建。
以下命令在容器中运行您的Maven构建:
docker run -it --rm \
-v "$(pwd)":/opt/maven \
-w /opt/maven \
maven:3.2-jdk-7 \
mvn clean install
笔记:
运行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链接到nexus容器,以便将依赖项缓存
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
笔记:
可能有很多方法。但是我通过以下两种方法来实现
给出的例子是Maven项目。
1.在maven项目中使用Dockerfile
使用以下文件结构:
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
使用Docker在Spring Boot上获取视频
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项目之后,我们将获得docker映像,而我们只需要运行该映像即可。
根据经验,应该使用Maven(包含您的代码和所有依赖项的JAR)构建胖JAR。
然后,您可以编写一个符合您要求的Dockerfile(如果您可以构建一个胖的JAR,则只需一个基本的os,例如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
这将使用Java7创建一个基于CentOS的映像。启动后,它将执行您的应用程序jar。
最好的部署方式是通过Docker Registry,就像Docker镜像的Github。
您可以构建如下图像:
# current dir must contain the Dockerfile
docker build -t username/projectname:tagname .
然后,您可以通过以下方式推送图像:
docker push username/projectname # this pushes all tags
将映像放置在Docker注册表上后,您可以从世界上的任何地方提取它并运行它。
有关更多信息,请参阅《Docker用户指南》。
注意事项:
您还可以将存储库放入映像中,并将其作为容器执行的一部分来构建jar,但这不是一个好方法,因为代码可能会更改,并且最终可能会使用其他版本的应用程序,而不会另行通知。
建立一个胖子罐可以消除此问题。
RUN wget -O {project.build.finalname}.jar
但是我想从nexus下载上面的jar。
这是我的贡献。
我不会尝试列出所有可利用Maven充分利用Docker的工具/库/插件。一些答案已经做到了。
取而代之的是,我将专注于应用程序类型和Dockerfile方式。
Dockerfile
实际上是Docker的一个简单而重要的概念(所有已知/公共映像都依赖于Docker),我认为尝试避免理解和使用Dockerfile
s不一定是进入Docker世界的更好方法。
1)对于我们要继续在已安装/独立的Java服务器(Tomcat,JBoss等)上运行的应用程序
这条路比较困难,也不是理想的目标,因为这会增加复杂性(我们必须管理/维护服务器),并且在构建/部署/取消部署方面,其扩展性和速度都比嵌入式服务器低。
但是对于旧版应用程序,这可能被视为第一步。
通常,这里的想法是为服务器定义一个Docker映像,并为每个要部署的应用程序定义一个映像。
应用程序的docker映像会产生预期的WAR / EAR,但不会作为容器执行,并且服务器应用程序的映像会将这些映像生成的组件部署为已部署的应用程序。
对于具有大量遗留内容的大型应用程序(数百万行代码),因此很难迁移到完整的Spring Boot嵌入式解决方案中,这确实是一个不错的改进。
我将不详细介绍该方法,因为这是针对Docker的少量用例,但我想展示该方法的整体思想,因为我认为对于面对这些复杂情况的开发人员来说,很高兴知道有些门可以打开集成Docker
2)对于本身嵌入/引导服务器的应用程序(带有嵌入式服务器的Spring Boot:Tomcat,Netty,Jetty ...)
这是Docker的理想目标。我之所以指定Spring Boot是因为这样做确实是一个非常不错的框架,并且具有很高的可维护性,但是从理论上讲,我们可以使用任何其他Java方法来实现这一目标。
通常,这里的想法是为每个要部署的应用程序定义一个Docker映像。
应用程序的docker映像会生成一个JAR或一组JAR / classs / configuration文件,当我们从这些映像创建并启动容器时,它们会使用应用程序(java命令)启动JVM。
对于新应用程序或迁移不太复杂的应用程序,这种方法必须优于独立服务器,因为这是使用容器的标准方法和最有效的方法。
我将详细介绍该方法。
1)没有Spring Boot
这个想法是用Maven(这是maven程序集插件和maven shade插件的帮助)创建一个胖的jar,其中包含应用程序的已编译类和所需的maven依赖项。
然后,我们可以确定两种情况:
如果应用程序是桌面应用程序或自主应用程序(不需要部署在服务器上):我们可以像CMD/ENTRYPOINT
在Dockerfile
应用程序的java执行中那样指定: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上运行。
暴露所有这些可能会很长,甚至很无聊。
最佳选择取决于您的要求。
但是无论采用哪种方式,就docker层而言的构建策略看起来都是一样的。
我们想使用一个多阶段构建:一个依赖Maven进行依赖关系解析和构建,另一个依赖JDK或JRE启动应用程序。
构建阶段(Maven图像):
mvn dependency:resolve-plugins
被束缚在一起mvn dependency:resolve
也许可以胜任,但并非总是如此。package
打包胖子jar的执行可能依赖于不同的工件/插件,甚至对于相同的工件/插件,它们仍可能提取不同的版本。因此,一种安全而又可能更慢的方法是,通过完全mvn
执行用于打包应用程序的命令来解析依赖关系(这将完全拉出您所需的依赖关系),但跳过源代码编译并删除目标文件夹以加快处理速度,从而解决依赖关系防止对该步骤进行任何不希望的层变化检测。 运行阶段(JDK或JRE映像):
这里有两个例子。
a)一种没有缓存的简单方法,用于下载的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依赖项的整个层。总的来说,如果您在映像构建期间不使用Maven存储库管理器,那么对于具有许多依赖项的应用程序(通常是Spring Boot拉动许多依赖项),这通常是不可接受的。
b)一种更有效的方法,用于下载Maven依赖项的缓存
此处的方法相同,但是Maven依赖项下载已缓存在Docker构建器缓存中。
缓存操作依赖于buildkit(docker的实验api)。
要启用buildkit,必须设置环境变量DOCKER_BUILDKIT = 1(您可以在所需的位置进行操作:.bashrc,命令行,docker daemon 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
mavenCentral()
在gradle依赖项中替换maven {url "http://nexus:8081..."
为,现在正遇到解决问题。