Docker 中 CMD 和 ENTRYPOINT 的区别
在不同环境中管理软件和依赖项时,Docker 容器已成为标准。在使用实际应用程序时,你需要在构建应用程序容器映像之前创建一个 docker 文件。
docker 文件只是一个只读文本文档,其中包含一组在组装镜像时将调用的指令。这些命令包括 RUN
、CMD
和 ENTRYPOINT
。
在本文中,我们将讨论这些命令的用法。大多数可能处于学习 docker 初始阶段的开发人员倾向于交替使用这些命令,这可能会给你带来一些问题。
Docker 中的 CMD
命令
此命令指定执行 docker RUN
命令时执行的指令。但是,这需要在不指定任何参数的情况下执行 docker RUN
命令。
指定参数时,此命令将被覆盖。另一方面,如果没有指定命令行参数,则执行 CMD
命令。
CMD
命令对于你的 docker 容器正常运行不是必需的,因为 ECHO
命令可以在运行时用于相同目的。但是,当每次容器启动时运行可执行文件时,CMD
命令会很方便。
为了演示如何使用 CMD
命令在运行时运行可执行文件,我们将创建一个简单的 docker 容器,其中包含一个打印出消息的简单 Flask 程序。请注意,这可以用任何语言复制,不一定是 Python。
我们将从创建我们的主应用程序开始,它应该如下所示那样简单。
from flask import Flask
app = Flask(__name__)
def hello():
print("Hello, this is a simple Flask application")
hello()
在同一个文件夹中,我们将使用命令 touch Dockerfile
创建我们的 Dockerfile。
Dockerfile 只指定了基础镜像、工作目录和应该安装的包。
在最后一行,你应该注意 CMD
命令。在这种情况下,我们使用 CMD
命令在容器启动时执行 app.py
文件。
# base image
FROM python
# Set your working directory
WORKDIR /var/www/
# Copy the necessary files
COPY ./app.py /var/www/app.py
COPY ./requirements.txt /var/www/requirements.txt
# Install the necessary packages
RUN pip install -r /var/www/requirements.txt
# Run the app
CMD python3 app.py
requirements.txt
应如下所示。
click==8.0.4
Flask==2.0.3
gunicorn==20.1.0
itsdangerous==2.1.0
Jinja2==3.0.3
MarkupSafe==2.1.0
Werkzeug==2.0.3
现在我们已经准备好了一切,我们现在可以继续构建 docker 镜像了。在此之前,我们需要确保我们位于存储程序的同一文件夹中。
在我们的例子中,我们将在构建映像之前将 cd
放入 my-app
文件夹,如下所示。
~/my-app$ docker build -t isaactonyloi_image .
输出:
=> [internal] load build context 0.9s
=> => transferring context: 320B 0.1s
=> [2/5] WORKDIR /var/www/ 5.1s
=> [3/5] COPY ./app.py /var/www/app.py 3.2s
=> [4/5] COPY ./requirements.txt /var/www/requirements.txt 3.2s
=> [5/5] RUN pip install -r /var/www/requirements.txt 53.9s
=> exporting to image 6.9s
=> => exporting layers 5.8s
=> => writing image sha256:5847e4777754d9d576accd929076bfbee633ca71f049ebe1af6e9bae161f3e96 0.1s
=> => naming to docker.io/library/isaactonyloi_image 0.2s
isaac@DESKTOP-HV44HT6:~/my-app$
我们已经成功地基于早期的 docker 文件构建了我们的镜像。我们可以在下面验证。
~/my-app$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
isaactonyloi_image latest 5847e4777754 7 minutes ago 929MB
我们终于可以使用基于此镜像的 docker run
命令创建我们的 docker 容器。另外,请注意,我们将在不传递任何参数来执行 CMD
命令的情况下执行此操作。
~/my-app$ docker run isaactonyloi_image
Hello, this is a simple Flask application
除此之外,CMD
命令还允许我们创建可以在运行时轻松覆盖的参数。
我们在下面的示例中对 CMD
命令进行了更改。其他文件保持不变,我们已经重建了一个新镜像。
# base image
FROM python
# Set your working directory
WORKDIR /var/www/
# Copy the necessary filesls
COPY ./app.py /var/www/app.py
COPY ./requirements.txt /var/www/requirements.txt
# Install the necessary packages
RUN pip install -r /var/www/requirements.txt
# Run the app
CMD ["echo", "Hello, Developer"]
这是我们根据对 Dockerfile 的更改重建的新映像。
~/my-app$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
new_image latest 73f323be0d2f 25 minutes ago 929MB
在不传递任何参数的情况下再次创建新的 docker 容器时,我们应该在 CMD
命令下收到消息。
isaac@DESKTOP-HV44HT6:~/my-app$ docker run new_image
Hello, Developer
但是,当我们在运行时传递参数时,CMD
命令将自动被覆盖,并且新参数应该优先。因此,它为我们提供了在运行时添加新参数的灵活性,如下所示。
~/my-app$ docker run new_image hostname
da0a832888cb
上面的输出显示 CMD
命令没有被新的主机名参数执行和覆盖。
Docker 中的 ENTRYPOINT
命令
docker ENTRYPOINT
命令与 CMD
命令有相似之处,但并不完全相同。
当使用 CMD
命令时,我们可以通过在运行时传递参数轻松地覆盖它,但 ENTRYPOINT
命令并非如此。
因此,ENTRYPOINT
可用于在运行时不覆盖入口点指令。
如下所示,我们可以通过简单地将 Dockerfile 中的 CMD
命令替换为 ENTRYPOINT
命令来探索此命令的工作原理。我们将根据对 docker 文件的更改构建一个新镜像。
isaac@DESKTOP-HV44HT6:~/my-app$ docker build -t tonyloi_newimage .
[+] Building 42.7s (10/10) FINISHED
输出:
=> [internal] load build definition from Dockerfile 2.0s
=> => transferring dockerfile: 365B 0.7s
=> [internal] load .dockerignore 1.6s
=> => transferring context: 2B 0.4s
=> [internal] load metadata for docker.io/library/python:latest 35.4s
=> [1/5] FROM docker.io/library/python@sha256:c90e15c86e2ebe71244a2a51bc7f094554422c159ce309a6faadb6debd5a6df0 0.3s
=> [internal] load build context 1.2s
=> => transferring context: 63B 0.1s
=> CACHED [2/5] WORKDIR /var/www/ 0.0s
=> CACHED [3/5] COPY ./app.py /var/www/app.py 0.0s
=> CACHED [4/5] COPY ./requirements.txt /var/www/requirements.txt 0.0s
=> CACHED [5/5] RUN pip install -r /var/www/requirements.txt 0.0s
=> exporting to image 2.1s
=> => exporting layers 0.0s
=> => writing image sha256:15fb8e4e3ff58ed529b11342bba75b029fd4323beb24aac70ca36b178d04cb34 0.2s
=> => naming to docker.io/library/tonyloi_newimage 0.1s
isaac@DESKTOP-HV44HT6:~/my-app$
此输出证明我们已成功构建了新映像。现在我们可以基于这个镜像创建一个新的 docker 容器。
你可以选择使用镜像名称或镜像 ID 创建容器,这两者都可以使用 docker images
命令访问。这也将显示我们之前创建的镜像。
~/my-app$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tonyloi_newimage latest 15fb8e4e3ff5 48 minutes ago 929MB
new_image latest 73f323be0d2f 48 minutes ago 929MB
isaactonyloi_image latest 5847e4777754 48 minutes ago 929MB
如果我们在不添加任何参数的情况下构建 docker 容器,我们应该得到如下输出。
isaac@DESKTOP-HV44HT6:~/my-app$ docker run tonyloi_newimage
Hello, Developer
如果我们在基于此镜像创建另一个容器时尝试传递参数,你会注意到,与 CMD
命令的情况不同,这些新参数不会覆盖 ENTRYPOINT
命令。
我们可以在下面验证这一点。
isaac@DESKTOP-HV44HT6:~/my-app$ docker run tonyloi_newimage Welcome to ADC
Hello, Developer Welcome to ADC
结论
我们可以说这两个命令非常相似; 但是,它们的区别在于 CMD
命令可以在运行时被覆盖,而 ENTRYPOINT
命令不能。
此外,某些情况可能需要同时使用这两个命令。
Isaac Tony is a professional software developer and technical writer fascinated by Tech and productivity. He helps large technical organizations communicate their message clearly through writing.
LinkedIn