本文发自 http://www.binss.me/blog/learn-docker-with-me-about-run-entrypoint-and-cmd/,转载请注明出处。

虽然已经使用Docker好一段时间了,但至今对RUN、ENTRYPOINT与CMD傻傻分不清。查阅了一些资料,以此文记录自己的理解。

定义

三者都可用于执行命令,但无论在目的、运行时机、用法等方面都存在差异:

RUN instruction will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile.

An ENTRYPOINT allows you to configure a container that will run as an executable.

The main purpose of a CMD is to provide defaults for an executing container.

运行时机

RUN在Dockerfile构建镜像的过程(Build)中运行,最终被commit的到镜像。

ENTRYPOINT和CMD在容器运行(run、start)时运行。

shell形式和exec形式

RUN指令有两种形式:

  • shell形式

    RUN command param1 param2

  • exec形式

    RUN ["executable", "param1", "param2"]

ENTRYPOINT也如此:

  • shell形式

    ENTRYPOINT command param1 param2

  • exec形式

    ENTRYPOINT ["executable", "param1", "param2"]

CMD还多了一种用于为ENTRYPOINT提供参数的形式:

  • shell形式

    CMD command param1 param2

  • exec形式

    CMD ["executable", "param1", "param2"]

  • 参数形式

    CMD ["param1","param2"]

shell形式和exec的形式的本质区别在于shell形式提供了默认的指令/bin/sh -c,所以其指定的command将在shell的环境下运行。因此指定command的pid将不会是1,因为pid为1的是shell,command进程是shell的子进程。

尝试用shell形式运行top的结果:

Mem: 320036K used, 1730580K free, 132628K shrd, 48940K buff, 148940K cached
CPU:  0.0% usr  0.0% sys  0.0% nic  100% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.05 0.03 0.05 1/153 7
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 root     S     1204  0.0   0  0.0 /bin/sh -c top -b
    7     1 root     R     1200  0.0   0  0.0 top -b

由于top的pid不为1,因此我们无法直接向其发送信号,敲Ctrl+C是没有任何反应的。通过docker stop强制退出,退出状态为137,137=128 + 9,表明最后是被kill -9杀掉的。

shell形式还有一个严重的问题:由于其默认使用/bin/sh来运行命令,如果镜像中不包含/bin/sh,容器会无法启动。

exec形式则不然,其直接运行指定的指令:

Mem: 323148K used, 1727468K free, 132628K shrd, 49964K buff, 149180K cached
CPU:  0.0% usr  0.0% sys  0.0% nic  100% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.00 0.03 0.05 1/151 5
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 root     R     1200  0.0   0  0.0 top -b

由于exec指定的命令不由shell启动,因此也就无法使用shell中的环境变量,如$HOME。如果希望能够使用环境变量,可以指定命令为sh:CMD [ "sh", "-c", "echo", "$HOME" ]

重载

Dockerfile中只有最后一个CMD指令会生效,其他会被重载。

Dockerfile中只有最后一个ENTRYPOINT指令会生效,其他会被重载。

CMD指定的命令可以被docker run传递的命令覆盖。

CMD ["echo"]会被docker run --rm binss/test echo test中的echo覆盖,最终输出test。

ENTRYPOINT指定的命令不会被docker run传递的命令覆盖。容器名后面的所有内容都当成参数传递给其指定的命令。

ENTRYPOINT ["echo"]最终输出echo test。echo test都被当做是ENTRYPOINT指定的指令——echo的参数。

当然,ENTRYPOINT指定的命令并不是不能重载的,只需指定--entrypoint来重载即可。

两者结合

ENTRYPOINT和CMD进行组合的效果如下:

于是如果想同时使用ENTRYPOINT和CMD,CMD因为被当作ENTRYPOINT的参数而无法运行。如:

ENTRYPOINT ["echo","ENTRYPOINT"]

CMD ["echo","CMD"]

运行的结果是:ENTRYPOINT echo CMD。怎么办呢?查看了多个官方Dockerfile,它们是这样解决的:把ENTRYPOINT需要运行的命令写到shell脚本中,并在该脚本中使用exec "$@"来运行CMD中的命令:

该行运行了传入该shell脚本的参数,即CMD指令的内容。

Dockerfile如下:

FROM busybox
MAINTAINER binss([email protected])

COPY ./entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["echo","CMD"]

entrypoint.sh如下:

#!/bin/sh
set -e

echo "ENTRYPOINT"

exec "$@"

总结

写Dockerfile时,应该根据运行时机选择RUN还是ENTRYPOINT和CMD,根据实际需要选择使用shell形式还是exec形式,并尝试组合ENTRYPOINT和CMD来达到组合两者的效果。

参考

https://docs.docker.com/engine/reference/builder/