面向Java开发者Docker和Kubernetes

您所在的位置:网站首页 docker打包现有环境 面向Java开发者Docker和Kubernetes

面向Java开发者Docker和Kubernetes

2023-04-15 17:43| 来源: 网络整理| 查看: 265

现在我们有了一个基于 Spring Bootstrap 的简单但功能强大的 Java 微服务,我们可以走得更远。在我们使用 Kubernetes 部署它之前,让我们将其打包为 Docker 映像。在这一章中,我们将创建一个包含我们的应用的 Docker 映像,我们将对一个 Spring Boot 应用进行 Docker 化,使其在一个隔离的环境(一个容器)中运行。

本章涵盖的主题包括:

创建文档文件 dockerfile instructions(Docker 文件说明) 塑造形象 创建和删除映像

让我们从Dockerfile的定义开始,这将是我们容器的定义。

Dockerfile

从[第一章] 01.html Docker 中的映像创建流程非常简单,基本上包括两个步骤:

首先,您准备一个名为Dockerfile的文本文件,其中包含一系列关于如何构建映像的说明。您可以在Dockerfile中使用的指令集不是很广泛,但足以完全指导 Docker 如何创建映像。 接下来,执行docker build命令,根据刚刚创建的Dockerfile创建一个 Docker 映像。docker build命令在上下文中运行。构建的上下文是指定位置的文件,可以是PATH或网址。PATH是本地文件系统上的一个目录,网址是一个 Git 存储库位置。上下文被递归处理。PATH将包括任何子目录。该网址将包括存储库及其子模块。

如果您创建了一个包含 Java 应用的映像,您也可以跳过第二步,使用一个可用的 Docker Maven 插件。在我们学习了如何使用docker build命令构建映像之后,我们还将使用 Maven 创建我们的映像。当使用 Maven 构建时,docker build命令的上下文(或者在这种情况下是构建过程)将由 Maven 自己自动提供。实际上,根本不需要Dockerfile,它会在构建过程中自动创建。我们一会儿就会谈到这个问题。

一个Dockerfile的标准名称只是Dockerfile。这只是一个纯文本文件。根据您使用的集成开发环境,有插件提供 Dockerfile 语法高亮显示和自动完成,这使得编辑它们变得轻而易举。Dockerfile 指令使用简单明了的语法,这使得它们非常容易理解、创建和使用。它们被设计成不言自明的,特别是因为它们允许注释,就像正确编写的应用源代码一样。现在让我们来了解一下Dockerfile的指示。

dockerfile instructions(Docker 文件说明)

我们将从每个 Dockerfile 在顶部必须具有的指令开始,即FROM指令。

这是 Dockerfile 中的第一条指令。它为文件中接下来的每个后续指令设置基本映像。FROM指令的语法很简单。只是。 FROM ,或FROM :,或FROM @ FROM指令以tag或digest为参数。如果你决定跳过它们,Docker 会假设你想从latest标签构建你的形象。要知道latest并不总是你想要建立的最新版本的形象。latest标签是一种特殊的标签。此外,它可能不会像您预期的那样工作。嗯,长话短说,没有什么特别的意思,除非映像创作者(openjdk或者fabric8)有特定的build、tag、push图案。分配给映像的latest标签仅仅意味着它是最后构建和执行的映像,没有提供特定的标签。很容易理解,可能会混淆,拉标签latest的图片取不到软件的最新版本。 Docker will not take care of checking if you are getting the newest version of the software when pulling the image tagged latest.

如果 Docker 在构建过程中找不到您提供的标记或摘要,它将抛出一个错误。你应该明智地选择基础形象。我的建议是总是更喜欢可以在 Docker Hub 上找到的官方存储库。通过选择一个官方映像,你可以非常确定它将是高质量的,经过测试,支持和维护的。

对于 Java 应用的容器化,我们有两种选择。第一种是使用一个基本的 Linux 映像,并使用RUN指令安装 Java(稍后我们将介绍RUN)。第二个选项是获取一个包含已经安装的 Java 运行时的映像。在这里你有更多的选择。例如:

openjdk:包含 Java 平台开源实现的官方存储库,标准版。标签latest,如果您没有指定任何标签,将会被使用,指向8u121-alpine OpenJDK 发行版,在撰写本书时 fabric8/java-alpine-openjdk8-jdk:fabric 8 Maven 插件实际上也在使用这个基础映像 frolvlad/alpine-oraclejdk8:有三个标签可以选择:full(只有 src tarballs 被移除)、clean(桌面部分被清除)、slim、除了编译器和 JVM 之外的所有东西都被移除。最新的标签指向干净的 jeanblanchard/java:包含基于 Alpine Linux 的映像的存储库,以保持最小的大小(大约是基于 Ubuntu 的映像的 25%)。标签latest指向甲骨文 Java 8(服务器 JRE)

通过在 https://hub.docker.com Docker 中心注册并创建您的帐户,您将可以访问 Docker 商店。在https://store.docker.com有售。尝试在 Docker 商店中搜索与 Java 相关的映像。你会发现很多有用的图片可供选择,其中之一就是官方的 Oracle Java 8 SE (Server JRE)图片。这个 Docker 映像提供了服务器 JRE,这是一个专门针对在服务器环境中部署 Java 的运行时环境。服务器 JRE 包括用于 JVM 监控的工具和服务器应用通常需要的工具。你可以通过在 Docker 商店购买来获得这个官方的 Java Docker 映像。点击获取内容,它的价格为 0.00 美元,因此它将免费用于您的开发目的。 Take note that images coming from the Docker Store are bound to your Docker Hub account. Before you pull them or build your own images having them as the base image, you will need to the authenticate to Docker Store using the docker login command and your Docker Hub credentials.

为了我们的目的,让我们选择jeanblanchard/java。这是运行在阿尔卑斯 Linux 发行版之上的官方甲骨文 Java。基本映像很小,下载速度也很快。我们的FROM说明将与此相同:

FROM jeanblanchard/java:8

如果在您的 Docker 主机上(例如,在您的本地机器上)找不到FROM映像,Docker 将尝试从 Docker Hub(或者您的私有存储库,如果您已经设置了的话)中找到并拉出它。Dockerfile中的所有后续说明将使用FROM中指定的映像作为基础起点。这就是为什么它是强制性的;有效的Dockerfile必须在顶部。

维护

使用MAINTAINER指令,设置生成映像的Author字段。这可以是你的名字,用户名,或者任何你想作为图片作者使用你正在写的Dockerfile创建的东西。这个命令可以放在Dockerfile中的任何地方,但是好的做法是把它放在文件的顶部,就在FROM指令之后。这是一个所谓的非执行命令,意味着它不会对生成的映像进行任何更改。语法也很简单:

MAINTAINER authors_name 工作目录

WORKDIR指令在 Dockerfile 中为其后的任何CMD、RUN、ENTRYPOINT、COPY和ADD指令添加一个工作目录。指令的语法是WORKDIR /PATH.如果提供了相对路径,一个 Dockerfile 中可以有多个WORKDIR指令;它将相对于先前WORKDIR指令的路径。

注意缺陷障碍 (Attention Deficit Disorder)

ADD基本上做的是将文件从源复制到容器自己的文件系统中的期望目标位置。它需要两个参数:源()和目的地():

ADD

源可以有两种形式:它可以是文件的路径、目录或网址。该路径是相对于将要启动构建过程的目录(我们前面提到的构建上下文)的。这意味着不能将"../../config.json"作为ADD指令的源路径参数。

源路径和目标路径可以包含通配符。这些与传统文件系统中的相同:对于任何文本字符串都是*,对于任何单个字符都是?。

例如,ADD target/*.jar /会将所有以.jar结尾的文件添加到映像文件系统的根目录中。

如果需要,可以指定多个源路径,并用逗号分隔。所有这些都必须相对于构建上下文,就像只有一个源路径一样。如果您的源路径或目标路径包含空格,您将需要使用特殊的语法,在: ADD ["" ""]

如果源路径不以结尾斜杠结束,它将被视为单个文件,并被复制到目标中。如果源路径以一个尾随斜杠结束,它将被视为一个目录:它的全部内容将被复制到目标路径,但目录本身不会在目标路径上创建。因此,正如你所看到的,当向映像添加文件或目录时,一个尾随斜线/是非常重要的。如果源路径指向一种常见格式(如 ZIP、TAR 等)的压缩归档文件,它将被解压缩到目标路径中。Docker 不能通过文件名识别档案,它会检查文件的内容。 If the archive is damaged or unreadable by Docker in any other way, it will not be extracted and you will not be given an error message. The file will just be copied into the destination path.

相同的结尾斜杠规则适用于目标路径;如果它以一个尾随斜杠结束,则意味着它是一个目录。否则,它将被视为单个文件。这为您构建映像的文件系统内容提供了极大的灵活性;您可以将文件添加到目录中,将文件作为单个文件添加(具有相同或不同的名称),或者只添加整个目录。 ADD命令不仅仅是从本地文件系统复制文件,你可以用它从网络上获取文件。如果源是一个网址,那么网址的内容将被自动下载并放在目的地。请注意,从网络下载的文件存档不会被解压缩。同样,在下载文件时,尾部斜线很重要;如果目标路径以斜杠结束,文件将被下载到目录中。否则,下载的文件将只保存在您作为目标路径提供的名称下。 要么是绝对路径,要么是相对于WORKDIR指令指定的目录的路径(我们稍后会介绍)。源(或多个源)将被复制到指定的目标。例如:

ADD config.json projectRoot/将把config.json文件添加到/projectRoot/ ADD config.json /absoluteDirectory/将把config.json文件添加到/absoluteDirectory/

当涉及到在映像中创建的文件的所有权时,它们将总是使用用户 ID ( UID ) 0和组 ID ( GID ) 0来创建。权限将与源文件中的相同,除非是从远程 URL 下载的文件:在这种情况下,它将获得权限值600(只有所有者可以读写该文件)。如果您需要更改这些值(所有权或权限),您需要在ADD指令之后的 Dockerfile 中提供更多指令。 If the files that you need to add to the image are placed on the URL that needs authentication, the ADD instruction will not work. You will need to use a shell command to download the file, such as wget or curl.

注意ADD不应该用如果不需要它的特殊功能,比如拆封档案,应该用COPY代替。

复制

COPY指令将从复制新的文件或目录,并将它们添加到路径处的容器文件系统中。

它与ADD指令非常相似,甚至语法也没有什么不同:

COPY

从ADD开始的相同规则适用于COPY:所有的源路径必须相对于构建的上下文。同样,在源路径和目标路径的末尾出现尾斜杠也很重要:如果存在,该路径将被视为文件;否则,它将被视为一个目录。

当然,和ADD一样,可以有多个源路径。如果源路径或目标路径包含空格,则需要用方括号将它们括起来:

COPY ["" ""]

是绝对路径(如果以斜杠开头),或者是相对于WORKDIR指令指定的路径的路径。

可以看到COPY的功能和ADD指令几乎一样,只有一点不同。COPY仅支持将本地文件基本复制到容器中。另一方面,ADD给出了一些更多的功能,比如档案提取、通过 URL 下载文件等等。Docker 的最佳实践是,如果你不需要ADD的附加功能,你应该更喜欢COPY。由于COPY命令的透明性,Dockerfile将更加清晰易懂。

对于ADD和COPY指令来说,有一个共同的重要方面,那就是缓存。基本上,Docker 会在构建过程中缓存进入映像的文件。检查映像中一个或多个文件的内容,并计算每个文件的校验和。在缓存查找过程中,会将校验和与现有映像中的校验和进行比较。如果文件中发生了任何变化,例如内容和元数据,则缓存将失效。否则,如果源文件没有更改,现有的映像层将被重用。 If you have multiple Dockerfile steps that use different files from your context, COPY them individually, rather than all at once. This will ensure that each step's build cache is only invalidated (forcing the step to be re-run) if the specifically required files change.

如您所见,COPY指令的语法和行为与ADD指令几乎相同,但它们的特性集却有所不同。对于不需要ADD功能的文件和目录,您应该始终使用COPY。

奔跑

RUN指令是Dockerfile的中央执行指令。本质上,RUN指令将在当前映像之上的新图层中执行一个命令 01.html

这也意味着COPY和ENTRYPOINT设置的参数可以在运行时被覆盖,所以如果你在启动你的容器后没有改变任何东西,结果将总是一样的。RUN但是,将在构建时执行,无论你在运行时做什么,它的效果都会在这里。 To make your Dockerfile more readable and easier to maintain, you can split long or complex RUN statements on multiple lines separating them with a backslash.

来自Dockerfile的RUN命令将按其出现的顺序执行。 Each RUN instruction creates a new layer in the image.

从[第 1 章] 01.html Be aware when the Docker cache needs to be invalidated, otherwise you will get unexpected results with your image builds.

这就是为什么知道如何有选择地使缓存无效是件好事。在 Docker 世界中,这被称为缓存破坏。

考虑下面的例子。大概RUN最常见的用例是apt-get的一个应用,这是一个在 Ubuntu 上下载包的包管理器命令。假设我们有以下安装 Java 运行时的 Dockerfile:

FROM ubuntu RUN apt-get update RUN apt-get install -y openjdk-8-jre

如果我们从这个Dockerfile构建一个映像,两个RUN指令的所有图层将被放入图层缓存。但是,过了一会儿,你决定想要你的映像中的node.js包,所以现在 Dockerfile 看起来和这个一样:

FROM ubuntu RUN apt-get update RUN apt-get install -y openjdk-8-jre RUN apt-get install -y nodejs

如果您第二次运行docker build,Docker 将通过从缓存中取出图层来重用这些图层。因此apt-get update不会被执行,因为会使用缓存的版本。实际上,您新创建的映像可能会有一个过时版本的java和node.js包。创建RUN指令时,应始终牢记缓存概念。在我们的例子中,我们应该总是在同一个RUN语句中组合RUN apt-get update和apt-get install,这将只创建一个单层;例如:

RUN apt-get update \ && apt-get install -y openjdk-8-jre \ && apt-get install -y nodejs \ && apt-get clean

比这更好的是,您还可以使用一种称为“版本锁定”的技术来避免缓存问题。它只不过是为您想要安装的软件包提供一个特定的、具体的版本。

煤矿管理局

CMD指令的目的是为正在执行的容器提供默认值。稍后运行容器时,您可以将CMD指令视为映像的起点。这可以是一个可执行文件,或者,如果您指定了ENTRYPOINT指令(我们接下来将解释),您可以省略可执行文件,只提供默认参数。CMD指令语法可以有两种形式:

CMD ["executable","parameter1","parameter2"]:这是一个所谓的exec形态。这也是首选和推荐的形式。参数是 JSON 数组,需要用方括号括起来。重要的是,当容器运行时,exec表单不会调用命令外壳。它只是运行作为第一个参数提供的可执行文件。如果Dockerfile中存在ENTRYPOINT指令,CMD为ENTRYPOINT指令提供一组默认参数。 CMD command parameter1 parameter2:这是指令的外壳形式。这一次,shell(如果映像中有)将处理提供的命令。指定的二进制文件将通过使用/bin/sh -c调用外壳来执行。这意味着如果您显示容器的主机名,例如,使用CMD echo $HOSTNAME,您应该使用指令的外壳形式。

我们之前说过CMD指令的推荐形式是exec形式。原因如下:通过 shell 启动的一切都将作为/bin/sh -c的子命令启动,不传递信号。这意味着可执行文件不会是容器的 PID 1,也不会收到 Unix 信号,所以你的可执行文件不会收到来自docker stop 的SIGTERM。还有一个缺点:你的容器中需要一个外壳。如果你正在构建一个最小的映像,它不需要包含一个外壳二进制文件。使用外壳形式的CMD指令将会失败。 When Docker is executing the command, it doesn't check if the shell is available inside the container. If there is no /bin/sh in the image, the container will fail to start.

另一方面,如果我们将CMD更改为exec形式,Docker 将寻找名为echo的可执行文件,这当然会失败,因为echo是一个 shell 命令。

因为CMD与 Docker 引擎在运行容器时的起点相同,所以一个 Dockerfile 中只能有一条CMD指令。 If there are more than one CMD instruction in a Dockerfile, only the last one will take effect.

您可能会注意到CMD指令与RUN非常相似。它们都可以运行任何命令(或应用)。有一个关键的重要区别:执行的时间。通过RUN指令提供的命令在构建期间执行,而通过CMD指令指定的命令在通过对新创建的映像执行docker run启动容器时执行。与CMD不同的是,RUN指令实际上用于构建映像,在之前提交的图层上创建一个新图层。 RUN is a build-time instruction, the CMD is a runtime instruction.

信不信由你,我们现在可以将我们的 REST 示例微服务容器化了。让我们通过在[第 4 章] 04.html)、创建 Java 微服务中创建的pom.xml文件上执行mvn clean install来检查它是否构建。成功构建后,我们应该创建一个带有rest-example-0.1.0.jar文件的目标目录。target目录中的 Spring Boot 应用 JAR 是一个可执行的胖 JAR。我们将从 Docker 容器中运行它。让我们使用我们已经知道的命令编写基本的Dockerfile并将其放在我们项目的根中(这将是我们的docker build命令的上下文

FROM jeanblanchard/java:8 COPY target/rest-example-0.1.0.jar rest-example-0.1.0.jar CMD java -jar rest-example-0.1.0.jar

我们现在可以运行docker build命令,使用rest-example作为映像名称,省略标签(正如您将记得的,在构建映像时省略标签将导致创建latest标签):

$ docker build . -t rest-example

作为第一个参数的点指定了docker build命令的上下文。在我们的例子中,它将只是我们的小型微服务的根目录。在构建过程中,Docker 将输出所有的步骤和层标识。请注意,几乎每个Dockerfile指令都会创建一个新层。如果你还记得[第一章] 01.html

已经创建了一个映像,因此它应该出现在可运行的映像上。要列出映像,请执行以下 Docker 命令:

$ docker image ls

从下面的截图中可以看到,我们的rest-example映像已经出现并准备运行:

目前为止,一切顺利。我们已经建立了基本的形象。虽然运行映像的过程是[第 6 章] 06.html

$ docker run -it rest-example

过一会儿,您应该会看到熟悉的 Spring Boot 横幅,作为我们的服务正在 Docker 容器内部运行的标志:

这不是很棘手,对吧?基本的Dockerfile只包含三行,使用FROM、COPY将可执行 jar 传输到映像文件系统的基本映像定义,以及运行服务的CMD指令。

使用 Maven 构建一个应用 jar 档案,然后使用 Dockerfile COPY指令复制它就可以了。将构建过程委托给 Docker 守护程序本身怎么样?嗯,我们可以用我们已经知道的Dockerfile指令来做。使用 Docker 守护程序构建 Java 应用的缺点是,该映像将包含所有的 JDK(包括 Java 编译器)、Maven 二进制文件和我们的应用源代码。我建议构建一个单独的工件(一个 JAR 或 WAR 文件),彻底测试它(使用面向发布的 QA 周期),并将唯一的工件(当然还有它的依赖项)部署到目标机器上。然而,要想知道Dockerfile有什么可能,请看下面的例子,假设我们的应用代码在本地磁盘的/app文件夹中:

FROM java:8 RUN apt-get update RUN apt-get install -y maven WORKDIR /app COPY pom.xml /app/pom.xml COPY src /app/src RUN ["mvn", "package"] CMD ["/usr/lib/jvm/java-8-openjdk-amd64/bin/java", "-jar", "target/ rest-example-0.1.0.jar"]

在前面的例子中,Maven 构建过程将由 Docker 执行。我们只需运行apt-get命令安装 Maven,将我们的应用源代码添加到映像中,执行 Maven package命令,然后运行我们的服务。它的行为将完全相同,就好像我们只是将已经构建好的工件复制到映像的文件系统中一样。

有一个与CMD指令有点关联的 Dockerfile 指令:ENTRYPOINT。我们现在来看看。

ENTRYPOINT

官方 Docker 文档称ENTRYPOINT指令允许您配置一个作为可执行文件运行的容器。不太清楚,至少第一次。ENTRYPOINT指令与CMD指令相关。事实上,一开始可能会令人困惑。原因很简单:CMD是先开发的,然后ENTRYPOINT是为了更多的定制而开发的,这两条指令之间有些功能重叠。让我们解释一下。ENTRYPOINT指定了一个在容器启动时将一直执行的命令。另一方面,CMD指定将被馈送到ENTRYPOINT的参数。Docker 有一个默认的ENTRYPOINT,即/bin/sh -c,但没有默认的CMD。例如,考虑以下 Docker 命令:

docker run ubuntu "echo" "hello world"

在这种情况下,映像将是最新的ubuntu,ENTRYPOINT将是默认的/bin/sh -c,传递给ENTRYPOINT的命令将是echo "hello world"。 ENTRYPOINT指令的语法可以有两种形式,类似于CMD。 ENTRYPOINT ["executable", "parameter1", "parameter2"]是exec的形态,首选,推荐。与CMD指令的exec形式完全相同,这不会调用命令外壳。这意味着正常的外壳处理不会发生。例如,ENTRYPOINT [ "echo", "$HOSTNAME" ]不会对$HOSTNAME变量进行变量替换。如果您想要 shell 处理,那么您需要使用 shell 表单或者直接执行 shell。例如:

ENTRYPOINT [ "sh", "-c", "echo $HOSTNAME" ]

使用ENV在 Dockerfile 中定义的变量(我们稍后将介绍这一点)将被 Dockerfile 解析器替换。 ENTRYPOINT command parameter1 parameter2是一个一个的贝壳形态。将进行正常的外壳处理。该表单还将忽略任何CMD或docker run命令行参数。另外,您的命令不会是 PID 1,因为它将由 shell 执行。因此,如果您接着run docker stop ,容器将不会干净地退出,并且停止命令将被迫在超时后发送一个SIGKILL。

与CMD指令完全相同,只有 Dockerfile 中的最后一条ENTRYPOINT指令才会生效。在 Dockerfile 中覆盖ENTRYPOINT允许您在容器运行时使用不同的命令处理您的参数。如果需要更改映像中的默认外壳,可以通过更改ENTRYPOINT来实现:

FROM ubuntu ENTRYPOINT ["/bin/bash"]

从现在开始,所有来自CMD的参数,或者使用docker run启动容器时提供的参数,将由 Bash shell 处理,而不是默认的/bin/sh -c。

考虑这个基于 BusyBox 的简单Dockerfile。BusyBox 是一种在单个可执行文件中提供几个精简的 Unix 工具的软件。为了演示ENTRYPOINT,我们将使用 BusyBox 中的ping命令:

FROM busybox ENTRYPOINT ["/bin/ping"] CMD ["localhost"]

让我们通过执行以下命令,使用前面的 Dockerfile 构建映像:

$ docker build -t ping-example .

如果您现在使用ping映像运行容器,则ENTRYPOINT指令将处理来自所提供的CMD参数的参数:在我们的例子中,默认为localhost。让我们使用以下命令运行它:

$ docker run ping-example

因此,您将得到一个/bin/ping localhost命令行响应,如下图所示:

CMD指令,正如您将从其描述中记得的,设置默认命令和/或参数,当您运行容器时,可以从命令行覆盖这些命令和/或参数。ENTRYPOINT不同,它的命令和参数不能用命令行覆盖。取而代之的是,所有的命令行参数将被附加在ENTRYPOINT参数之后。这样,您可以锁定将在容器启动期间始终执行的命令。 Unlike the CMD parameters, the ENTRYPOINT command and parameters are not ignored when a Docker container runs with command-line parameters.

因为命令行参数将被附加到ENTRYPOINT参数上,我们可以用传递给ENTRYPOINT的不同参数来运行我们的ping映像。让我们尝试一下,用不同的输入运行 ping 示例:

$ docker run ping-example www.google.com

这一次它的行为会有所不同。提供的参数值www.google.com将被追加到ENTRYPOINT中,而不是 Dockerfile 中提供的默认CMD值。将执行的总命令行为/bin/ping www.google.com,如下图所示:

You can use the exec form of ENTRYPOINT to set fairly stable default commands and arguments and then use either form of CMD to set additional defaults that are more likely to be changed.

拥有ENTRYPOINT指令给了我们很大的灵活性。最后但并非最不重要的是,当使用docker run命令的--entrypoint参数启动容器时,ENTRYPOINT也可以被覆盖。请注意,您可以使用--entrypoint覆盖ENTRYPOINT设置,但这只能将二进制设置为执行(不使用sh -c)。如您所见,CMD和ENTRYPOINT指令都定义了在运行容器时执行什么命令。让我们总结一下我们对差异及其合作的了解:

一个 Dockerfile 应该至少指定一个CMD或ENTRYPOINT指令 只会使用文件中的最后一个CMD和ENTRYPOINT 当将容器用作可执行文件时,应定义ENTRYPOINT 您应该使用CMD指令来定义定义为ENTRYPOINT的命令或在容器中执行ad-hoc命令的默认参数 当使用替代参数运行容器时,CMD将被覆盖 ENTRYPOINT设置每次使用映像创建容器时使用的具体默认应用 如果你将ENTRYPOINT和CMD连接起来,你可以从CMD中移除一个可执行文件,并留下它的参数,这些参数将被传递给ENTRYPOINT ENTRYPOINT的最佳用途是设置映像的主命令,允许该映像像该命令一样运行(然后使用CMD作为默认标志)

嗯,我们的服务运行得很好,但不是很有用。首先,它需要很多手动步骤才能运行,这就是为什么我们将在本章稍后使用 Maven 来自动化它。其次,正如您所记得的,我们的服务监听端口号8080上传入的HTTP请求。我们的基本映像可以运行,但不会暴露任何网络端口,因此没有人或任何东西可以访问该服务。让我们继续学习剩余的 Dockerfile 指令来修复它。

揭露

EXPOSE指令通知 Docker,容器在运行时监听指定的网络端口。我们已经在[第 2 章] 02.html

让我们回到我们的Dockerfile并暴露一个端口:

FROM jeanblanchard/java:8 COPY target/rest-example-0.1.0.jar rest-example-0.1.0.jar CMD java -jar rest-example-0.1.0.jar EXPOSE 8080

如果您现在使用相同的命令docker build . -t rest-example重新构建映像,您会注意到 Docker 输出了第四层,表示端口8080已经暴露。公开的端口将可用于这个 Docker 主机上的其他容器,如果您在运行时映射它们,也可用于外部世界。好吧,让我们试试看,使用以下docker run命令:

$ docker run -p 8080:8080 -it rest-example

如果您现在像我们在[第 4 章] 04.html)、创建 Java 微服务中所做的那样,用POST(用于保存我们的图书实体)或GET(用于获取图书列表或单本图书

从[第 1 章] 01.html)、对 Docker 的介绍中,您会记得,默认情况下,容器文件系统是临时的。如果您启动一个 Docker 映像(也就是运行容器),您将在层栈的顶部得到一个读写层。您可以根据需要创建、修改和删除文件,然后提交图层以保存更改。在[第 2 章](02.html

语法再简单不过了:只是VOLUME ["/volumeName"]。 VOLUME的参数可以是 JSON 数组,一个带有一个或多个参数的普通字符串。例如:

VOLUME ["/var/lib/tomcat8/webapps/"] VOLUME /var/log/mongodb /var/log/tomcat

VOLUME指令用指定的名称创建一个装载点,并将其标记为保存从本地主机或其他容器外部装载的卷。 VOLUME命令将在您的容器中装入一个目录,并将该目录中创建或编辑的任何文件存储在容器文件结构之外的主机磁盘上。使用Dockerfile中的VOLUME让我们知道某个目录包含永久数据。Docker 将为该数据创建一个卷,并且永远不会删除它,即使您删除了所有使用它的容器。它还绕过了联合文件系统,因此该卷实际上是一个实际的目录,在所有共享它的容器中以读写或只读方式装载到正确的位置(例如,如果它们是用--volumes-from选项启动的)。为了理解VOLUME,让我们来看看简单的 Dockerfile:

FROM ubuntu VOLUME /var/myVolume

如果您现在运行您的容器并将一些文件保存在/var/myVolume中,它们将可供其他容器共享。

基本上VOLUME和-v几乎相等。VOLUME和-v的区别在于,您可以动态使用-v,并通过执行docker run在启动时将您的host目录挂载到您的容器上。原因是 Dockerfiles 意味着可移植和共享。主机目录卷 100%依赖于主机,并且会在任何其他机器上崩溃,这有点偏离 Docker 的想法。因此,只能在 Dockerfile 中使用可移植指令。 The fundamental difference between VOLUME and -v is this: -v will mount existing files from your operating system inside your Docker container and VOLUME will create a new, empty volume on your host and mount it inside your container.

标签

要将元数据添加到我们的映像中,我们使用LABEL指令。单个标签是键值对。如果标签值中需要有空格,则需要用一对引号将它括起来。标签是附加的,它们包括从作为你自己的映像基础的映像中获取的所有标签(来自FROM指令的标签)。如果 Docker 遇到已经存在的标签,它将使用新值覆盖具有相同关键字的标签。定义标签时,您必须遵守一些规则:键只能由小写字母数字字符、点和破折号组成,并且必须以字母数字字符开始和结束。为了防止命名冲突,Docker 建议使用名称空间来标记使用反向域表示法的键。另一方面,没有名称空间(点)的键被保留用于命令行。 LABEL指令的语法很简单:

LABEL "key"="value"

要获得多行值,请用反斜杠分隔行;例如:

LABEL description="This is my \ multiline description of the software."

一张映像中可以有多个标签。用空格或反斜杠分隔它们;例如:

LABEL key1="value1" key2="value2" key3="value3" LABEL key1="value1" \ key2="value2" \ key3="value3"

实际上,如果你需要在你的映像中有多个标签,建议使用LABEL指令的多标签形式,因为这将导致映像中只有一个附加层。 Each LABEL instruction creates a new layer. If your image has many labels, use the multiple form of the single LABEL instruction.

如果您想检查映像有哪些标签,请使用您在前面章节中已经知道的docker inspect命令。

包封/包围(动词 envelop 的简写)

ENV是将环境变量设置为值的Dockerfile指令。使用ENV有两种选择:

第一个,ENV ,将单个变量设置为一个值。第一个空格后的整个字符串将被视为。这将包括任何字符,以及空格和引号。例如:ENV JAVA_HOME /var/lib/java8 第二个带等号的是ENV =。该表单允许一次设置多个环境变量。如果需要在值中提供空格,则需要使用引号。如果值中需要引号,请使用反斜杠:ENV CONFIG_TYPE=file CONFIG_LOCATION="home/Jarek/my \app/config.json"

请注意,您可以使用ENV更新PATH环境变量,然后CMD参数将知道该设置。这将使Dockerfile中的CMD参数更加清晰。例如,设置以下内容:

ENV PATH /var/lib/tomcat8/bin:$PATH

这将确保CMD ["startup.sh"]工作,因为它会在系统PATH中找到startup.sh文件。您也可以使用ENV设置经常修改的版本号,以便升级更容易处理,如下例所示:

ENV TOMCAT_VERSION_MAJOR 8 ENV TOMCAT_VERSION 8.5.4 RUN curl -SL http://apache.uib.no/tomcat/tomcat-$TOMCAT_VERSION_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz | tar zxvf apache-tomcat-$TOMCAT_VERSION.tar.gz -c /usr/Jarek/apache-tomcat-$TOMCAT_VERSION ENV PATH /usr/Jarek/apache-tomcat-$TOMCAT_VERSION/bin:$PATH

在前面的例子中,Docker 将下载ENV变量中指定的 Tomcat 版本,将其提取到名称中带有该版本的新目录中,并且还将系统PATH设置为可供运行。

当从结果映像运行容器时,使用ENV设置的环境变量将保持不变。与使用LABEL创建的标签相同,您可以使用docker inspect命令查看ENV值。使用docker run --env =,也可以在容器启动前覆盖ENV值。

用户

USER指令设置运行映像时要使用的用户名或 UID。它将影响用户接下来在Dockerfile中的任何RUN、CMD和ENTRYPOINT指令。

指令的语法只是USER ;例如:

USER tomcat

如果可执行文件可以在没有权限的情况下运行,您可以使用USER命令。Dockerfile 可以包含与此相同的用户和组创建指令:

RUN groupadd -r tomcat && useradd -r -g tomcat tomcat

频繁地来回切换用户将会增加结果映像中的层数,并且还会使 Dockerfile 更加复杂。

在docker build命令期间,ARG指令被用于向 Docker 守护程序传递参数。ARG变量定义从Dockerfile中定义它的那一行开始生效。通过使用--build-arg开关,您可以为定义的变量赋值:

$ docker build --build-arg = .

来自--build-arg的值将被传递给构建映像的守护进程。您可以使用多个ARG指令指定多个参数。如果您指定了未使用ARG定义的构建时间参数,构建将失败并出现错误,但是可以在Dockerfile中指定默认值。您可以这样指定默认参数值:

FROM ubuntu ARG user=jarek

如果在开始生成之前没有指定参数,将使用默认值: It is not recommended to use ARG for passing secrets as GitHub keys, user credentials, passwords, and so on, as all of them will be visible to any user of the image by using the docker history command!

构建时

ONBUILD指令指定了一个附加指令,当使用该映像作为其基础映像构建其他映像时,该指令将被触发。换句话说,ONBUILD指令是父母Dockerfile给孩子Dockerfile(下游构建)的指令。任何构建指令都可以注册为触发器,这些指令将在Dockerfile中的FROM指令之后立即被触发。 ONBUILD指令的语法如下:

ONBUILD

在这其中,是另一个 Dockerfile 构建指令,它将在稍后构建子映像时被触发。有一些限制:ONBUILD指令不允许链接另一条ONBUILD指令,也不允许FROM和MAINTAINER指令作为ONBUILD触发器。

如果您正在构建一个将用作构建其他映像基础的映像,这将非常有用。例如,可以使用特定于用户的配置定制的应用构建环境或守护程序。ONBUILD指令非常有用(https://docs.docker.com/engine/reference/builder/#onbuild和https://docs . docker . com/engine/reference/builder/# maintainer-弃用),用于自动构建您选择的软件栈。考虑下面关于 Maven 和构建 Java 应用的例子(是的,Maven 也可以作为 Docker 容器使用)。基本上,您的项目的 Dockerfile 需要做的就是引用包含ONBUILD指令的基本容器:

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

没有魔法,只要你查看父母的档案,一切都会变得清晰。在我们的例子中,它将是一个在 GitHub 上可用的docker-maven Dockerfile:

FROM maven:3-jdk-8 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ONBUILD ADD . /usr/src/app ONBUILD RUN mvn install

有一个安装了 Java 和 Maven 的基础映像,以及一系列复制文件和运行 Maven 的指令。 ONBUILD指令向映像添加了一条触发指令,当映像用作另一个构建的基础时,该指令将在以后执行。触发器将在子构建的上下文中执行,就好像它是在子Dockerfile中的FROM指令之后立即插入的一样。

当 Docker 在构建过程中遇到ONBUILD指令时,构建器会向正在构建的映像的元数据添加一种触发器。但这是影响这一形象的唯一方式。在构建结束时,所有触发器的列表存储在映像清单中的OnBuild键下。你可以看到他们使用docker inspect命令,我们已经知道了。

稍后,可以使用FROM指令,将映像用作新构建的基础。作为处理FROM指令的一部分,Docker builder 查找ONBUILD触发器,并按照注册它们的相同顺序执行它们。如果任何一个触发器失败,则FROM指令被中止,这将使构建失败。如果所有触发器都成功,则FROM指令完成,构建继续。

停止信号

要指定应该向容器发送什么系统调用信号来退出,请使用STOPSIGNAL指令。该信号可以是与内核的syscall表中的位置匹配的有效无符号数字:例如9,或者是格式为SIGNAME的信号名称,例如SIGKILL。

健康检查

HEALTHCHECK指令可用于通知 Docker 如何测试容器,以检查其是否仍在工作。这可以是检查我们的 rest 服务是响应HTTP呼叫还是只监听指定端口。

一个容器可以有几种状态,可以使用docker ps命令列出。这些可以是created、restarting、running、paused、exited或dead。但有时这还不够;从 Docker 的角度来看,一个容器可能仍然是活动的,但是应用可能会以其他方式挂起或失败。对应用状态的额外检查会很有用,HEALTHCHECK会派上用场。 HEALTHCHECK状态开始。只要健康检查通过,它就会变成healthy(无论它之前处于什么状态)。连续失败一定次数后,就变成了unhealthy。 HEALTHCHECK指令的语法如下:

HEALTHCHECK --interval= --timeout= CMD

(默认值为 30 秒)和(同样,默认值为 30 秒)是时间值,相应地指定检查间隔和超时。是用于检查应用是否仍在运行的命令。Docker 正在使用的退出代码来确定健康检查是失败还是成功。这些值可以是0,表示容器是健康的,可以使用;也可以是1,表示有问题,容器工作不正常。Java 微服务healthcheck实现可能只是一个简单的/ping REST 端点,返回任何东西(作为时间戳),甚至返回一个空响应,其中带有证明它是活动的HTTP 200状态代码。我们的HEALTHCHECK可以在这个端点上执行GET方法,检查服务是否响应:

HEALTHCHECK --interval=5m --timeout=2s --retries=3 CMD curl -f http://localhost/ping || exit 1

在前面的例子中,命令curl -f http://localhost/ping将每 5 分钟执行一次,最长超时为 2 秒。如果单次检查耗时超过 2 秒,则认为检查失败。如果连续三次重试失败,容器将获得unhealthy状态。 There can only be one HEALTHCHECK instruction in a Dockerfile. If you list more than one then only the last HEALTHCHECK will take effect. HEALTCHECK指令为您提供了微调容器监控的可能性,从而确保您的容器工作正常。比只是running、exited或者dead标准的 Docker 状态要好。

既然我们已经理解了Dockerfile指令,我们就准备好我们的映像了。让我们把事情自动化一点。我们将使用 Maven 创建和运行我们的映像。

使用 Maven 创建映像

自然,我们可以使用 Docker 本身来构建我们的 Docker 映像。但这不是 Spring 开发人员的典型用例。我们的一个典型用例是使用 Maven。如果您设置了连续的集成流程,例如使用 Jenkins,这可能特别有用。将映像构建过程委托给 Maven 给了你很大的灵活性,也节省了很多时间。GitHub 上至少有两个免费的 Docker Maven 插件,例如:

https://github.com/spotify/docker-maven-plugin:一个由 Spotify 构建和推送 Docker 图片的 Maven 插件。 https://github . com/aleexec/dock-maven 插件。 https://github.com/fabric8io/docker-maven-plugin:这是我觉得最有用最可配置的一个。在撰写本文时,Docker 的所有 Maven 插件中,Fabric8 似乎是最健壮的。Fabric8 是一个集成的开源 DevOps 和集成平台,可以在任何 Kubernetes 或 OpenShift 环境下开箱即用地工作,并提供持续交付、管理、ChatOps 和混沌猴子。我们将在本章的剩余部分使用这个。

我们的用例将使用 Maven 打包 Spring Boot 可执行 JAR,然后将构建工件复制到 Docker 映像中。将 Maven 插件用于 Docker 主要集中在两个方面:

构建和推送包含构建工件的 Docker 映像 启动和停止用于集成测试和开发的 Docker 容器。这就是我们将在[第 6 章] 06.html

现在让我们从插件目标和可能的配置选项开始,专注于创建一个映像。 fabric8 Docker 插件提供了几个 Maven 目标:

docker:build:这使用来自 maven-assembly-plugin 的程序集描述符格式来指定将从映像中的子目录添加的内容(默认情况下为/maven) docker:push:用这个插件构建的映像可以被推送到公共或私有的 Docker 注册表中 docker:start和docker:stop:用于启动和停止容器 docker:watch:这将依次执行docker:build和docker:run。它可以永远在后台运行(单独的控制台),除非你用 CTRL+C 停止它。它可以观察程序集文件的变化并重新运行构建。它节省了很多时间 docker:remove:这是用来清理映像和容器的 docker:logs:这会打印出正在运行的容器的输出 docker:volume-create和docker:volume-remove:分别用于创建和删除卷。我们将在本章的后面部分回到这些

在我们运行这些目标之前,我们需要指示插件应该如何运行。我们在项目的pom.xml文件中的插件配置中这样做:

maven docker 外挂程式组态

插件定义中的重要部分是元素。这是你设置插件行为的地方。中有两个主要元素:

指定如何构建映像的配置 描述如何创建和启动容器的配置

下面是 Docker 的fabric8 Maven 插件配置的一个最简单的例子:

io.fabric8 docker-maven-plugin 0.20.1 http://127.0.0.1:2375 true rest-example:$ Dockerfile artifact

指定了正在运行的 Docker 引擎的 IP 地址和端口,所以当然,要构建它,您需要首先运行 Docker。在前面的例子中,如果你从外壳运行mvn clean package docker:build命令,Fabric8 Docker 插件将使用你提供的Dockerfile来构建映像。但是还有另外一种建立形象的方式,完全不用Dockerfile,至少不用明确定义。为此,我们需要稍微改变一下插件配置。看看修改后的配置:

rest-example:$ rest-example jeanblanchard/java:8 artifact java -jar maven/$-${project.version}.jar

如你所见,我们不再提供Dockerfile。相反,我们只是提供Dockerfile指令作为插件配置元素。这非常方便,因为我们不再需要硬编码可执行的 jar 名称、版本等。它将取自 Maven 构建范围。例如,将为元素提供罐子的名称。这将导致自动在Dockerfile中生成有效的CMD指令。如果我们现在使用mvn clean package docker:build命令构建项目,Docker 将使用我们的应用构建一个映像。让我们按字母顺序列出可供我们使用的配置元素: | 元素 | 描述 | | assembly | 元素定义了如何构建工件和其他可以进入 Docker 映像的文件。您可以使用targetDir元素来提供一个目录,在该目录下包含在组件中的文件和工件将被复制到映像中。这个的默认值是/maven。在我们的示例中,我们将使用来提供预定义的装配描述符之一。是一种便捷的捷径,可以取以下值:

artifact-with-dependencies:附加一个项目的工件及其所有依赖项。此外,当目标目录中存在类路径文件时,它将被添加到。 artifact:只附加项目的工件,不附加依赖项。 project:附加整个 Maven 项目,但没有target/目录。 rootWar:将神器复制为ROOT.war到exposed目录。例如,Tomcat 然后可以在root上下文下部署战争。 | | buildArgs | 允许提供指定 Docker buildArgs值的映射,当使用使用构建参数的外部 Dockerfile 构建映像时,应使用该映射。键值语法与定义 Maven 属性(或labels或env)时相同。 | | buildOptions | 指定构建映像时提供给 Docker 守护程序的构建选项的映射。 | | cleanup | 这有助于在每次构建后清理未标记的映像(包括从它们创建的任何容器)。默认值是try,它试图删除旧的映像,但是如果这是不可能的,则不会导致构建失败,例如,因为该映像仍由正在运行的容器使用。 | | cmd | 这相当于我们已经知道的CMD指令,用于提供默认执行的命令。 | | compression | 可以取none(默认)、gzip或bzip2值。它允许我们指定压缩模式以及如何将构建档案传输到 Docker 守护程序(docker:build)。 | | entryPoint | 相当于 Dockerfile 中的ENTRYPOINT。 | | env | 相当于 Dockerfile 中的ENV。 | | from | 相当于 Dockerfile 中的FROM,用于指定基础映像。 | | healthCheck | 相当于 Dockerfile 中的HEALTHCHECK。 | | labels | 用于定义标签,与 Dockerfile 中的LABEL相同。 | | maintainer | 相当于 Dockerfile 中的MAINTAINER。 | | nocache | 用于禁用 Docker 的构建层缓存。这可以通过在运行 Maven 命令时设置系统属性docker.nocache来覆盖。 | | optimize | 如果设置为真,那么它将把所有的runCmds压缩成一个单一的RUN指令。强烈建议尽量减少创建的映像层数。 | | ports | 相当于 Dockerfile 中的EXPOSE。这是一个元素列表,每个端口暴露一个。格式可以是纯数字的"8080"或附加协议的"8080/tcp"。 | | runCmds | 相当于RUN,在构建过程中要运行的命令。它包含将被传递到外壳的元素。 | | tags | 可以包含一个元素列表,以提供映像在构建后要标记的附加标签。 | | user | 相当于 Dockerfile 中的USER,它指定 Dockerfile 应该切换到的用户。 | | volumes | 包含一个等效的VOLUME列表,一个创建容器体积的元素列表。 | | workdir | 相当于从 docker fileWORKDIR开始,启动容器时要变成的目录。 |

正如您所看到的,插件配置非常灵活,它包含了一套完整的 Dockerfile 指令的等价物。让我们看看我们的pom.xml在适当的配置下会是什么样子。

完整的pom.xml。

如果您从一开始就关注我们的项目,完整的 Maven POM 如下所示:

4.0.0 pl.finsys rest-example 0.1.0 org.springframework.boot spring-boot-starter- parent 1.5.2.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data- jpa org.hibernate hibernate-validator org.hsqldb hsqldb runtime io.springfox springfox-swagger2 2.6.1 io.springfox springfox-swagger-ui 2.5.0 org.springframework.boot spring-boot-starter- test test org.springframework.boot spring-boot-starter- test test com.jayway.jsonpath json-path test 1.8 org.springframework.boot spring-boot-maven- plugin org.springframework.boot spring-boot-maven- plugin io.fabric8 docker-maven-plugin 0.20.1 rest-example:$ rest-example openjdk:latest artifact java -jar maven/${project.name}-${project.version}.jar Hello World! spring-releases https://repo.spring.io/libs-release spring-releases https://repo.spring.io/libs-release 塑造形象

要使用我们的 Spring Boot 工件构建 Docker 映像,请运行以下命令:

$ mvn clean package docker:build

clean告诉 Maven 删除target目录。Maven 将始终使用package命令编译您的类。用docker:build命令运行package命令非常重要。如果您尝试分两步运行这些程序,将会遇到错误。构建 Docker 映像时,您将在控制台中看到以下输出:

新映像的标识将显示在控制台输出中。如果你想知道自动生成的 Dockerfile 看起来如何,你会在你的项目中的target/docker/rest-example/0.1.0/build目录中找到它。第一次构建 Docker 映像时,由于所有图层都在下载中,因此需要更长的时间。但是由于层缓存,每次构建都会快很多。

创建和删除卷

如果没有管理卷的可能性,Fabric8 Maven Docker 插件就不可能是一个完整的解决方案。事实上,它提供了两种处理卷的方法:docker:volume-create和docker:volume-remove。您可能还记得[第 2 章] 02.html

[...] myVolume local tmpfs tmpfs size=100m,uid=1000 true

在前面的示例中,我们使用本地文件系统驱动程序创建了一个命名卷。它可以在容器启动时安装,如pom.xml文件的部分所述。

摘要

在本章中,我们了解了如何开始使用 Docker 容器和打包 Java 应用。我们可以使用docker build命令和Dockerfile手动完成,或者我们可以使用 Maven 来自动化事情。对于 Java 开发人员来说,Docker 有助于在干净的环境中隔离我们的应用。隔离很重要,因为它降低了我们使用的软件环境的复杂性。Fabric8 Maven Docker 插件是一个很好的工具,我们可以使用它来使用 Maven 自动构建我们的映像,尤其是在处理 Java 应用时。不再手工编写 Dockerfiles,我们只是使用大量选项配置插件,我们就完成了。此外,通过与 Maven 合作,我们可以轻松地将 Docker 构建合并到我们现有的开发流程中,例如使用 Jenkins 的持续交付。在[第 6 章] 06.html

文章列表 面向Java开发者Docker和Kubernetes-一、Docker 简介 面向Java开发者Docker和Kubernetes-七、Kubernetes 简介 面向Java开发者Docker和Kubernetes-三、使用微服务 面向Java开发者Docker和Kubernetes-九、使用 Kubernetes API 面向Java开发者Docker和Kubernetes-二、网络和持久存储 面向Java开发者Docker和Kubernetes-五、使用 Java 应用创建映像 面向Java开发者Docker和Kubernetes-八、在 Java 中使用 Kubernetes 面向Java开发者Docker和Kubernetes-六、使用 Java 应用运行容器 面向Java开发者Docker和Kubernetes-十、在云中的 Kubernetes 上部署 Java 面向Java开发者Docker和Kubernetes-十一、更多资源 面向Java开发者Docker和Kubernetes-四、创建 Java 微服务 面向Java开发者Docker和Kubernetes-零、前言 面向Java开发者Docker和Kubernetes-面向 Java 开发者的 Docker 和 Kubernetes 教程


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3