概述

在Dockerfile中,我们经常会遇到runcmdentrypoint 这样的指令。乍一看,它们似乎都是用于指定并执行命令的。但是,它们之间有什么区别?又如何相互作用呢?

在本教程中,我们将回答这些问题。我们将详细介绍每个指令的功能以及它们的工作方式。我们还将探讨它们在构建镜像和运行Docker容器时所扮演的角色。

1. 配置

首先,让我们创建一个脚本log-event.sh。它只是向文件添加一行并打印出来:

#!/bin/sh

echo `date` $@ >> log.txt;
cat log.txt;

然后,让我们创建一个简单的Dockerfile:

FROM alpine
ADD log-event.sh /

我们将通过在不同场景下利用我们的脚本来向log.txt文件添加行。

2. run指令

run指令在构建镜像时执行。这意味着传入run指令的命令会在当前镜像之上创建一个新的层执行。然后结果被提交到镜像中。让我们看看实际操作是什么样子的。

首先,在我们的Dockerfile中添加一个run指令:

FROM alpine
ADD log-event.sh /
RUN ["/log-event.sh", "image created"]

然后,使用以下命令构建我们的镜像:

docker build -t myimage .

现在,我们期望有一个包含log.txt文件的Docker镜像,文件中有一行image created。让我们通过基于该镜像运行容器来检查这一点:

docker run myimage cat log.txt

列出文件内容时,我们会看到如下输出:

Fri Sep 18 20:31:12 UTC 2020 image created

如果我们多次运行容器,日志文件中的日期不会改变。这合乎逻辑,因为run步骤是在构建时间执行的,而不是在容器运行时。

现在,我们再次构建我们的镜像。注意到日志中的创建时间没有改变。这是因为Docker会缓存run指令的结果,如果Dockerfile没有改变的话。如果我们想清除缓存,我们需要在构建命令中传递--no-cache选项。

3. cmd指令

通过cmd指令,我们可以指定一个默认命令,在容器启动时执行。让我们在Dockerfile中添加一个cmd条目并看看它是如何工作的:

...
RUN ["/log-event.sh", "image created"]
CMD ["/log-event.sh", "container started"]

构建镜像后,现在让我们运行容器并检查输出:

$ docker run myimage
Fri Sep 18 18:27:49 UTC 2020 image created
Fri Sep 18 18:34:06 UTC 2020 container started

如果我们多次运行这个容器,我们会发现image created条目保持不变。但container started条目随每次运行更新。这表明cmd确实每次容器启动时都会执行。

注意我们在启动容器时使用的docker run命令略有不同。让我们看看如果使用相同的命令会发生什么:

$ docker run myimage cat log.txt
Fri Sep 18 18:27:49 UTC 2020 image created

这次,Docker文件中指定的cmd被忽略了。这是因为我们为docker run命令指定了参数。

接下来,让我们看看如果Dockerfile中有多个cmd条目会发生什么。让我们添加一个新条目显示另一个消息:

...
RUN ["/log-event.sh", "image created"]
CMD ["/log-event.sh", "container started"]
CMD ["/log-event.sh", "container running"]

构建镜像并再次运行容器后,我们将会找到以下输出:

$ docker run myimage
Fri Sep 18 18:49:44 UTC 2020 image created
Fri Sep 18 18:49:58 UTC 2020 container running

如您所见,container started条目不存在,只有container running是。这是因为如果指定了多个cmd,只会考虑最后一个。

4. entrypoint指令

正如我们上面看到的,如果在启动容器时传递任何参数,cmd会被忽略。如果我们希望有更多的灵活性呢?例如,我们想要自定义附加文本并将其作为docker run命令的参数传递。为此目的,让我们使用entrypoint。我们将指定当容器启动时要运行的默认命令,并且我们现在能够提供额外的参数。

让我们用entrypoint替换Dockerfile中的cmd条目:

...
RUN ["/log-event.sh", "image created"]
ENTRYPOINT ["/log-event.sh"]

现在,让我们通过提供自定义文本条目来运行容器:

$ docker run myimage container running now
Fri Sep 18 20:57:20 UTC 2020 image created
Fri Sep 18 20:59:51 UTC 2020 container running now

我们可以看到entrypoint的行为类似于cmd此外,它允许我们自定义启动时执行的命令。

cmd一样,如果Dockerfile中有多个entrypoint条目,只考虑最后一个。

5. cmdentrypoint之间的交互

我们已经使用了cmdentrypoint来定义运行容器时执行的命令。现在让我们看看如何组合使用cmdentrypoint

一种这样的使用场景是在entrypoint中定义默认参数。让我们在Dockerfile中将cmd条目添加到entrypoint之后:

...
RUN ["/log-event.sh", "image created"]
ENTRYPOINT ["/log-event.sh"]
CMD ["container started"]

现在,让我们运行容器而不提供任何参数,并使用cmd中指定的默认参数:

$ docker run myimage
Fri Sep 18 21:26:12 UTC 2020 image created
Fri Sep 18 21:26:18 UTC 2020 container started

我们也可以选择覆盖它们:

$ docker run myimage custom event
Fri Sep 18 21:26:12 UTC 2020 image created
Fri Sep 18 21:27:25 UTC 2020 custom event

值得注意的是,当在shell形式中使用entrypoint时的行为有所不同。让我们更新Dockerfile中的entrypoint

...
RUN ["/log-event.sh", "image created"]
ENTRYPOINT /log-event.sh
CMD ["container started"]

在这种情况下,当我们运行容器时,我们会看到Docker忽略传给docker runcmd的任何参数。

6. 总结

在这篇文章中,我们观察了Docker指令:runcmdentrypoint 之间的差异和相似之处。我们注意到了它们在何时被调用。我们也查看了它们的用途及其工作方式。 ```