在Java和Maven中只使用Docker构建和运行应用程序的例子

367 阅读5分钟

想象一下以下情况:你开始在一个新的项目上工作,也许还使用了一种你不习惯的不同的编程语言。所以,项目在那里,你应该能够运行它。

你希望有任何文档告诉你该怎么做--这并不常见--如果/如果有的话,通常也不会成功。你需要知道要安装什么在哪里安装,如何设置,等等。这并不是一个不常见的情况,实际上你可以在某些时候期待这种情况。但是,如果有一种方法可以确保这种情况不会再发生呢?

在这篇文章中,我们将看到不同的方法,我们可以只使用Docker来使其更容易。

第一层:使用Docker的别名

使用Java+Maven的例子

让我们以一个Java项目为例。通常情况下,要运行一个Java应用程序,你需要运行java -jar application.jar

为了生成jar 文件和管理项目的依赖关系,你可以使用很多不同的工具,其中最著名的是Maven和Gradle。在这个例子中,让我们考虑一下Maven。让我们看看一些Maven命令:

  • mvn dependency:copy-dependencies
    如果还没有下载依赖项,就下载它们。
  • mvn package
    构建应用程序并生成jar 。如果还没有下载依赖项,它也会下载。如果你想在构建过程中跳过运行测试,你也可以传递-Dmaven.test.skip=true 参数。

假设我们需要Maven 3和Java 11,这就是你可以使用Docker的方法。

alias java='docker run -v "$PWD":/home -w /home openjdk:11-jre-slim java'
alias mvn='docker run -it --rm --name maven -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3-jdk-11-slim mvn'

这样一来,您就可以运行任何Maven和Java命令,而无需安装Java或Maven。你可以通过运行java -versionmvn -version 来测试这些命令。通常,这些工具的官方Docker镜像会给出运行说明,你只需为此创建一个别名。

优点:

  • 如果你不需要再使用它,你可以删除相关的Docker镜像。
  • 改变版本很容易。

缺点是:

  • 在这个例子中,你仍然需要找出使用的是什么Java版本,使用的是什么工具(本例中是Maven)及其版本。
  • 如果你面对的是一种你不懂的编程语言,那就需要花更多的时间去了解该怎么做。
  • 你仍然需要知道要运行哪些命令。

这是一个公平的方法,特别是如果你知道自己在做什么。但这并不是项目本身就有的。所以,让我们试着改进一下吧。

第二层:使用Docker来运行应用程序

这就是Dockerfile开始发威的地方。我们知道如何只使用Docker来运行命令,但如何运行应用程序呢?

针对这种情况,一个常见的Dockerfile可以是:

FROM openjdk:11-jre-slim

ARG JAR_FILE=target/*.jar

ADD ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]

而你可以用你通常构建docker镜像的方式来构建它,比如说使用docker build -t my-application . ,。

你可以看到,它依赖于一个现有的JAR 文件来构建。正如你在上一个话题中看到的,我们知道如何生成它,但如果是另一种编程语言或工具,我们就麻烦了。

这似乎是一个非常小的改进,但这已经帮助了很多,你可以在它的优点/缺点中看到。

优点

  • Docker文件应该在项目中出现。因此,它已经告诉你如何运行该应用程序,而不考虑编程语言知识。
  • 它还会告诉你正在使用的版本和图像。
  • 如果你也应用一级知识,它就会继承一级主题的优点。

缺点

  • 你仍然需要找出如何构建应用程序。
  • 这也意味着你仍然需要知道要运行哪些命令。

这是一个好方法。你可以将第一级第二级合并,以达到更好的效果。该项目应该有Dockerfile,生活已经变得有点简单了。但是,让我们看看生活如何能变得更容易

第三级:使用Docker来构建和运行应用程序

如果你对Java和Maven一无所知,而你仍然能够用一个你已经知道的命令来构建应用程序,那会怎么样?

这就是多阶段构建的优势所在。

在多阶段构建中,你在Docker文件中使用多个FROM 。每个FROM 指令都可以使用不同的基数,并且每个指令都开始一个新的构建阶段。你可以有选择地将工件从一个阶段复制到另一个阶段,留下你不希望在最终镜像中出现的一切。

这怎么能帮助我们呢?好吧,让我们考虑一下之前的Docker文件。我们需要一个JAR 文件来构建Docker镜像。有了多阶段构建,Dockerfile本身可以负责生成它。在一个简单的方法中,该Dockerfile看起来像这样:

# ============= DEPENDENCY + BUILD ===========================
# Download the dependencies on container and build application
# ============================================================

FROM maven:3-jdk-11-slim AS builder

COPY ./pom.xml /app/pom.xml
COPY . /app

WORKDIR /app

RUN mvn package $MAVEN_CLI_OPTS -Dmaven.test.skip=true

# ============= DOCKER IMAGE ================
# Prepare container image with application artifacts
# ===========================================

FROM openjdk:11-jre-slim

COPY --from=builder /app/target/*.jar app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]

让我们来看看这里发生了什么。

从第一个FROM 到第一个RUN 语句,它正在做与Maven有关的事情--拷贝需要拷贝的文件,运行下载依赖项和构建应用程序的命令。它通过使用maven:3-jdk-11-slim 镜像来完成这些工作,并设置名称为builder

在这之后,你看到第二个FROM 语句,使用openjdk:11-jre-slim 镜像。你还看到一个COPY 语句,它从一个叫做builder 的地方复制。但那是什么地方?那个建立者是什么?

那是我们在第一个FROM 语句中为Maven镜像设置的名字。它从那个容器中复制了jar 文件。因此,你可以从字面上玩弄不同的FROM 条目来构建你想要的东西,而构建Docker镜像的命令仍然是一样的:docker build -t my-application .

优点:

  • 不管是哪种编程语言,如果项目有这种方法,你就可以在不安装Docker以外的东西的情况下运行应用程序。
  • 它继承了第一级第二级的优点。

值得一说的是,你也可以用Docker Compose来使用这个Docker文件,这可能真的很强大,特别是当你的应用程序需要暴露端口、共享卷,或者它依赖于其他镜像时。

附录。在每个主要命令中使用Docker

现在你知道了如何玩转不同的FROM 语句,另一个可能的Dockerfile可以是:

# ============= DEPENDENCY RESOLVER =============
# Download the dependencies on container
# ===============================================

FROM maven:3-jdk-11-slim AS dependency_resolver

# Download all library dependencies
COPY ./pom.xml /app/pom.xml

WORKDIR /app

RUN mvn dependency:copy-dependencies $MAVEN_CLI_OPTS

# ============= TESTING =================
# Run tests on container
# =======================================

FROM dependency_resolver AS tester

WORKDIR /app

CMD mvn clean test $MAVEN_CLI_OPTS

# ============= BUILDER =================
# Build the artifact on container
# =======================================

FROM dependency_resolver as builder

# Build application
COPY . /app

RUN mvn package $MAVEN_CLI_OPTS -Dmaven.test.skip=true

# ============= DOCKER IMAGE ================
# Prepare container image with application artifacts
# ===========================================

FROM openjdk:11-jre-slim

COPY --from=builder /app/target/*.jar app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]

所以,现在我们有四个不同的步骤。dependency_resolver,tester,builder, 和应用程序本身。

如果我们想构建应用程序或测试它,我们需要项目的依赖性。因此,那里有一个dependency_resolver 的步骤。在第二个和第三个FROM 语句中,你可以看到它们依赖于dependency_resolver

重要的是:如果你试图用docker build -t my-application . 构建docker镜像,只有第一、第三和最后一步(分别是dependency_resolverbuilder 、和应用程序本身)会运行

但为什么呢?

当你尝试构建镜像时,它会尝试查看镜像的最终状态是什么,这将是应用程序本身。正如我们所知,也可以看到,最后一步取决于builder (COPY 语句)。如果我们检查builder 步骤,我们会看到它依赖于dependency_resolver (FROM 语句)。因此,它将按照这个顺序运行。

dependency_resolver -> -> 应用程序builder

那么,tester 这个步骤在那里做什么?有什么方法可以到达它吗?

你可以通过使用--target:docker build --target tester -t my-application . 来指定一个目标。

这也是与Docker Compose兼容的。

总结

我们看到了如何只使用Docker来构建和运行应用程序和命令,不再需要之前的一些工具或编程语言的知识。另外,值得一提的是,尽管我们在这些例子中使用了Docker,但这也适用于其他容器运行机制,如Podman。

我希望你觉得这篇文章对你有帮助,并传播关于Docker的信息。