Dockerfile指令

我们已经看过一些Dockerfile中可用的指令,如RUN和EXPOSE。

实际上还可以在Dockerfile中放入很多其他指令,如CMD、ENTRYPOINT、ADD、COPY、VOLUME、WORKDIR、USER、ONBUILD和ENV等。

可在https://docs.docker.com/engine/reference/builder/#arg查看Dockerfile中可以使用的全部指令清单。下面将介绍这些指令的具体功能。

CMD

CMD指令用于制定一个容器启动时要运行的命令。

这有点类似于RUN指令,只是RUN指令是指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令。

三种格式分别为:

  • CMD ["executable", "param1", "param2"] 使用exec执行,推荐方式;
  • CMD command param1 param2 在/bin/sh中执行,提供给需要交互的应用;
  • CMD ["param1", "param2"] 提供给ENTRYPOINT的默认参数;

这和使用docker run命令启动容器时指定要运行的命令非常类似:

# docker run -it test/static_web /bin/true

## 上面的命令等效于在Dockerfile中使用如下代码
CMD ["/bin/true"]

也可以为要运行的命令指定参数:

## 这里将 -l 参数传递给 /bin/bash 命令
CMD ["/bin/bash","-l"]

## 需要注意的是,要运行的命令是存放在一个数组结构中。

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

## 假设我们Dockerfile文件中加入CMD
#vi Dockerfile 

# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
ENV REFRESHED_AT 2017-05-18
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'Hi, I am in your container' > /usr/share/nginx/html/index.html
RUN sed -i '22d' /etc/nginx/sites-enabled/default
EXPOSE 80
CMD ["/bin/bash"]

## 重新构建一个新的镜像,镜像名为static_test,并基于这个镜像启动一个新的容器
# docker build -t="test/static_test" .
# docker run -t -i test/static_test
root@40786fce97d2:/# exit
## 这里docker run命令的末尾我们并没有指定要运行什么命令。
## 实际上,Docker使用了CMD中的命令。


## 如果我们指定了要运行的命令,如这里运行的/bin/ps,容器并没有启动shell
## 通过命令行参数覆盖了CMD中指定的命令,容器运行后列出正在运行的进程列表,之后停止了容器。
# docker run -t -i test/static_test /bin/ps
   PID TTY          TIME CMD
     1 ?        00:00:00 ps

在Dockerfile中只能指定一条CMD指令。如果指定了多条,也只有最后一条CMD指令会被使用。 

ENTRYPOINT

ENTRYPOINT指令与CMD指令非常类似,区别在于我们可以在docker run命令行中覆盖CMD命令,而ENTRYPOINT指令提供的命令则不容易(如果确实需要,可以在运行时通过docker run的--entrypoint标志覆盖)在启动时被覆盖。实际上,docker run命令行中指定的任何参数都会被当做参数再次传递给ENTRYPOINT指令中指定的命令。

有两种格式:

  • ENTRYPOINT ["executable", "param1", "param2"]
  • ENTRYPOINT command param1 param2(shell中执行)
ENTRYPOINT ["/usr/sbin/nginx"]

## 通过数组的方式为命令指定相应的参数
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]

 重新构建镜像,添加ENTRYPOINT ["/usr/sbin/nginx"]指令:

ENTRYPOINT ["/usr/sbin/nginx"]

# docker build -t="test/static_web" .

# docker run -d -p 80 test/static_web -g "daemon off;"
207886d3d33e78ce3bafe07ae3b5589e37c29e4691f23c77a61c6a0e56d9a95a
# docker ps -l
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                   NAMES
207886d3d33e        test/static_web     "/usr/sbin/nginx -g    10 seconds ago      Up 9 seconds        0.0.0.0:32774->80/tcp   jolly_kilby

在这里-g "daemon off;"参数传递给了ENTRYPOINT指定的命令,在这里命令为/usr/sbin/nginx -g "daemon off;"。我们也可以组合ENTRYPOINT和CMD指令完成一些巧妙的工作。

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

## 如果启动容器时不指定任何参数,CMD指令会被传递,显示Nginx帮助信息;
## 同时也支持通过docker run命令行为该命令指令指定可覆盖的选项或标志。

每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最后一个起效。

WORKDIR

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

我们可以使用该指令为Dockerfile中后续的一些列指令设置工作目录,也可以为最终的容器设置工作目录。

格式为: WORKDIR /path/to/workdir

## 为特定的指令设置不同的工作目录
WORKDIR /opt/webapp/db
## 将工作目录切换为/opt/webapp/db 后运行bundle install
RUN bundle install
WORKDIR /opt/webapp
## 将工作目录切换为/opt/webapp后设置ENTRYPOINT指令来启动rackup命令。
ENTRYPOINT ["rackup"]

通过-w标志在运行时覆盖工作目录,该命令会将容器内的工作目录设置为/var/log。

docker run -it -w /var/log ubuntu pwd
/var/log

ENV

ENV指令用来在镜像构件过程中设置环境变量,格式为: ENV <key> <value>

ENV RVM_PATH /home/rvm

这个新的环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样

RUN gem install unicorn
## 这条RUN指令就会以如下的方式执行
RVM_PATH=/home/rvm/ gem install unicorn


## 也能在其他命令中直接使用这些环境变量
ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR
## 这里设定了一个TARGET_DIR的环境变量,并在WORKDIR中调用了这个环境变量
## 所以这里的WORKDIR指令的值会被设置为/opt/app

这些环境变量也会被持久保存到从我们的镜像创建的任何容器中。如果我们在使用ENV指令构建容的容器中运行env命令,将会看到我们所设置的环境变量。

也通过docker run命令行的 -e 标志来传递环境变量。这些环境变量只会在运行时有效

# docker run -it -e "WEB_PORT=8080" ubuntu env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=2688f9db5f34
TERM=xterm
WEB_PORT=8080
HOME=/root

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

USER

USER指令用来指定该镜像会以什么样的用户去运行

格式为:USER daemon

USER nginx

基于该镜像启动的容器会以nginx用的身份来运行。我们可以指定用户名或UID以及组或GID,甚至是两者结合。

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

也可以在docker run命令中通过-u选项来覆盖该指令指定的值,如果不通过USER指令指定用户,默认用户为root。

VOLUME

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

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

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

格式为: VOLUME ["/data"]

也可以通过指定数组的方式指定多个卷

格式为: VOLUME ["/data1", "/data2"]

ADD

ADD指令用来将构建环境下的文件和目录复制到镜像中。格式为:ADD <src> <dest>

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

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

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

源文件也可以使用URL的格式:

ADD http://wordpress.org/latest.zip /root/wordpress.zip

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

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

这里会将归档文件latest.tar.gz 解压到/var/www/wordpress/目录下。如果目的位置不存在的话,Docker将会为我们创建这个全路径,包括路径中的任何目录。新创建的文件和目录的模式为0755,并且UID和GID都是0。

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

COPY

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

格式为:COPY <src> <dest>

COPY conf.d/ /etc/apache2/

这条指令会把本地conf.d目录中的文件复制到/etc/apache2/目录中。文件源路径必须是一个与当前构建环境相对的文件或者目录,本地文件都放到和Dockerfile同一个目录下。不能复制该目录之外的任何文件,因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中进行的,任何位于构建环境之外的东西都不可用。COPY指令的目的位置则必须是容器内部的一个绝对路径。

任何由指定创建的文件或者目录的UID和GID都会设置为0。如果目的位置不存在,Docker将会自动创建所有需要的目录结构。

ONBUILD

ONBUILD指令能为镜像添加触发器(trigger)。当一个镜像被用作其他镜像的基础镜像,该镜像的触发器将会被执行。触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在FROM之后指定的。触发器可以是任何构建指令。

格式为:ONBUILD [INSTRUCTION]

比如,我们为apache镜像构建一个全新的Dockerfile,该镜像名为static_apache2

# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
RUN apt-get update
RUN 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"]

# docker build -t="test/static_apache2" .

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

我们可以通过构建一个名为webapp的镜像来看看如何使用镜像模板功能:

FROM test/static_apache2
MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
ENV APPLICATION_name webapp
ENV ENVIRONMENT development

# docker build -t="test/webapp" .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon 
Step 0 : FROM test/static_apache2
# Executing 1 build triggers
Trigger 0, ADD . /var/www/ 
Step 0 : ADD . /var/www/
 ---> 92a790340715
Removing intermediate container cf7accb5ca5b
Step 1 : MAINTAINER Bourbon Tian "bourbon@1mcloud.com"
...
Successfully built 36ae30d2e972

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

ONBUILD触发器会按照在父镜像中指定的顺序执行,并且只能被继承一次。如果再基于test/webapp构建一个镜像,则新镜像不会继承test/static_apache2中的触发器。

值得注意的是,有好几条指令是不能用在ONBUILD指令中,其中包括FROM、MAINTAINER和ONBUILD本身。之所以这么规定是为了防止在Dockerfile构建过程中产生递归调用的问题。

 

posted @ 2017-05-22 17:24  Bourbon.Tian  阅读(...)  评论(...编辑  收藏