ENTRYPOINT 和 CMD 的搭配规则用文字很难记。这篇不背规则,直接把九种写法都跑出来,看容器最终执行的 Path 和 Args,再看 shell 形式对 PID 1 的影响。
[1] exec 形式 ENTRYPOINT + exec 形式 CMD,不加运行参数
Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD ["a", "b"] 运行参数:(无)
镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["a","b"]
容器实际执行:Path=/e.sh Args=["a","b"]
输出:入口脚本收到 2 个参数:a b
[2] 同上,docker run 后面加参数 x(替换 CMD)
Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD ["a", "b"] 运行参数:x
镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["a","b"]
容器实际执行:Path=/e.sh Args=["x"]
输出:入口脚本收到 1 个参数:x
[3] 同上,用 --entrypoint 覆盖入口
Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD ["a", "b"] 运行参数:--entrypoint echo IMG new
镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["a","b"]
容器实际执行:Path=echo Args=["new"]
输出:new
[4] shell 形式 ENTRYPOINT + exec 形式 CMD(CMD 被忽略)
Dockerfile:ENTRYPOINT /e.sh ; CMD ["a", "b"] 运行参数:(无)
镜像里存的 Entrypoint / Cmd = ["/bin/sh","-c","/e.sh"] ["a","b"]
容器实际执行:Path=/bin/sh Args=["-c","/e.sh","a","b"]
输出:入口脚本收到 0 个参数:
[5] exec 形式 ENTRYPOINT + shell 形式 CMD
Dockerfile:ENTRYPOINT ["/e.sh"] ; CMD echo hello 运行参数:(无)
镜像里存的 Entrypoint / Cmd = ["/e.sh"] ["/bin/sh","-c","echo hello"]
容器实际执行:Path=/e.sh Args=["/bin/sh","-c","echo hello"]
输出:入口脚本收到 3 个参数:/bin/sh -c echo hello
[6] 只有 exec 形式 CMD
Dockerfile:CMD ["echo", "from cmd"] 运行参数:(无)
镜像里存的 Entrypoint / Cmd = null ["echo","from cmd"]
容器实际执行:Path=echo Args=["from cmd"]
输出:from cmd
[7] 同上,docker run 后面给了新命令(整个替换)
Dockerfile:CMD ["echo", "from cmd"] 运行参数:ls /etc/alpine-release
镜像里存的 Entrypoint / Cmd = null ["echo","from cmd"]
容器实际执行:Path=ls Args=["/etc/alpine-release"]
输出:/etc/alpine-release
[8] 只有 shell 形式 CMD
Dockerfile:CMD echo from shell cmd 运行参数:(无)
镜像里存的 Entrypoint / Cmd = null ["/bin/sh","-c","echo from shell cmd"]
容器实际执行:Path=/bin/sh Args=["-c","echo from shell cmd"]
输出:from shell cmd
[9] ENTRYPOINT 自带参数 + CMD,docker run 再给 over
Dockerfile:ENTRYPOINT ["/e.sh", "fixed"] ; CMD ["default"] 运行参数:over
镜像里存的 Entrypoint / Cmd = ["/e.sh","fixed"] ["default"]
容器实际执行:Path=/e.sh Args=["fixed","over"]
输出:入口脚本收到 2 个参数:fixed overexec 形式的 ENTRYPOINT 会把 CMD(或 docker run 后面的参数)追加成它的参数;docker run 给了参数就整体替换 CMD;--entrypoint 直接覆盖入口。只有 CMD 时,run 后面的命令整个替换它。
[ENTRYPOINT ["sleep", "300"]] ps 看到:1 sleep 300 | 7 ps -o pid,args [ENTRYPOINT sleep 300] ps 看到:1 sleep 300 | 7 ps -o pid,args [ENTRYPOINT sleep 300 && echo done] ps 看到:1 /bin/sh -c sleep 300 && echo done | 7 sleep 300 | 8 ps -o pid,args
ENTRYPOINT /e.sh 被存成 ["/bin/sh","-c","/e.sh"],CMD ["a","b"] 只是 sh -c 的位置参数,脚本收到 0 个参数。CMD 用 shell 形式、ENTRYPOINT 用 exec 形式时,入口收到的是 /bin/sh -c "echo hello" 三个参数,通常不是你想要的。PID 1:exec 形式时就是你的程序;shell 形式 + 单条命令,这个 busybox sh 把它直接 exec 了,PID 1 仍是程序(别的 shell 不一定);复合命令(&&)时 PID 1 是 /bin/sh,你的程序是子进程,信号处理就不同了——后面讲 PID 1 与信号的文章会继续验证。