Docker学习笔记十一:CMD和ENTRYPOINT指令&ENV、EXPOSE、WORKDIR、ARG指令&ONBUILD和VOLUME指令

CMD指令

CMD 指定默认的容器主进程启动命令
格式:CMD <command>(shell格式)或 CMD [“executable”,”param1”,”param2”](exec格式,推荐格式)或 CMD[”param1”,”param2”]。(为ENTRYPOINT指令提供参数)

CMD指令提供容器启动时运行的默认命令,例如ubuntu镜像默认的CMD是/bin/bash,因此我们可以直接使用 docker run -it ubuntu进入bash。

同时也可以使用docker run -it ubuntu cat /etc/os-release,执行该命令后会输出系统版本信息。因为当在执行docker run命令时,如果显示地指定了容器的启动命令,那么会将Dockerfile中CMD设置的默认启动命令覆盖,也就是说:cat /etc/os-release命令会替代成为容器的启动命令,所以输出了系统版本信息。

在指令格式上,一般推荐使用exec格式,因为使用 shell格式时,实际的命令会被包装为 sh -c的参数的形式进行执行。比如:CMD echo $HOME,在实际执行中,会将其变更为:CMD [ "sh", "-c", "echo $HOME" ]

ENTRYPOINT指令

ENTRYPOINT 指定默认的容器主进程启动命令
格式:ENTRYPOINT <command>(shell格式)或ENTRYPOINT [“executable”,”param1”,”param2”]。(exec格式,推荐格式)

ENTRYPOINT和CMD一样,都可以指定容器默认的启动命令,但是它又和CMD有所不同。上面我们说过,用户在执行docker run命令创建并启动容器时,如果指定了启动命令,那么“该启动命令”会覆盖CMD指令设置的默认启动命令,但是ENTRYPOINT设置的启动命令该不能被覆盖。
细心的同学可能发现了CMD命令可以为ENTRYPOINT指令提供参数。实际上,如果使用Dockerfile构建镜像时,既使用了ENTRYPOINT指令,又指定了CMD指令,那么CMD指令的含义就发生了改变, CMD 的内容将作为参数传给 ENTRYPOINT指令,换句话说实际执行时,变成了<ENTRYPOINT> <CMD>。同时,如果执行docker run基于该镜像创建并启动容器,并设置了启动命令时,docker run设置的“启动命令”依然会覆盖CMD的内容,但也仅仅是作为ENTRYPOINT指令的参数。

实例

假设需要一个得知使用者当前公网IP的镜像,可以使用下面的Dockerfile构建一个镜像。

FROM centos
RUN yum install curl
CMD ["curl","-s","http://ip.cn"]

执行docker build -t myip .来构建一个名为myip的镜像,镜像构建完成后,如果想要查询当前公网的IP,执行docker run myip,如下所示:

[root@localhost tempdir]# docker run myip
当前 IP:XXXXXXXX 来自:XXXXXXXXXXXX

嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 CMD 中可以看到实质的命令是curl ,那么如果我们希望显示HTTP头信息,就需要加上-i参数。那么我们可以直接加 -i参数给 docker run myip么?

[root@localhost tempdir]# docker run  myip -i
container_linux.go:247: starting container process caused "exec: \"-i\": executable file not found in $PATH"
docker: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "exec: \"-i\": executable file not found in $PATH"

我们可以看到执行明后后输出了 executable file not found的错误信息,也就是“可执行文件找不到”。之前我们说过,跟在镜像名后面的是command,运行时会替换 CMD的默认值。因此这里的-i替换了原来的CMD,而不是添加在原来的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我们希望加入-i这参数,我们就必须重新完整的输入这个命令:docker run myip curl -s http://ip.cn –i。这显然不是很好的解决方案,而使用 ENTRYPOINT 就可以解决这个问题。现在改写Dockerfile,使用ENTRYPOINT设置启动命令:

FROM centos
RUN yum install curl
ENTRYPOINT ["curl","-s","http://ip.cn"]

这次我们再来尝试直接使用 docker run myip以及 docker run myip -i:可以看到,这次成功了。这是因为当存在 ENTRYPOINT 后,docker run命令ENTRYPOINT不会被覆盖。它会作为参数传给ENTRYPOINT,从而达到了我们预期的结果。

[root@localhost tempdir]# docker run myip
当前 IP:XXXXXXXXXXXX 来自:XXXXXXXXXXXXXX
[root@localhost tempdir]# docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.11.9
Date: Wed, 09 Aug 2017 08:32:24 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
当前 IP:XXXXXXXXXXXX 来自:XXXXXXXXXXXXXXX

WORKDIR指令

  • WORKDIR为其他指令设置工作目录;
    格式:WORKDIR <工作目录路径>

WORKDIR指令为Dockerfile中的任何RUN,CMD,ENTRYPOINT,COPY和ADD指令设置工作目录(或称当前目录)。(也就是说以后各层的当前目录就被改为WORKDIR指定的目录)如果WORKDIR对应的目录不存在,将会自动被创建。

ENV指令

  • ENV设置环境变量;
    格式:ENV <key> <value>ENV <key>=<value>

这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,还是运行时的应用,都可以直接使用这里定义的环境变量。如下所示,下面是为tomcat设置环境变量并让tomcat自启动的一个Dockerfile片段。ENV设置的环境变量CATALINA_HOME能够被后续定义指令使用。

ENV CATALINA_HOME /var/tmp/apache-tomcat-8.0.45
ENV PATH $PATH: $CATALINA_HOME/bin
ENTRYPOINT $CATALINA_HOME /bin/startup.sh && /bin/bash

ARG指令

  • ARG构建参数;
    格式:ARG <参数名>[=<默认值>]

ARG与ENV有些类似,它们都可以被后面的其它指令直接使用,但是它并不是环境变量,这意味着将来容器运行时是不会存在ARG变量的。

Dockerfile中的ARG指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build 中用 --build-arg <参数名>=<值> 来覆盖。
什么时候用ARG,什么时候用ENV?
如果想保存为环境变量,就用ENV;如果只想在Dockerfile中临时使用,就用ARG

EXPOSE指令

  • EXPOSE暴露端口;
    格式:EXPOSE <端口1> [<端口2>...]

EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。如果想要公开容器的端口,必须在docker run是指定-p参数去公开端口或者指定-P参数公开所有被EXPOSE的端口。具体可以参照Docker官方文档

在Dockerfile中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P时,会自动随机映射EXPOSE 的端口。

实例:使用Dockerfile,创建一个拥有java和tomcat运行环境的镜像。

Dockerfile的内容如下,首先使用FROM指定基础镜像为ubuntu:latest镜像。然后使用WORKDIR设置当前的工作目录为/var/tmp。接下来使用RUN命令将jre.tar.gz下载到工作目录,并解压文件,然后删除jre.tar.gz;然后用类似的方式处理tomcat。

接下来使用ENV配置java与tomcat的环境变量,由于tomcat服务会默认监听8080端口,所以使用EXPOSE暴露端口号。最后使用ENTRYPOINT设置启动命令,使tomcat服务随容器启动而启动。

FROM ubuntu
WORKDIR /var/tmp
RUN apt-get update && \
    apt-get install -y wget && \
    wget --no-check-certificate --no-cookies --header "Cookie: o\fraclelicense=accept-securebackup-cookie"  http://download.o\fracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jre-8u144-linux-x64.tar.gz && \
    tar -xzf jre-8u144-linux-x64.tar.gz && \
    rm jre-8u144-linux-x64.tar.gz
RUN wget "http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.0.45/bin/apache-tomcat-8.0.45.tar.gz" && \
    tar -xzf apache-tomcat-8.0.45.tar.gz && \
    rm apache-tomcat-8.0.45.tar.gz
ENV JAVA_HOME /var/tmp/jre1.8.0_144
ENV CATALINA_HOME /var/tmp/apache-tomcat-8.0.45
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin
EXPOSE 8080
ENTRYPOINT /var/tmp/apache-tomcat-8.0.45/bin/startup.sh && /bin/bash

ONBUILD指令

  • ONBUILD添加一个将来执行的触发器(trigger);
    格式:ONBUILD <其它指令>

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如RUN, COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
ONBUILD指令的具体执行步骤

  1. 在构建过程中,ONBUILD指令会添加到触发器指令镜像元数据中,这些触发器指令并不会在当前构建过程中执行。
  2. 在构建过程后,触发器指令会被存储在镜像详情中,其主键是OnBuild,可以使用docker inspect命令查看。
  3. 在之后该镜像可能作为其他Dockerfile中FROM指令的参数。在构建过程中,FROM指令会查找ONBUILD触发器指令,并且会以它们注册的顺序执行。若有触发器指令执行失败,则FROM指令被中止,并返回失败;若所有触发器指令执行成功,则FROM指令完成并继续执行下面的指令。在镜像构建完成后,触发器指令会被清除,不会被子孙镜像继承。

ONBUILD指令的实例

  1. 首先编写一个Dockerfile文件,内容如下所示:
FROM busybox
ONBUILD RUN touch 1.txt
  1. 利用上面的Dockerfile文件构建一个新镜像:docker build -t image1 .。执行docker run image1 cat 1.txt,提示:cat: can't open '1.txt': No such file or directory。可以知道基于image1镜像构建的容器中不存在1.txt文件。我们通过inspect image1,在里面可以找到。
"OnBuild":[
    "RUN touch 1.txt"
],
  1. 编写一个新的Dockerfile文件,内容如下所示:
FROM image1
RUN echo 'hello'
  1. 利用第三步创建的的Dockerfile文件构建一个新镜像:docker build -t image2 .。如下所示:在执行完FROM指令后,首先执行的是触发器,也就是# Executing 1 build trigger...,该指令创建了一个1.txt文件。然后才执行RUN echo 'hello'。执行docker run image2 cat 1.txt,执行成功!!
[root@localhost dir1]# docker build -t image2 .
Sending build context to Docker daemon 2.048 kB
Step 1/2 : FROM image1
# Executing 1 build trigger...
Step 1/1 : RUN touch 1.txt
 ---> Running in 5c9b99ef7801
 ---> 4c5b719176b8
Removing intermediate container 5c9b99ef7801
Step 2/2 : RUN echo 'hello'
 ---> Running in 8a2598e90e1f
hello
 ---> f5642bb4a975
Removing intermediate container 8a2598e90e1f
Successfully built f5642bb4a975
[root@localhost dir1]# docker run image2 cat 1.txt`
[root@localhost dir1]#

VOLUME指令

  • VOLUME定义匿名卷;
    格式:VOLUME ["<路径1>", "<路径2>"...]或VOLUME <路径>

之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于数据卷(volume)中,后面的章节我们会进一步介绍Docker数据卷的概念。

参考文献:《Docker容器与容器云p179》。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值