当我尝试通过 Dockerfile 中的 CMD 指令在运行时执行两个命令时,不明白发生了什么。我认为这应该有效:
CMD ["/etc/init.d/nullmailer", "start", ";", "/usr/sbin/php5-fpm"]
但它不起作用。容器尚未启动。所以我不得不这样做:
CMD ["sh", "-c", "/etc/init.d/nullmailer start ; /usr/sbin/php5-fpm"]
我不明白。为什么会这样?为什么第一行不是正确的方式?有人能用简单的话给我解释一下这些“CMD shell 格式 vs JSON 格式等”吗?
只需注意 -与预期一样,command:
中的指令也是相同的docker-compose.yml
。
答案1
我认为差异可能是因为第二个命令进行 shell 处理,而第一个命令不进行。根据官方文档,有exec
和shell
形式。您的第一个命令是一个exec
形式。exec
形式不扩展环境变量,而形式会扩展。使用形式时,命令shell
可能会失败,因为它依赖于 shell 处理。您可以通过运行来检查这一点exec
docker logs CONTAINERID
您的第二条命令(shell 形式)相当于 -
CMD /etc/init.d/nullmailer start ; /usr/sbin/php5-fpm
文档摘录 -
注意:与 shell 形式不同,exec 形式不会调用命令 shell。这意味着不会发生正常的 shell 处理。例如,
CMD [ "echo", "$HOME" ]
不会对 进行变量替换$HOME
。如果您需要 shell 处理,则可以使用 shell 形式或直接执行 shell,例如:CMD [ "sh", "-c", "echo", "$HOME" ]
。
答案2
不要给自己找麻烦。只需创建一个 bash 文件“start.sh”:
#!/bin/bash
/usr/bin/command2 param1
/usr/bin/command1
在你的 Dockerfile 中执行以下操作:
ADD start.sh /
RUN chmod +x /start.sh
CMD ["/start.sh"]
答案3
CMD
(和RUN
和)的 json 语法ENTRYPOINT
将参数直接作为 exec 系统调用传递给内核。在 exec 系统调用中,没有用空格将命令与参数分开、没有引号转义、没有 IO 重定向、没有变量替换、没有命令之间的管道、没有运行多个命令等。系统调用只接受要运行的可执行文件和要传递给该可执行文件的参数列表,然后运行它。
$
诸如扩展变量、;
分隔命令、(空格)分隔参数
&&
以及||
链接命令、>
输出重定向、在命令之间进行管道传输等字符|
都是 shell 的功能,需要类似/bin/sh
或 之类的东西/bin/bash
来解释和实现它们。
如果你切换到 的字符串语法CMD
,docker 将使用 shell 运行你的命令:
CMD /etc/init.d/nullmailer start ; /usr/sbin/php5-fpm
否则,你的第二种语法会做完全相同的事情:
CMD ["sh", "-c", "/etc/init.d/nullmailer start ; /usr/sbin/php5-fpm"]
请注意,我不建议在容器内以这种方式运行多个命令,因为如果第一个命令失败,则没有错误处理,尤其是在后台运行时。您还会在容器内留下一个作为 pid 1 运行的 shell,这将破坏信号处理,导致 10 秒延迟,并且 docker 会非正常终止您的容器。可以使用 shell 命令来缓解信号处理exec
:
CMD /etc/init.d/nullmailer start ; exec /usr/sbin/php5-fpm
但是,处理后台静默失败的进程需要您切换到某种多进程管理器(如supervisord),或者最好将您的应用程序分解为多个容器并使用docker-compose之类的工具进行部署。
答案4
我猜测第一个命令失败是因为在 DOCKER CMD 格式中,只执行第一个参数,其余的都输入到这个命令中。
第二种形式有效,因为所有用“;”分隔的命令都被输入到 sh 命令中,并由 sh 命令执行。