使用 Dockerfile 中的 ENTRYPOINT 和 CMD 指令

如果您曾经需要在启动时在Docker容器中运行一两个命令,那么这个教程适合您。使用Dockerfile ENTRYPOINTCMD指令,您可以运行尽可能多的启动命令。

在本教程中,您将学习如何使用ENTRYPOINTCMD指令在Dockerfile中运行启动命令,并了解它们之间的区别。

先决条件

由于本教程将进行实际演示,请确保您已经做好了以下准备:

  • A Windows 10 PC – Windows 10 v10.0.19042 was used in this tutorial.
  • Docker桌面版 – 本教程使用Docker桌面版v3.3.1。

创建Dockerfile

在运行Docker容器启动命令之前,您必须首先创建一个Dockerfile。Dockerfile是一个文本文档,包含构建容器的命令列表,Docker镜像的命令,并确定如何创建Docker镜像。

1. 首先,以管理员身份打开PowerShell

2. 创建一个新文件夹来存储 Dockerfile 和本教程将使用的所有相关文件,并切换到该目录。本教程使用 ~/docker

mkdir ~/docker
cd docker

3. 现在,使用以下命令创建一个空白文本文件并命名为 Dockerfile

cd > Dockerfile

或者,如果您使用的是 Linux 或 Mac OS,可以使用以下命令创建 Dockerfile。

touch Dockerfile

4. 最后,将以下内容添加到 Dockerfile 中。

FROM ubuntu:20.04

您现在已经创建了一个即将用于 Dockerfile 的文件!

构建 Docker 镜像

现在您已经创建了 Dockerfile,您需要构建一个 Docker 镜像来执行 Dockerfile 中 ENTRYPOINT 和 CMD 指令中编写的命令。构建镜像的一种方法是使用 build 命令

~/docker 目录中,运行以下命令。下面的命令通过指定当前工作目录 (.) 在 ~/docker 中的 Dockerfile 创建一个名为 demo 的 Docker 镜像(-t demo)。

docker build -t demo .
Building a Docker Image

运行 Docker 容器

在构建了 Docker 镜像之后,您将需要一个容器来运行 Docker 镜像,以执行 Dockerfile 中 ENTRYPOINT 和 CMD 指令中的命令。

要运行一个 Docker 容器,调用 run 命令 来创建一个可写的容器层覆盖 Docker 镜像(demo)。下面的示例使用了 -it 参数与容器进行交互连接,这样您就可以看到示例输出。

docker run -it demo
Running a Docker Container

Exec vs. Shell Form

当您开始使用 Dockerfile 并弄清楚如何运行启动命令时,您可能会遇到两种不同的定义这些命令的方法。每种方法都会调用命令,但执行方式略有不同。

当 Docker 执行命令时,它可以直接调用 exec,也可以通过容器的 shell(在 Linux 上为 /bin/sh -c,在 Windows 上为 cmd /S /C)调用 shell

您会注意到通过 exec 执行的命令具有一个指令,后跟要调用的可执行文件,然后是一个或多个命令行参数,如下所示。

ENTRYPOINT ["executables", "parameter1", "parameter2", ...]
CMD ["executables", "parameter1", "parameter2:, ...]

另一方面,以 shell 形式编写命令不需要将命令包装在方括号中,如下所示。

ENTRYPOINT <command> "parameter1"
CMD <command> "parameter1"

如果您没有指定 CMD 的参数,Docker 将始终以 exec 形式执行命令,例如 CMD <command>

如果您刚开始,区分这两种命令调用可能不太重要,但随着您变得更加高级,很快您将看到每种方法的优缺点。

运行启动命令

让我们现在深入了解本教程的实质,并通过演示一些在 Dockerfile 的 ENTRYPOINT 和 CMD 指令中运行启动命令的示例来亲自动手。

1. 在您首选的文本编辑器中打开您之前创建的 Dockerfile。

2. 将示例 Dockerfile 的内容复制粘贴到您的 Dockerfile 中,如下所示,并保存。

这个 Dockerfile 使用 ubuntu:20.04 作为基础镜像创建了一个层。然后它告诉 Docker 使用 execshell 形式,在 Dockerfile 的 CMDENTRYPOINT 指令中传递 Hello world 参数来调用 echo 命令。

FROM ubuntu:20.04
# CMD 指令
CMD ["echo", "Hello world"] # Exec Form
CMD echo "Hello world"      # Shell Form
# ENTRYPOINT 指令
ENTRYPOINT ["echo", "Hello world"] # Exec Form
ENTRYPOINT echo "Hello world"      # Shell Form

3. 在 ~/docker 目录中,通过运行 docker build 构建新的镜像,并将其命名为 demo。下面的命令将 标记 这个镜像为 demo,并在当前工作目录 (.) 中寻找 Dockerfile。

docker build -t demo .
Building a Docker image with docker build

4. 现在,运行一个容器使用这个镜像,然后运行一个基于之前创建的 Docker 镜像的 Docker 容器。您现在将看到容器返回 Hello world,这来自 Dockerfile 中提供的 CMD 指令。

docker run -it demo
Running a Docker container with docker run

在 Dockerfile 中使用变量

有时候你可能事先不知道要传递给命令的确切命令行参数。你需要传递给命令的参数只有在运行时才暴露出来。与其静态地分配命令参数,你可以使用变量来捕获并传递这些参数给命令。

你只能在shell形式中使用Dockerfile变量。Docker不支持通过exec形式调用的命令中的变量。

再次打开你喜欢的文本编辑器中的Dockerfile,用以下一系列命令替换其中的所有内容,并保存。

这一次你会注意到,Dockerfile使用了环境变量,并且使用ENV进行显示。在下面的例子中,Dockerfile定义了一个名为name的环境变量,其值为friend。一旦创建,这个环境变量就可以通过$name来引用。

当Docker根据这个Dockerfile运行容器时,它会调用echo命令,并传递Welcome, friend作为参数。

FROM ubuntu:20.04
ENV name friend

CMD echo "Welcome, $name"
# 或者
## ENTRYPOINT echo "Welcome, $name"

现在,创建Docker镜像并再次运行容器,可选地提供一个shellform的标签名。你会注意到Docker调用了echo命令,并返回了预期的输出。

Building a Docker Image (shellform) and Running a Docker Container

结合Dockerfile的ENTRYPOINT和CMD指令

大部分时候,您可以在CMD或ENTRYPOINT指令中调用启动命令。毕竟,您可以使用每种方法调用任意数量的命令。但是,您也可以调用单个命令并使用这两个指令“构建”。

基于前面的示例,也许您有一个Dockerfile,看起来像下面的示例。当前,如果您创建一个镜像并从该镜像运行一个容器,Docker将调用echo命令并返回Hello

FROM ubuntu:20.04
ENTRYPOINT ["echo", "Hello"]

也许您有另一个参数想传递给echo命令,但不是立即传递。也许您希望在Dockerfile中的后面执行此操作。通过调用没有命令的CMD指令,您可以实现这一点。

当您通过ENTRYPOINT指令指定要运行的命令,然后是CMD指令时,Docker会自动假设传递给CMD的值是一个参数,而不是一个命令。

现在,添加一个没有命令,只有一个名为world的参数的CMD指令引用,如下所示。

FROM ubuntu:20.04
ENTRYPOINT ["echo", "Hello"]
CMD ["world"]

组合指令应始终使用exec形式编写,因为它的“数组样式”行为可以通过逗号分隔的方式单独指定值,而不是全部放在一个字符串中。

构建镜像并从镜像运行容器后,您会发现Docker只返回一行输出(Helloworld),这意味着只执行了一个echo命令。

Building a Docker Image (demo3) and Running a Docker Container

结论

你现在应该对通过 CMDENTRYPOINT Dockerfile 指令运行 Docker 容器启动命令有了很好的理解。每个指令都有一些不同之处,但都能完成相同的任务,甚至可以一起使用。

你能想到一种情况吗,在这种情况下你更愿意使用 CMD 而不是 ENTRYPOINT 来运行启动命令?

Source:
https://adamtheautomator.com/dockerfile-entrypoint/