使用exec模式与shell模式,执行ENTRYPOINT和CMD的区别

Dockerfile 是一个包含用于组合镜像的指令的文本文档, Docker 通过读取 Dockerfile 中的指令自动生成镜像。Dockerfile 是一个包含用于组合镜像的指令的文本文档, Docker 通过读取 Dockerfile 中的指令自动生成镜像。

#Part 1 先介绍 Dockerfile 中三个最常用的指令:

RUN

执行命令并创建新的镜像层,RUN 经常用于安装软件包。

例如 在 Alpine Linux 的 Docker 镜像中安装 curl

FROM alpine:latest
RUN apk add --update curl && rm -rf /var/cache/apk/*

CMD

设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换。

FROM alpine:latest
RUN apk add --update curl && rm -rf /var/cache/apk/*
CMD ["curl"]

ENTRYPOINT

配置容器启动时运行的命令,不能被默认参数替换。

FROM alpine:latest
RUN apk add --update curl && rm -rf /var/cache/apk/*
ENTRYPOINT ["curl"]

看似两个命令几乎是重复的指令。实则用户可以通过命令行参数的方式覆盖 CMD 指令提供的默认命令;使用 ENTRYPOINT 指令,命令行上添加的参数会被追加到了 curl 命令的参数列表中。

创建 curl 目录,把上面的代码保存到 curl 目录的 Dockerfile 中,然后进入 curl 目录构建镜像并启动一个容器:

mkdir curl && cd curl
cat > Dockerfile <<EOF
FROM alpine:latest
RUN apk add --update curl && rm -rf /var/cache/apk/*
ENTRYPOINT ["curl"]
EOF
docker build -t curl .
docker run --rm curl http://www.baidu.com

输出为:

Good job! 一切正常工作。

镜像也只有7.73MB,如下图:

在这个例子中,我们用 ENTRYPOINT 指令作为容器的“入口”。而CMD 指令,官方给出的定义是一个容器的默认的可执行体:

The main purpose of aCMDis to provide defaults for an executing container.These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINTinstruction as well.

这两个指令都是用来指定容器启动时运行的命令,单独使用其中的一个就可以实现绝大多数的用例。但是 Docker 同时提供了它们,为了在使用中不至于混淆,本文试图把它们的用法理清楚。废话不多说,来一起看看 Part 2 详细的介绍吧。


#Part 2 CMD 和 ENTRYPOINT本质的不同

CMD 和 ENTRYPOINT 指令在工作方式上有根本的区别,它们适合不同的应用程序、环境和场景。

当 CLI 命令docker run中带有参数时,

  1. 守护进程将忽略 Dockerfile 中定义的 CMD 指令。
  2. ENTRYPOINT 不会被忽略,命令行上的参数被附加到 ENTRYPOINT 指定的命令的参数列表中。

接下来,让我们仔细看看这两种指令。

Docker CMD

为容器提供默认的执行命令。

一个 Dockerfile 可以有一个或多个 CMD 指令,在有多个CMD 指令的情况下,除最后一个外,其余的都会被忽略。

例如:

mkdir cmd && cd cmd
cat > Dockerfile <<EOF
FROM ubuntu
CMD ["/bin/echo", "1"]
CMD ["/bin/echo", "2"]
EOF
docker build -t cmd .
docker run --rm cmd

输出为:

如果我们在docker run命令添加参数,将会覆盖默认的 CMD 指令,语法为:

docker run IMAGE [COMMAND] [ARG...]

例如:

docker run cmd hostname

输出为:

所以,最好的使用 CMD 指令的时机是:

当用户在命令行上不输入参数时,为容器提供默认的执行命令。

该指令通过在容器镜像运行后立即启动应用程序来确保容器处于运行状态。之所以这样,CMD 指令会在容器启动时立即加载基础镜像。

Docker ENTRYPOINT

在 Dockerfile 中,ENTRYPOINT 指令用于设置在启动容器时始终运行的可执行文件。与 CMD 指令不同,ENTRYPOINT 指令不能被忽略或重写(准确的说docker run后面跟的命令行参数,不会覆盖ENTRYPOINT指令;docker run--entrypoint可以覆盖 Dockerfile 中 ENTRYPOINT 设置的命令),即使容器运行时声明了命令行参数。

Docker ENTRYPOINT 指令支持 shell 和 exec 两种模式的写法:

  • Exec 模式: ENTRYPOINT ["executable", "parameter1", "parameter2"]
  • Shell 模式: ENTRYPOINT command parameter1 parameter2

先上一个例子:

mkdir entrypoint && cd entrypoint
cat > Dockerfile <<EOF
FROM ubuntu
ENTRYPOINT ["/bin/echo"]
EOF
docker build -t entrypoint .
docker run --rm entrypoint

输出为:

空,什么都没有,这符合我们的预期,因为并没有为echo命令传参数。

运行一个新的容器,添加参数 $HOME

docker run --rm entrypoint $HOME

输出为:

结论:ENTRYPOINT 指令为 exec 模式时,命令行上指定的参数会作为参数添加到 ENTRYPOINT 指定命令的参数列表中。

那么,当 ENTRYPOINT 指令为 shell 模式时,会怎样处理命令行参数呢?

修改 Dockerfile ,创建镜像并运行容器:

cat > Dockerfile <<EOF
FROM ubuntu
ENTRYPOINT /bin/echo
EOF
docker build -t entrypoint .
docker run --rm entrypoint

不添加参数的时候输出为空:

添加参数$HOME

docker run --rm entrypoint $HOME

输出仍为空:

shell 模式,通过 CMD 传参数,ENTRYPOINT 指令可以接收到么,我们再来试一下:

cat > Dockerfile <<EOF
FROM ubuntu
ENTRYPOINT /bin/echo
CMD $HOME
EOF
docker build -t entrypoint .
docker run --rm entrypoint

输出还是为空:

结论:ENTRYPOINT 指令为 shell 模式时,会忽略命令行和 CMD 参数。

一般我们会用 ENTRYPOINT 的 exec 模式作为 docker 容器启动以后的默认执行命令,里面放的是不变的部分,需要其他参数,可以通过docker run运行容器的时候添加在命令行中。

同时使用 CMD 和 ENTRYPOINT

CMD 和 ENTRYPOINT 是两个功能类似的指令,有些情况可能需要在 Dockerfile 中使用它们的组合指令。可以使用 ENTRYPOINT 指令定义命令,同时使用 CMD 定义参数。

例子:

mkdir cmd-entrypoint && cd cmd-entrypoint
cat > Dockerfile <<EOF
FROM ubuntu
ENTRYPOINT ["echo", "Hello"]
CMD ["Darwin"]
EOF
docker build -t cmd-entrypoint .
docker run --rm cmd-entrypoint

输出为:

在命令后面附加一个参数(如 Hawking )将重写 CMD 指令,并使用 CLI 参数作为参数仅执行 ENTRYPOINT 指令。例如:

docker run --rm cmd-entrypoint Hawking

输出为:

这是因为 ENTRYPOINT 指令不能被忽略,而使用 CMD 时,命令行参数会覆盖该指令。

使用 ENTRYPOINT 还是 CMD

ENTRYPOINT 和 CMD 对于构建和运行 Dockerfiles 都是必不可少的,这完全取决于使用场景,一般来说:

  1. 使用 RUN 指令安装应用和软件包,构建镜像。
  2. 总是需要执行命令构建可执行 Docker 镜像时,选择 ENTRYPOINT 指令。
  3. CMD 指令最适合作为默认指令的附加参数集,同时可利用docker run命令行替换默认参数。

还要注意的是,在 Dockerfile 中:

  1. 如果有多个 ENTRYPOINT 指令,则最后一个会覆盖前面的
  2. 如果有多个 CMD 指令,则最后一个会覆盖前面的
  3. 至少有一个 ENTRYPOINT 指令或 CMD 指令

#Part 3 Shell 模式和 exec 模式

首先,我们需要了解 Docker 守护进程在指令传递后是如何处理的。

所有的 Docker 指令的命令要不属于shell 模式,要不属于exec模式。我们通过简单的 Dockerfile 实例来理解这两种模式下的命令:

1 Shell 模式

顾名思义,shell 形式的命令启动在 shell 中运行的进程,相当于以 /bin/sh -c "task command" 的方式执行任务命令。

语法为:

<instruction><command>

看下面的例子:

FROM ubuntu
CMD top

创建 shell-command-form 目录,把上面的代码保存到 shell-command-form 目录的 Dockerfile 中,构建镜像,并启动一个容器:

mkdir shell-command-form && cd shell-command-form
cat > Dockerfile <<EOF
FROM ubuntu
CMD top
EOF
docker build -t shell-command-form .
docker run -itd --name shell-command-form-test shell-command-form 

然后查看容器中的进程 ID:

docker exec shell-command-form-test ps aux 

1 号进程执行的命令居然是/bin/sh -c top,而我们指定的top命令的进程 ID 为 7。Docker 之所以这么编排,目的是让我们执行的命令或者脚本可以取到环境变量

通过运行 docker ps 命令,也可以看出 Docker 首先运行的是/bin/sh -c top

在 Dockerfile 中定义环境变量

语法为:

ENV <key> <value>

定义一个 key 为 name 的环境变量:

ENV name Darwin
CMD /bin/echo "Welcome, $name"

基于上面的列子,构建镜像,并启动一个容器:

docker build -t darwin .
docker run --rm darwin

输出为:

这种命令形式会在返回结果之前进行验证,通常会导致性能瓶颈。因此,shell 模式通常不是首选模式,除非有特定的命令/环境验证的需求。

2 Executable 模式

与 Shell 模式不同,以 Executable 命令模式编写的指令直接运行可执行脚本,而不经过shell验证和处理。

语法如下:

<instruction>[“executable”, “parameter 1”, “parameter 2”, …]

例子:

RUN ["yum", "-y", "update"]
CMD ["yum", "install", "-y", "ebtables"]

在 exec 模式下执行带有环境变量的命令:

cat > Dockerfile <<EOF
FROM ubuntu
ENV name Darwin
CMD ["/bin/echo", "Welcome, \$name"]
EOF
docker build -t executable-command-form .
docker run --rm executable-command-form

输出为:

这证明了 Docker 在运行过程中,并没有将用户定义的环境变量替换为对应的值。

那系统的环境变量在 exec 模式下会被输出么?我们把 Linux 系统环境变量中 HOSTNAME 在 exec 模式下通过 CMD 指令打印出来:

cat > Dockerfile <<EOF
FROM ubuntu
CMD ["/bin/echo", "\$HOSTNAME"]
EOF
docker build -t executable-command-form .
docker run --rm executable-command-form

输出为:

至此,也得出了这样的结论:

exec 模式是不会通过 shell 执行相关的命令,所以像 $HOSTNAME 这样的系统环境变量是取不到的。

那在 exec 模式下,想输出自定义环境变量和系统环境变量,应该怎么办呢?

就是在该模式下执行 shell 便可获得环境变量:

cat > Dockerfile <<EOF
FROM ubuntu
ENV name Darwin
CMD ["/bin/sh", "-c", "echo \$name && echo \$HOSTNAME"]
EOF
docker build -t executable-command-form .
docker run --rm executable-command-form

输出为:

至此,完美的取到了自定义环境变量和系统环境变量的值。


使用 exec 模式与 shell 模式,执行 ENTRYPOINT 和 CMD 的区别


参考:

Dockerfile reference​docs.docker.com/engine/reference/builder/#cmd​编辑

Dockerfile reference​docs.docker.com/engine/reference/builder/#entrypoint​编辑

Docker CMD vs ENTRYPOINT: What’s The Difference & How To Choose​www.bmc.com/blogs/docker-cmd-vs-entrypoint/​编辑

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值