Dockerfile指令含义及使用案例简书

我们已经看过了一些Dockerfile中可用的指令,如RUNEXPOSE。但是,实际上还可以在Dockerfile中放入很多其他指令,这些指令包括CMDENTRYPOINTADDCOPYVOLUMEWORKDIRUSERONBUILDLABELSTOPSIGNALARGENV等。可以在http://docs.docker.com/ reference /builder/查看Dockerfile中可以使用的全部指令的清单。

1.CMD

CMD指令用于指定一个容器启动时要运行的命令。这有点儿类似于RUN指令,只是RUN指令是指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令。这和使用docker run命令启动容器时指定要运行的命令非常类似,比如代码清单4-45所示。

代码清单4-45 指定要运行的特定命令

$ sudo docker run -i -t jamtur01/static_web /bin/true

可以认为代码清单4-45所示的命令和在Dockerfile中使用代码清单4-46所示的CMD指令是等效的。

代码清单4-46 使用CMD指令

CMD ["/bin/true"]

当然也可以为要运行的命令指定参数,如代码清单4-47所示。

代码清单4-47 给CMD指令传递参数

CMD ["/bin/bash", "-l"]

这里我们将-l标志传递给了/bin/bash命令。

警告

需要注意的是,要运行的命令是存放在一个数组结构中的。这将告诉Docker按指定的原样来运行该命令。当然也可以不使用数组而是指定CMD指令,这时候Docker会在指定的命令前加上/bin/sh -c。这在执行该命令的时候可能会导致意料之外的行为,所以Docker推荐一直使用以数组语法来设置要执行的命令。

最后,还需牢记,使用docker run命令可以覆盖CMD指令。如果我们在Dockerfile里指定了CMD指令,而同时在docker run命令行中也指定了要运行的命令,命令行中指定的命令会覆盖Dockerfile中的CMD指令。

深刻理解CMDENTRYPOINT之间的相互作用关系也非常重要,我们将在后面对此进行更详细的说明。

让我们来更贴近一步来看看这一过程。假设我们的Dockerfile文件中有代码清单4-48所示的CMD指令。

代码清单4-48 覆盖Dockerfile文件中的CMD指令

CMD [ "/bin/bash" ]

可以使用docker build命令构建一个新镜像(假设镜像名为jamtur01/test),并基于此镜像启动一个新容器,如代码清单4-49所示。

代码清单4-49 用CMD指令启动容器

$ sudo docker run -t -i jamtur01/test 
root@e643e6218589:/_#_

注意到有什么不一样的地方了吗?在docker run命令的末尾我们并未指定要运行什么命令。实际上,Docker使用了CMD指令中指定的命令。

如果我指定了要运行的命令会怎样呢?如代码清单4-50所示。

代码清单4-50 覆盖本地命令

$ sudo docker run -i -t jamtur01/test /bin/ps 
PID TTY      TIME CMD 
1 ?  00:00:00 ps 
$

可以看到,在这里我们指定了想要运行的命令/bin/ps,该命令会列出所有正在运行的进程。在这个例子里,容器并没有启动shell,而是通过命令行参数覆盖了CMD指令中指定的命令,容器运行后列出了正在运行的进程的列表,之后停止了容器。

Dockerfile中只能指定一条CMD指令。如果指定了多条CMD指令,也只有最后一条CMD指令会被使用。如果想在启动容器时运行多个进程或者多条命令,可以考虑使用类似Supervisor这样的服务管理工具。

2.ENTRYPOINT

ENTRYPOINT指令与CMD指令非常类似,也很容易和CMD指令弄混。这两个指令到底有什么区别呢?为什么要同时保留这两条指令?正如我们已经了解到的那样,我们可以在docker run命令行中覆盖CMD指令。有时候,我们希望容器会按照我们想象的那样去工作,这时候CMD就不太合适了。而ENTRYPOINT指令提供的命令则不容易在启动容器时被覆盖。实际上,docker run命令行中指定的任何参数都会被当做参数再次传递给ENTRYPOINT指令中指定的命令。让我们来看一个ENTRYPOINT指令的例子,如代码清单4-51所示。

代码清单4-51 指定ENTRYPOINT指令

ENTRYPOINT ["/usr/sbin/nginx"]

类似于CMD指令,我们也可以在该指令中通过数组的方式为命令指定相应的参数,如代码清单4-52所示。

代码清单4-52 为ENTRYPOINT指令指定参数

ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

注意

从上面看到的CMD指令可以看到,我们通过以数组的方式指定ENTRYPOINT在想运行的命令前加入/bin/sh -c来避免各种问题。

现在重新构建我们的镜像,并将ENTRYPOINT设置为ENTRYPOINT ["/usr/sbin/ nginx"],如代码清单4-53所示。

代码清单4-53 用新的ENTRYPOINT指令重新构建static_web镜像

$ sudo docker build -t="jamtur01/static_web" .

然后,我们从jamtur01/static_web镜像启动一个新容器,如代码清单4-54所示。

代码清单4-54 使用docker run命令启动包含ENTRYPOINT指令的容器

$ sudo docker run –t -i jamtur01/static_web -g "daemon off;"

从上面可以看到,我们重新构建了镜像,并且启动了一个交互的容器。我们指定了-g "daemon off;"参数,这个参数会传递给用ENTRYPOINT指定的命令,在这里该命令为/usr/sbin/nginx -g "daemon off;"。该命令会以前台运行的方式启动Nginx守护进程,此时这个容器就会作为一台Web服务器来运行。

我们也可以组合使用ENTRYPOINTCMD指令来完成一些巧妙的工作。比如,我们可能想在Dockerfile里指定代码清单4-55所示的内容。

代码清单4-55 同时使用ENTRYPOINTCMD指令

ENTRYPOINT ["/usr/sbin/nginx"] 
CMD ["-h"]

此时当我们启动一个容器时,任何在命令行中指定的参数都会被传递给Nginx守护进程。比如,我们可以指定-g "daemon off";参数让Nginx守护进程以前台方式运行。如果在启动容器时不指定任何参数,则在CMD指令中指定的-h参数会被传递给Nginx守护进程,即Nginx服务器会以/usr/sbin/nginx -h的方式启动,该命令用来显示Nginx的帮助信息。

这使我们可以构建一个镜像,该镜像既可以运行一个默认的命令,同时它也支持通过docker run命令行为该命令指定可覆盖的选项或者标志。

提示

如果确实需要,用户也可以在运行时通过docker run--entrypoint标志覆盖ENTRYPOINT指令。

3.WORKDIR

WORKDIR指令用来在从镜像创建一个新容器时,在容器内部设置一个工作目录,ENTRYPOINT和/或CMD指定的程序会在这个目录下执行。

我们可以使用该指令为Dockerfile中后续的一系列指令设置工作目录,也可以为最终的容器设置工作目录。比如,我们可以如代码清单4-56所示这样为特定的指令设置不同的工作目录。

代码清单4-56 使用WORKDIR指令

WORKDIR /opt/webapp/db 
RUN bundle install 
WORKDIR /opt/webapp 
ENTRYPOINT [ "rackup" ]

这里,我们将工作目录切换为/opt/webapp/db后运行了bundle install命令,之后又将工作目录设置为/opt/webapp,最后设置了ENTRYPOINT指令来启动rackup命令。

可以通过-w标志在运行时覆盖工作目录,如代码清单4-57所示。

代码清单4-57 覆盖工作目录

$ sudo docker run -ti -w /var/log ubuntu pwd 
/var/log

该命令会将容器内的工作目录设置为/var/log

4.ENV

ENV指令用来在镜像构建过程中设置环境变量,如代码清单4-58所示。

代码清单4-58 在Dockerfile文件中设置环境变量

ENV RVM_PATH /home/rvm/

这个新的环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样,如代码清单4-59所示。

代码清单4-59 为RUN指令设置前缀

RUN gem install unicorn

该指令会以代码清单4-60所示的方式执行。

代码清单4-60 添加ENV前缀后执行

RVM_PATH=/home/rvm/ gem install unicorn

可以在ENV指令中指定单个环境变量,或者,从Docker 1.4开始可以像代码清单4-61所示那样指定多个变量。

代码清单4-61 使用ENV设置多个环境变量

ENV RVM_PATH=/home/rvm RVM_ARCHFLAGS="-arch i386"

也可以在其他指令中使用这些环境变量,如代码清单4-62所示。

代码清单4-62 在其他Dockerfile指令中使用环境变量

ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR

在这里我们设定了一个新的环境变量TARGET_DIR,并在WORKDIR中使用了它的值。因此实际上WORKDIR指令的值会被设为/opt/app

注意

如果需要,可以通过在环境变量前加上一个反斜线来进行转义。

这些环境变量也会被持久保存到从我们的镜像创建的任何容器中。所以,如果我们在使用ENV RVM_PATH /home/rvm/指令构建的容器中运行env命令,将会看到代码清单4-63所示的结果。

代码清单4-63 Docker容器中环境变量的持久化

root@bf42aadc7f09:~# env
. . . 
RVM_PATH=/home/rvm/ 
. . .

也可以使用docker run命令行的-e标志来传递环境变量。这些变量将只会在运行时有效,如代码清单4-64所示。

代码清单4-64 运行时环境变量

$ sudo docker run -ti -e "WEB_PORT=8080" ubuntu env 
HOME=/ 
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 
HOSTNAME=792b171c5e9f 
TERM=xterm 
WEB_PORT=8080

我们可以看到,在容器中WEB_PORT环境变量被设为了8080

5.USER

USER指令用来指定该镜像会以什么样的用户去运行,比如代码清单4-65所示。

代码清单4-65 使用USER指令

USER nginx

基于该镜像启动的容器会以nginx用户的身份来运行。我们可以指定用户名或UID以及组或GID,甚至是两者的组合,比如代码清单4-66所示。

代码清单4-66 指定USERGROUP的各种组合

USER user 
USER user:group
USER uid 
USER uid:gid 
USER user:gid 
USER uid:group

也可以在docker run命令中通过-u标志来覆盖该指令指定的值。

提示

如果不通过USER指令指定用户,默认用户为root

6.VOLUME

VOLUME指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。

  • 卷可以在容器间共享和重用。
  • 一个容器可以不是必须和其他容器共享卷。
  • 对卷的修改是立时生效的。
  • 对卷的修改不会对更新镜像产生影响。
  • 卷会一直存在直到没有任何容器再使用它。

卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库。我们将在第5章和第6章看到相关的例子。

可以像代码清单4-67所示的这样使用VOLUME指令。

代码清单4-67 使用VOLUME指令

VOLUME ["/opt/project"]

这条指令将会为基于此镜像创建的任何容器创建一个名为/opt/project的挂载点。

提示

docker cp是和VOLUME指令相关并且也是很实用的命令。该命令允许从容器复制文件和复制文件到容器上。可以从Docker命令行文档(https://docs.docker.com/engine/reference/ commandline/cp/)中获得更多信息。

也可以通过指定数组的方式指定多个卷,如代码清单4-68所示。

代码清单4-68 使用VOLUME指令指定多个卷

VOLUME ["/opt/project", "/data" ]

提示

第5章和第6章中经包括更多关于卷和如何使用卷的内容。如果现在就对卷功能很好奇,也可以在http://docs.docker.com/userguide/dockervolumes/读到更多关于卷的信息。

7.ADD

ADD指令用来将构建环境下的文件和目录复制到镜像中。比如,在安装一个应用程序时。ADD指令需要源文件位置和目的文件位置两个参数,如代码清单4-69所示。

代码清单4-69 使用ADD指令

ADD software.lic /opt/application/software.lic

这里的ADD指令将会将构建目录下的software.lic文件复制到镜像中的/opt/`` ``application/software.lic。指向源文件的位置参数可以是一个URL,或者构建上下文或环境中文件名或者目录。不能对构建目录或者上下文之外的文件进行ADD操作。

ADD文件时,Docker通过目的地址参数末尾的字符来判断文件源是目录还是文件。如果目标地址以/结尾,那么Docker就认为源位置指向的是一个目录。如果目的地址以/结尾,那么Docker就认为源位置指向的是目录。如

最后值得一提的是,ADD在处理本地归档文件(tar archive)时还有一些小魔法。如果将一个归档文件(合法的归档文件包括gzip、bzip2、xz)指定为源文件,Docker会自动将归档文件解开(unpack),如代码清单4-71所示。

代码清单4-71 将归档文件作为ADD指令中的源文件

ADD latest.tar.gz /var/www/wordpress/

这条命令会将归档文件latest.tar.gz解开到/var/www/wordpress/目录下。Docker解开归档文件的行为和使用带-x选项的tar命令一样:该指令执行后的输出是原目的目录已经存在的内容加上归档文件中的内容。如果目的位置的目录下已经存在了和归档文件同名的文件或者目录,那么目的位置中的文件或者目录不会被覆盖。

警告

目前Docker还不支持以URL方式指定的源位置中使用归档文件。这种行为稍显得有点儿不统一,在以后的版本中应该会有所变化。

最后,如果目的位置不存在的话,Docker将会为我们创建这个全路径,包括路径中的任何目录。新创建的文件和目录的模式为0755,并且UID和GID都是0。

注意

ADD指令会使得构建缓存变得无效,这一点也非常重要。如果通过ADD指令向镜像添加一个文件或者目录,那么这将使Dockerfile中的后续指令都不能继续使用之前的构建缓存。

8.COPY

COPY指令非常类似于ADD,它们根本的不同是COPY只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decompression)的工作。COPY指令的使用如代码清单4-72所示。

代码清单4-72 使用COPY指令

COPY conf.d/ /etc/apache2/

这条指令将会把本地conf.d目录中的文件复制到/etc/apache2/目录中。

文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。

不能复制该目录之外的任何文件,因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中进行的。任何位于构建环境之外的东西都是不可用的。COPY指令的目的位置则必须是容器内部的一个绝对路径。

任何由该指令创建的文件或者目录的UID和GID都会设置为0。

如果源路径是一个目录,那么这个目录将整个被复制到容器中,包括文件系统元数据;如果源文件为任何类型的文件,则该文件会随同元数据一起被复制。在这个例子里,源路径以/结尾,所以Docker会认为它是目录,并将它复制到目的目录中。

如果目的位置不存在,Docker将会自动创建所有需要的目录结构,就像mkdir -p命令那样。

9.LABEL

LABEL指令用于为Docker镜像添加元数据。元数据以键值对的形式展现。我们可以来看一个例子,见代码清单4-73。

代码清单4-73 添加LABEL指令

LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"

LABEL指令以label="value"的形式出现。可以在每一条指令中指定一个元数据,或者指定多个元数据,不同的元数据之间用空格分隔。推荐将所有的元数据都放到一条LABEL指令中,以防止不同的元数据指令创建过多镜像层。可以通过docker inspect命令来查看Docker镜像中的标签信息,如代码清单4-74所示。

代码清单4-74 使用docker inspect命令查看容器标签

$ sudo docker inspect jamtur01/apache2
. . .
"Labels": {
   "version": "1.0",
   "location"="New York",
   "type"="Data Center",
   "role"="Web Server"
},

这里我们可以看到前面用LABEL指令定义的元数据信息。

注意

LABEL指令是在Docker 1.6版本中引入的。

10.STOPSIGNAL

STOPSIGNAL指令用来设置停止容器时发送什么系统调用信号给容器。这个信号必须是内核系统调用表中合法的数,如9,或者SIGNAME格式中的信号名称,如SIGKILL。

注意

STOPSIGNAL指令是在Docker 1.9版本中引入的。

11.ARG

ARG指令用来定义可以在docker build命令运行时传递给构建运行时的变量,我们只需要在构建时使用--build-arg标志即可。用户只能在构建时指定在Dockerfile文件中定义过的参数。

代码清单4-75 添加ARG指令

ARG build
ARG webapp_user=user

上面例子中第二条ARG指令设置了一个默认值,如果构建时没有为该参数指定值,就会使用这个默认值。下面我们就来看看如何在docker build中使用这些参数。

代码清单4-76 使用ARG指令

$ docker build --build-arg build=1234 -t jamtur01/webapp .

这里构建jamtur01/webapp镜像时,build变量将会设置为1234,而webapp_user变量则会继承设置的默认值user。

警告

读到这里,也许你会认为使用ARG来传递证书或者秘钥之类的信息是一个不错的想法。但是,请千万不要这么做。你的机密信息在构建过程中以及镜像的构建历史中会被暴露。

Docker预定义了一组ARG变量,可以在构建时直接使用,而不必再到Dockerfile中自行定义。

代码清单4-77 预定义ARG变量

HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy

要想使用这些预定义的变量,只需要给docker build命令传递--build-arg <variable>=<value>标志就可以了。

注意

ARG指令是在Docker 1.9版本中引入的,可以在Docker文档(https://docs.docker.com/ engine/reference/builder/#arg)中阅读详细说明。

12.ONBUILD

ONBUILD指令能为镜像添加触发器(trigger)。当一个镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行。

触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的。触发器可以是任何构建指令,比如代码清单4-78所示。

代码清单4-78 添加ONBUILD指令

ONBUILD ADD . /app/src 
ONBUILD RUN cd /app/src && make

上面的代码将会在创建的镜像中加入ONBUILD触发器,ONBUILD指令可以在镜像上运行docker inspect命令来查看,如代码清单4-79所示。

代码清单4-79 通过docker inspect命令查看镜像中的ONBUILD指令

$ sudo docker inspect 508efa4e4bf8 
. . . 
"OnBuild": [ 
"ADD . /app/src", 
"RUN cd /app/src/ && make" 
] 
. . .

比如,我们为Apache2镜像构建一个全新的Dockerfile,该镜像名为jamtur01/ apache2,如代码清单4-80所示。

代码清单4-80 新的ONBUILD镜像Dockerfile

FROM ubuntu:14.04 
MAINTAINER James Turnbull "[email protected]" 
RUN apt-get update && apt-get install -y apache2 
ENV APACHE_RUN_USER www-data 
ENV APACHE_RUN_GROUP www-data 
ENV APACHE_LOG_DIR /var/log/apache2 
ONBUILD ADD . /var/www/ 
EXPOSE 80 
ENTRYPOINT ["/usr/sbin/apache2"] 
CMD ["-D", "FOREGROUND"]

现在我们就来构建该镜像,如代码清单4-81所示。

代码清单4-81 构建apache2镜像

$ sudo docker build -t="jamtur01/apache2" . 
. . . 
Step 7 : ONBUILD ADD . /var/www/ 
---> Running in 0e117f6ea4ba 
---> a79983575b86 
Successfully built a79983575b86

在新构建的镜像中包含一条ONBUILD指令,该指令会使用ADD指令将构建环境所在的目录下的内容全部添加到镜像中的/var/www/目录下。我们可以轻而易举地将这个Dockerfile作为一个通用的Web应用程序的模板,可以基于这个模板来构建Web应用程序。

我们可以通过构建一个名为webapp的镜像来看看如何使用镜像模板功能。它的Dockerfile如代码清单4-82所示。

代码清单4-82 webappDockerfile

FROM jamtur01/apache2 
MAINTAINER James Turnbull "[email protected]" 
ENV APPLICATION_NAME webapp 
ENV ENVIRONMENT development

让我们看看构建这个镜像时将会发生什么事情,如代码清单4-83所示。

代码清单4-83 构建webapp镜像

$ sudo docker build -t="jamtur01/webapp" . 
. . . 
Step 0 : FROM jamtur01/apache2 
# Executing 1 build triggers 
Step onbuild-0 : ADD . /var/www/ 
---> 1a018213a59d 
---> 1a018213a59d 
Step 1 : MAINTAINER James Turnbull "[email protected]" 
. . . 
Successfully built 04829a360d86

可以清楚地看到,在FROM指令之后,Docker插入了一条ADD指令,这条ADD指令就是在ONBUILD触发器中指定的。执行完该ADD指令后,Docker才会继续执行构建文件中的后续指令。这种机制使我每次都会将本地源代码添加到镜像,就像上面我们做到的那样,也支持我为不同的应用程序进行一些特定的配置或者设置构建信息。这时,可以将jamtur01/apache2当做一个镜像模板。

ONBUILD触发器会按照在父镜像中指定的顺序执行,并且只能被继承一次(也就是说只能在子镜像中执行,而不会在孙子镜像中执行)。如果我们再基于jamtur01/webapp构建一个镜像,则新镜像是jamtur01/apache2的孙子镜像,因此在该镜像的构建过程中,ONBUILD触发器是不会被执行的。

注意

这里有好几条指令是不能用在ONBUILD指令中的,包括FROMMAINTAINERONBUILD本身。之所以这么规定是为了防止在 Dockerfile构建过程中产生递归调用的问题。

发布了201 篇原创文章 · 获赞 85 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Doudou_Mylove/article/details/102454341