Dockerfile中CMD与ENTRYPOINT的区别是什么?

510 阅读4分钟

Dockerfile配置详情

FROM指定基础镜像FROM python:3.8.3-alpine
MAITAINER维护人MAITAINER lakeiedward
COPY从宿主机复制文件或目录到容器内COPY ./ruoyi-admin.jar /tmp/app.jar
ADD复制文件并自动解压ADD myfile.tar /html/myapp
RUN创建镜像时要执行的命令RUN pip install -r requirements.txt
USER切换执行后续命令的用户和用户组, 但这个用户必需首先已使用RUN的命令进行创建好了。RUN groupadd -r redis && useradd -r -g redis redis; USER redis(切换用户)
WORKDIR指定工作目录WORKDIR /html/myapp
CMD给启动的容器指定默认要运行的程序。运行 docker run 时如果使用了 --entrypoint 选项,将覆盖 CMD 指令指定的程序。CMD ["/bin/bash"]
ENV设置环境变量ENV APP_HOME /html/myapp
ENTRYPOINT容器启动时指定运行程序的入口点。同CMD,但其不会被覆盖,可以和docker run命令传递的参数进行拼接执行。如果设置:ENTRYPOINT ["nginx", "-c"] , 运行$ docker run mynginx_1 -c /etc/nginx/myweb.conf将默认执行命令:nginx -c /etc/nginx/myweb.conf。
VOLUME定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。VOLUME /tmp
EXPOSE暴露容器端口,端口会暴露给link到当前容器或通过网络连接的容器,不会和宿主机端口映射关系。EXPOSE 8080

Shell vs Exec

加中括号和不加中括号的区别,加中括号时,里面必须是双引号!

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

Shell写法

ENTRYPOINT和CMD指令支持2种不同的写法: shell表示法和exec表示法. 下面的例子使用了shell表示法:

CMD executable  param1 param2

当使用shell表示法时, 命令行程序作为sh程序的子程序运行, docker用/bin/sh -c的语法调用. 如果我们用docker ps命令查看运行的docker, 就可以看出实际运行的是/bin/sh -c命令

$ docker run -d demo
15bfcddb11b5cde0e230246f45ba6eeb1e6f56edb38a91626ab9c478408cb615

$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
15bfcddb4312 demo:latest "/bin/sh -c 'ping localhost'" 2 seconds ago 

我们再次运行demo镜像, 可以看出来实际运行的命令是/bin/sh -c 'ping localhost'.

Exec写法

CMD指令后面用了类似于JSON的语法表示要执行的命令. 这种用法告诉docker不需要调用/bin/sh执行命令

重新build镜像, 用docker ps命令检查效果:

$ docker build -t demo .
[truncated]

$ docker run -d demo
90cd472887807467d699b55efaf2ee5c4c79eb74ed7849fc4d2dbfea31dce441

$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
90cd47288780 demo:latest "/bin/ping localhost" 4 seconds ago 

现在没有启动/bin/sh命令, 而是直接运行/bin/ping命令, ping命令的PID是1. 无论你用的是ENTRYPOINT还是CMD命令, 都强烈建议采用exec表示法。

命令行参数

CMD 与 ENTRYPOINT 的区别

  • CMD:会被命令行的参数替换
  • ENTRYPOINT:命令行上指定的参数会作为参数添加到 ENTRYPOINT 指定命令的参数列表中
FROM ubuntu
ENTRYPOINT ["top","-b"]
CMD ["-c"]

当命令行没有参数时,CMD中的 -c 会作为ENTRYPOINT的默认参数放到-b 后面

#最终命令为 
top -b -c

当命令行传递参数时,传递的参数会替换CMD中的参数放到ENTRYPOINT的参数-b 后面

#运行下面的命令:
$ docker run --rm test1 -n 1

#最终命令为 
top -b -n 1

覆盖默认的ENTRYPOINT

ENTRYPOINT 指令也是可以被命令行覆盖的,只不过不是默认的命令行参数,而是需要显式的指定 --entrypoint 参数。

$ docker run --entrypoint [new_command] [docker_image] [optional:value]

当我们再次执行时,随便搞个指令测试一下

docker run --name test --entrypoint hostname java -n 1

发现命令整个就被覆盖了,完全变成命令行中的命令了!

图片.png

同时使用 CMD 和 ENTRYPOINT 的情况

如果你想让你的docker image做真正的工作, 一定会在Dockerfile里用到ENTRYPOINT或是CMD. 但是请注意,这2个命令不是互斥的. 在很多情况下, 你可以组合ENTRYPOINT和CMD命令, 提升最终用户的体验。

对于 CMD 和 ENTRYPOINT 的设计而言,多数情况下它们应该是单独使用的。当然,有一个例外是 CMD 为 ENTRYPOINT 提供默认的可选参数。

可以通过官方的下图来查看不同的CMD 和 ENTRYPOINT组合执行的命令:

图片.png

我们大概可以总结出下面几条规律:

  1. 如果 ENTRYPOINT 使用了 shell 模式,CMD 指令会被忽略。
  2. 如果 ENTRYPOINT 使用了 exec 模式,CMD 也应该使用 exec 模式,即CMD指定的内容会被追加到ENTRYPOINT末尾当参数。

注意事项

  • Dockerfile应该至少指定CMD或ENTRYPOINT命令中的一个。
  • 当将容器用作可执行文件时,应该定义ENTRYPOINT。
  • CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行临时命令的一种方式。
  • 当使用可选参数运行容器时,CMD将被覆盖。
  • Exec写法时,必须是双引号,不能是单引号 CMD ["java","-jar","/tmp/app.jar"]

构建java镜像示例

# 基础镜像
FROM centos:7.9.2009
# 配置环境变量和jdk的安装目录
ENV JAVA_DIR=/usr/local
# 安装jdk
# \ 就是换到下一行输入,对实际命令没有任何影响,只是为了方便观看
COPY ./jdk-8u351-linux-x64.tar.gz $JAVA_DIR/
COPY ./fabledt-admin.jar /tmp/app.jar

RUN cd $JAVA_DIR \ 
	# 使用tar命令将当前目录下(这里因为上面配置了所以所在的目录也就是 /usr/local/下面)的jdk文件进行解压
	&& tar -xvf ./jdk-8u351-linux-x64.tar.gz \
	# 然后修改解压后的文件名为java8 ,此时就是 /usr/local/java8
	# 这里需要注意的是下面的 jdk1.8.0_321,他就是解压后的文件名
	# 如果这里你不清楚解压后的文件是什么,则可以先解压看以下文件名是什么,然后在进行书写这里的名称
	&& mv ./jdk1.8.0_351 ./java8
	
# 配置JAVA的环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
# 配置到PAHT中
ENV PATH=$PATH:$JAVA_HOME/bin
# 暴露端口 5050 --!!!容器内部端口
EXPOSE 5050

# 入口,jar文件的启动命令 # 这里app.jar就是我的jar文件
ENTRYPOINT ["java","-jar"]
CMD ["/tmp/app.jar"]

打包编译docker镜像

$ docker build -t docker_java .

根据镜像启动容器

docker run -p 5050:5050 --name java  \
-e TZ="Asia/Shanghai" \
-d docker_java

此时 CMD 就作为 ENTRYPOINT 的默认参数放到末尾执行,并且启动了这个java程序。

查看容器的Command

docker exec java ps aux

图片.png

可以看到 /tmp/app.jar放到"java","-jar"后面执行的。

参考文章

Dockerfile reference

www.cnblogs.com/sparkdev/p/…