如何为 Java 应用程序构建 docker 容器

Posted

技术标签:

【中文标题】如何为 Java 应用程序构建 docker 容器【英文标题】:How to build a docker container for a Java application 【发布时间】:2015-10-20 04:44:07 【问题描述】:

我想做的是为我的 Java 应用程序构建一个 docker 映像,但以下注意事项对于大多数编译语言来说应该是正确的。

问题

在我的构建服务器上,我想为我的应用程序生成一个 docker 映像作为可交付成果。为此,我必须使用一些构建工具(通常是 Gradle、Maven 或 Ant)编译应用程序,然后将创建的 JAR 文件添加到 docker 映像中。因为我希望 docker 镜像只执行 JAR 文件,所以我当然会从已安装 Java 的基础镜像开始。

有以下三种方法:

让构建工具控制进程

在这种情况下,我的构建工具控制着整个过程。因此它准备了 JAR 文件,并在创建 JAR 后调用 Docker 来创建映像。这是因为 JAR 是预先创建的,并且 Docker 可以忽略创建 JAR 所需的构建过程。

但我的 Dockerfile 不再是独立的。这取决于在 Docker 之外发生的步骤才能正常工作。在我的 Dockerfile 中,我将有一个 COPYADD 语句应该将 JAR 文件复制到映像。如果没有事先创建 jar,此语句将失败。所以仅仅执行 Dockerfile 可能行不通。如果您想与仅使用当前 Dockerfile 构建的服务(例如 DockerHub 上的自动构建功能)集成,这将成为一个问题。

让 Docker 控制构建

在这种情况下,创建镜像的所有必要步骤都添加到 Dockerfile 中,因此只需执行 Docker 构建即可创建镜像。

这种方法的主要问题是无法将应该在正在创建的 docker 映像之外执行的命令添加到 Dockerfile 中。这意味着我必须将我的源代码和我的构建工具添加到 docker 镜像中,并在镜像中构建我的 JAR 文件。这将导致我的图像比它必须的更大,因为添加的所有文件在运行时都是不必要的。这也会为我的图像添加额外的图层。

编辑:

正如@adrian-mouat 指出的那样,如果我在一个 RUN 语句中添加源、构建应用程序并删除源,我可以避免向 Docker 映像添加不必要的文件和层。这意味着创建一些疯狂的链式命令。

两个独立的构建

在这种情况下,我们将构建分为两部分:首先,我们使用构建工具创建 JAR 文件并将其上传到存储库(Maven 或 Ivy 存储库)。然后我们触发一个单独的 Docker 构建,它只是从存储库中添加 JAR 文件。

结论

在我看来,更好的方法是让构建工具控制过程。这将产生一个干净的 docker 镜像,因为镜像是我们想要交付的,所以这很重要。为避免出现可能无法正常工作的 Dockerfile,应将其创建为构建的一部分。所以没有人会不小心使用它来启动一个损坏的构建。

但这不允许我与 DockerHub 集成。

问题

还有其他我想念的方法吗?

2020 年 6 月更新

自从我第一次提出这个问题以来的几年里,很多东西都发生了变化。在这一点上,我会提倡使用Googel's JIB Tool。它与最常见的 Java 构建工具(Maven 和 Gradle)集成,并允许您直接从构建中创建容器。这比我多年前考虑的任何旧方法都简洁得多。

2021 年 2 月更新

我发现 James Ward 的这篇博文和视频更好地反映了当前最先进的技术。 https://cloud.google.com/blog/topics/developers-practitioners/comparing-containerization-methods-buildpacks-jib-and-dockerfile

【问题讨论】:

既然已经有了可以自动化的构建工具,为什么还需要与 DockerHub 集成? @Thomasleveil 因为我不想运行单独的 CI 服务器来构建 docker 映像并将其推送到 DockerHub 的注册表中,因为我可以从 DockerHub 免费获得它。能够将 DockerHub 上的自动构建功能与 Java 应用程序一起使用会很棒,但我不知道如何最好地做到这一点。 如果你真的想从免费的 DockerHub 自动化服务中获利,那么看起来你必须做出妥协,让 docker 进行 Ant/Maven/Gradle 构建 关于“这意味着创建一些疯狂的链式命令”,不一定,你可以将这些命令放在一个shell脚本中,COPY将shell脚本放入你的图像中,然后使用RUN指令调用脚本→生成一层 您能否澄清一下您是想使用自己的构建服务器(“在我的构建服务器上,我想生成一个 docker 映像”)还是使用“Docker 自动构建功能”作为在上面的 cmets 中提到(因此不包括自己的基础设施)? 【参考方案1】:

docker 注册中心有一个Maven image,可用于创建 java 容器。

使用这种方法,构建机器不需要预先安装 Java 或 Maven,Docker 可以控制整个构建过程。

示例

├── Dockerfile
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── demo
    │   │           └── App.java
    │   └── resources
    │       └── log4j.properties
    └── test
        └── java
            └── org
                └── demo
                    └── AppTest.java

图像构建如下:

docker build -t my-maven .

并运行如下:

$ docker run -it --rm my-maven
0    [main] INFO  org.demo.App  - hello world

Dockerfile

FROM maven:3.3-jdk-8-onbuild
CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

更新

如果你想优化你的镜像以排除源,你可以创建一个只包含构建的 jar 的 Dockerfile:

FROM java:8
ADD target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar /opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar
CMD ["java","-jar","/opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

并分两步构建镜像:

docker run -it --rm -w /opt/maven \
   -v $PWD:/opt/maven \
   -v $HOME/.m2:/root/.m2 \
   maven:3.3-jdk-8 \
   mvn clean install

docker build -t my-app .

__

更新 (2017-07-27)

Docker 现在具有multi-stage build 功能。这使 Docker 能够构建包含构建工具但仅包含运行时依赖项的映像。

下面的例子演示了这个概念,注意jar是如何从第一个构建阶段的目标目录复制的

FROM maven:3.3-jdk-8-onbuild 

FROM java:8
COPY --from=0 /usr/src/app/target/demo-1.0-SNAPSHOT.jar /opt/demo.jar
CMD ["java","-jar","/opt/demo.jar"]

【讨论】:

@Stephane 第一个示例使用包含 Java 和 Maven 的基础映像。源代码构建在容器内。第二个示例是为了回应 Tobias 的评论,即他希望源不在容器内,需要两步过程,首先构建 jar,然后创建一个包含 Java 基础映像和构建的 jar 的容器。 @DarioBB 用于构建“maven:3.3-jdk-8-onbuild”映像的 Docker 文件将工作目录设置为 /usr/src/app 并将本地文件复制到此位置。请参阅:github.com/carlossg/docker-maven/blob/…。我是如何发现这个 Docker 文件的?从 DockerHub 链接:hub.docker.com/_/maven @DarioBB 再次查看 onbuild 映像的 Dockerfile。它将工作目录设置为“/usr/src/app”。其次,它提供了两个 ONBUILD 指令。第一个将您的源代码复制到“/usr/src/app”,第二个将运行“mvn install”。那是什么意思?您的 jar 文件将在“/usr/src/app/target”目录中创建 让我们continue this discussion in chat。 请注意,Maven 不是为共享 ~/.m2/repo 而设计的。您应该只在确定一次只运行一个实例的情况下执行此操作。【参考方案2】:

java应用的结构

Demo
└── src
|    ├── main
|    │   ├── java
|    │   │   └── org
|    │   │       └── demo
|    │   │           └── App.java
|    │   └── resources
|    │       └── application.properties
|    └── test
|         └── java
|               └── org
|                   └── demo
|                         └── App.java  
├──── Dockerfile
├──── pom.xml

Dockerfile 的内容

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

构建和运行映像的命令

进入项目目录,比如说D:/Demo
$ cd D/demo
$ mvn clean install
$ docker build demo .
$ docker run -p 8080:8080 -t demo

检查容器是否正在运行

$ docker ps

输出将是

CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                    NAMES
55c11a464f5a        demo1               "java -jar demo.jar"   21 seconds ago      Up About a minute   0.0.0.0:8080->8080/tcp   cranky_mayer

【讨论】:

【参考方案3】:

最简单的方法是让构建工具控制流程。否则,您将不得不维护构建工具的构建文件(如 Maven 的 pom.xml 或 Gradle 的 build.gradle)以及 Dockerfile

为您的 Java 应用程序构建 Docker 容器的一种简单方法是使用 Jib,它可以作为 MavenGradle 插件使用。

例如,如果您使用 Maven 并希望将容器构建到正在运行的 Docker 守护程序,则只需运行以下命令:

mvn compile com.google.cloud.tools:jib-maven-plugin:0.9.2:dockerBuild

您也可以使用 Jib build directly to a Docker registry,而无需安装 docker、运行 Docker 守护程序(需要 root 权限)或编写 Dockerfile。它也更快并且可重现地构建图像。

在 Github 存储库中查看有关 Jib 的更多信息:https://github.com/GoogleContainerTools/jib

【讨论】:

你救了我的命,你给出的命令比文档中的命令要好。【参考方案4】:

我们用了一段时间Spotify Docker Maven Plugin。该插件允许您将 Docker 构建绑定到 Maven 生命周期的某个阶段。

一个例子: 通过配置插件将构建的应用程序作为资源添加到 Docker 构建上下文中,在打包(阶段:打包)您的应用程序后运行 Docker 构建。在部署阶段运行 Docker 推送目标以将您的 Docker 映像推送到注册表。这可以在普通部署插件旁边运行,后者将工件发布到 Nexus 等存储库中。

后来,我们在 CI 服务器上将构建拆分为两个单独的作业。由于 Docker 只是运行应用程序的一种方式(有时我们需要在不同环境中发布应用程序而不仅仅是 Docker),因此 Maven 构建不应依赖 Docker。

所以第一个作业在 Nexus 中发布应用程序(通过 Maven 部署)。第二个作业(可以是第一个作业的下游依赖项)下载最新发布的工件,执行 Docker 构建并将映像推送到注册表。为了下载最新版本,我们使用Versions Maven Plugin (versions:use-latest-releases) 以及Maven Dependency Plugin (dependency:get 和dependency:copy)。

也可以为特定版本的应用程序启动第二个作业,以(重新)为旧版本构建 Docker 映像。此外,您可以使用构建管道(在 Jenkins 上),它执行这两个作业并将发布版本或发布工件传递给 Docker 构建。

【讨论】:

【参考方案5】:

使用 Jib 工具将您的 Java 应用程序容器化,而无需编写 dockerfile

Jib 是由 Google 维护的开源 Java 工具,用于构建 Java 应用程序的 Docker 映像。它简化了容器化,因为有了它,我们不需要编写 dockerfile。实际上,我们甚至不必安装 docker 就可以自己创建和发布 docker 映像。

Google 将 Jib 发布为 Maven 和 Gradle 插件。 https://github.com/GoogleContainerTools/jib

使用 Maven 项目将您的 java 应用程序容器化

https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#quickstart

使用 Gradle 项目容器化您的 Java 应用程序

https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#quickstart

【讨论】:

我更喜欢吊臂。正如 baeldung 所说:“还有一些其他工具,比如 Spotify 的 docker-maven-plugin 和 dockerfile-maven 插件,尽管前者现在已被弃用,而后者需要 dockerfile。”【参考方案6】:

有几点:

如果您在添加文件的同一条指令中删除文件,它们将不会占用映像中的空间。如果您查看官方图像的一些 Dockerfile,您会看到它们下载源代码、构建它并在同一步骤中将其全部删除(例如 https://github.com/docker-library/python/blob/0fa3202789648132971160f686f5a37595108f44/3.5/slim/Dockerfile)。这确实意味着你需要做一些烦人的体操,但这是完全可行的。

我没有看到两个单独的 Dockerfile 的问题。这样做的好处是您可以使用 JRE 而不是 JDK 来托管您的 jar。

【讨论】:

【参考方案7】:

运行 jar 或 war 包还有其他用途

将 jar 添加到图像中。 为 java 设置堆大小 通过入口点运行 jar 命令

示例 dockerfile

FROM base
ADD sample.jar renamed.jar
ENV HEAP_SIZE 256m
ENTRYPOINT exec java -Xms$HEAP_SIZE -Xmx$HEAP_SIZE -jar renamed.jar

另外在tomcat上的包部署示例

FROM tomcat7
ADD sample.war $CATALINA_HOME/webapps/ROOT.war
CMD $CATALINA_HOME/bin/catalina.sh run

将 dockerfiles 构建为图像

cp tomcat.dockerfile /workingdir/Dockerfile
docker build -t name /workingdir/Dockerfile .

列出图片

docker images

使用图片创建容器

docker run --name cont_name --extra-vars var1=val1 var2=val2 imagename

【讨论】:

【参考方案8】:

Here 我描述了我是如何在我的开发环境中做到这一点的。

使用 Maven 在本地构建 war/jar 将其复制到本地 Docker 文件夹 运行Intellij Docker plugin创建一个包含war/jar的docker镜像,运行应用服务器并将其部署到远程Docker服务器上

希望对你有帮助。

【讨论】:

以上是关于如何为 Java 应用程序构建 docker 容器的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Docker LXC 容器设置 MAC 地址?

如何为包含 kafka、postgres 和 rest api docker 容器的应用程序编写 e2e 测试自动化

如何为AWS Lambda创建和压缩docker容器

如何为在 docker 容器内运行的 corda 节点运行迁移脚本?

如何为多个docker容器使用相同的python映像?

Docker如何为企业产生价值?