在上一讲中,我们通过Dockerfile构建了自定义镜像,我们知道:Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。如果可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么类似于无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。这一讲就来详细介绍一下Dockerfile。

1、什么是Dockerfile

Dockerfile是用来构建Docker镜像的文件,它是由一系列指令和参数构成的脚本,每条指令对应Linux下面的一条命令。Docker可以通过获取Dockerfile编写的命令自动Build出一个新的镜像,里面的Docker内建命令会在已有的image下创建一个新的定制image。有了Dockerfile,当需要定制自己额外的需求时,只需在Dockerfile上添加或者修改指令,重新生成image即可,省去了敲命令的麻烦。

2、Dockerfile内容基本知识

(1)基本知识

  • 每条保留字指令都必须为大写字母且后面至少有一个参数。
  • 指令从上到下顺序执行。
  • #表示注释。
  • 每条指令都会创建一个新的镜像层,并对镜像进行提交。

(2)在Dockerfile中使用变量的方式

第一种:$varname

第二种:${varname}

第三种:${varname:-default value}

第四种:$(varname:+default value}

说明:

第一种和第二种是一个意思,只是不同的呈现形式。

第三种表示:当变量不存在使用-号后面的值

第四种表示:当变量存在时使用+号后面的值(当然不存在也是使用后面的值)

(3)构建过程

  1. docker会从Dockerfile文件头FROM指定的基础镜像运行一个容器.
  2. 执行一条指令并对容器作出修改。
  3. 执行类似docker commit的操作,提交一个新的镜像层,创建出新的镜像层。
  4. docker在基于刚提交的镜像运行一个新的容器。
  5. 执行dockerfile中的下一条指令直到所有指令都执行完。

说明:docker会删除中间层创建的容器,但不会删除中间层镜像,所以通过使用docker run运行一个中间层容器,从而查看每一步构建后的镜像状态,达到调试的目的。

3、Dockerfile的基本结构

Dockerfile 分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,其中#是Dockerfile中的注释。我们来看一下上一讲的小例子,其基本内容如下:

#volume test
#继承centos镜像
FROM centos
#在上面继承的centos目录下建立两个数据卷
VOLUME ["/dataVolumeContainer1","/dataVolumeContainer2"]
#容器启动时需要执行的指令
CMD echo "finished and success!!!"
CMD /bin/bash

 这个例子是比较简单的,接下来看一下完整的例子,内容如下:

# This my first nginx Dockerfile
# Version 1.0

# Base images 基础镜像
FROM centos

#MAINTAINER 维护者信息
MAINTAINER tianfeiyu 

#ENV 设置环境变量
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD  文件放在当前目录下,拷过去会自动解压
ADD nginx-1.8.0.tar.gz /usr/local/  
ADD epel-release-latest-7.noarch.rpm /usr/local/  

#RUN 执行以下命令 
RUN rpm -ivh /usr/local/epel-release-latest-7.noarch.rpm
RUN yum install -y wget lftp gcc gcc-c++ make openssl-devel pcre-devel pcre && yum clean all
RUN useradd -s /sbin/nologin -M www

#WORKDIR 相当于cd
WORKDIR /usr/local/nginx-1.8.0 

RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install

RUN echo "daemon off;" >> /etc/nginx.conf

#EXPOSE 映射端口
EXPOSE 80

#CMD 运行以下命令
CMD ["nginx"]

关于上面文件的内容中操作指令的介绍将在下一部分内容介绍。 

4、Dockerfile的操作指令介绍

Docker按照从上到下的顺序运行Dockerfile的指令。为了指定基本映像,文件内容的第一条指令必须是FROM接下来就介绍一下这些基本命令。

(1)FROM

作用:指定基础镜像,必须是第一个命令,放在最前面。

格式:

FROM <image>

FROM <image>:<tag>

FROM <image>@<digest>

示例:

FROM mysql:5.6

说明:tag或digest是可选的,如果不使用这两个值时,会使用latest版本的基础镜像。

(2)MAINTAINER

作用:指明镜像维护者的名字和邮箱地址。

格式:

MAINTAINER <name>

示例:

MAINTAINER YHT

MAINTAINER YHT@163.com

(3)RUN

作用:容器构建时需要运行的命令。

执行方式:

方式一:shell执行

格式: RUN <command>

方式二:exec执行

格式: RUN ["executable", "param1", "param2"]

示例:

RUN ["executable", "param1", "param2"]

RUN apk update

RUN ["/etc/execfile", "arg1", "arg1"]

说明:RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache

(4)ADD

作用:将宿主机目录下的文件拷贝到镜像,ADD命令会自动处理URL和解压tar压缩包(网络压缩资源不会被解压),可以访问网络资源。

格式:

ADD <src>... <dest>

ADD ["<src>",... "<dest>"]     用于支持包含空格的路径

示例:

ADD hom* /mydir/            # 添加所有以"hom"开头的文件

ADD hom?.txt /mydir/      # ? 替代一个单字符,例如:"home.txt"

ADD test relativeDir/       # 添加 "test" 到 工作目录/relativeDir/

ADD test /absoluteDir/    # 添加 "test" 到 /absoluteDir/

说明:所有拷贝到容器中的文件和文件夹的权限为0755,其uid和gid为0。ADD只有在build镜像的时候才会运行一次,后面运行容器的时候就不会被重新加载。如果写成一个url,那么ADD就类似于wget命令,例如:ADD http://www.w3.com。当src为一个目录的时候,会自动把目录下的文件复制过去,目录本身不会复制。如果src为多个文件,dest一定要是一个目录。

关于ADD文件复制准则的说明:

  •  如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并直接被创建为<dest>;如果<dest>以/结尾,则文件名URL指定的文件将被直接下载,并保存为<dest>/<filename>,注意,URL不能是ftp格式的url。
  • 如果<src>是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令,然后,通过URL获取到的tar文件将不会自动展开。
  • 如果<src>有多个,或其间接或直接使用了通配符,则<dest>必须是一个以/结尾的目录路径;如果<dest>不以/结尾,则其被视作一个普通文件,<src>的内容将被直接写入到<dest>。

(5)COPY

作用:功能类似ADD,拷贝文件和目录到镜像中,将从构建上下文目录中<源路径>的文件/目录复制到新的一层镜像内的<目标路径>位置。但是不会自动解压文件,也不能访问网络资源。

格式:

COPY src dest
COPY ["src","dest"]

示例:

COPY index.html /var/www/html

说明:在路径中有空白字符时,通常使用第二种格式 。

关于COPY文件复制准则的说明:

  • <src>必须是build上下文中的路径,即只能放在workshop这个工作目录下,不能是其父目录中的文件。
  • 如果<src>是目录,其内部文件或者子目录会被递归复制,但<src>目录自身不会被复制。
  • 如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且dest目录必须以/结尾。
  • 如果<dest>事先不存在,它将会被自动创建,这包括其父目录路径。

(6)CMD

作用:指定一个容器启动时要运行的命令。Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被dockerr run之后的参数替换。

格式:

CMD ["executable","param1","param2"] (执行可执行文件,优先)

CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)

CMD command param1 param2 (执行shell内部命令)

示例:

CMD echo "success"

说明:CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。

(7)ENTRYPOINT

作用:指定一个容器启动时要运行的命令。ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序以及参数。

格式:

ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)

ENTRYPOINT command param1 param2 (shell内部命令)

示例:

FROM ubuntu

ENTRYPOINT ["top", "-b"]

CMD ["-c"]

说明:ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。Dockerfile中只允许有一个ENTRYPOINT命令,如果指定多个时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。

(8)LABEL

作用:用于为镜像添加元数据。

格式:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

示例:

LABEL version="1.0" description="这是centos的1.0版本" by="YHT"

说明:使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。

(9)ENV

作用:在构建镜像的过程中设置环境变量。

格式:

ENV <key> <value> #<key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量

ENV <key>=<value> ... #可以设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行

示例:

ENV MY_PATH /usr/myDir

(10)EXPOSE

作用:指定当前容器与外界交互的端口,可以是多个。

格式:

EXPOSE <port> [<port>...]

示例:

EXPOSE 80 443

EXPOSE 8080

EXPOSE 11211/tcp 11211/udp

说明:EXPOSE并不会让容器的端口访问到主机。使用这个指令的目的是告诉应用程序:在容器内应用程序会使用的端口。这是docker处于安全的目的,不会自动打开端口。要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口。

(11)VOLUME

作用:用于指定数据持久化的目录

格式:

VOLUME ["/path/to/dir"]

示例:

VOLUME ["/data"]

VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"

说明:

一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:

a.卷可以容器间共享和重用。

b.容器并不一定要和其它容器共享卷。

c.修改卷后会立即生效。

d.对卷的修改不会对镜像产生影响。

e.卷会一直存在,直到没有任何容器在使用它。

(12)WORKDIR

作用:指定容器创建之后,终端默认登陆进入的工作目录,相当于一个落脚点。

格式:

WORKDIR /path/to/workdir

示例:

WORKDIR /a  (这时工作目录为/a)

WORKDIR b  (这时工作目录为/a/b)

WORKDIR c   (这时工作目录为/a/b/c)

说明:通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。

(13)USER

作用:用于指定镜像为什么用户去运行。

格式:  

USER user  

USER user:group  

USER uid  

USER uid:gid  

USER user:gid  

USER uid:group

示例:

USER www

说明:使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。例如:USER nginx,镜像就会以nginx的身份运行。

(14)ARG

作用:用于指定传递给构建运行时的变量。

格式:

ARG <name>[=<default value>]

示例:

ARG site ARG build_user=www

(15)ONBUILD

作用:用于设置镜像触发器。当一个镜像被用作其他镜像的基础镜像时,这个触发器会被执行。当子镜像被构建时,会插入触发器中的指令。

格式:

ONBUILD [INSTRUCTION]

示例:

ONBUILD ADD . /app/src

ONBUILD RUN /usr/local/bin/python-build --dir /app/src

说明:当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发。

5、关于Dockerfile指令的几点说明

(1)ENTRYPOINT 和 CMD 的区别

a.相同点:

  • 只能写一条,如果写了多条,那么只有最后一条生效
  • 容器启动时才运行,运行时机相同

b.不同点:

  • ENTRYPOINT不会被运行的command覆盖,而CMD则会被覆盖
  • 如果我们在Dockerfile种同时写了ENTRYPOINT和CMD,并且CMD指令不是一个完整的可执行命令,那么CMD指定的内容将会作为ENTRYPOINT的参数
  • ENTRYPOINT指定了该镜像启动时的入口,CMD则指定了容器启动时的命令。

(2)ADD和COPY的说明

ADD包含了类似tar的解压功能,如果只是单纯复制文件,建议使用COPY,而且,两者的源文件路径使用Dockerfile相对路径,目标路径使用绝对路径。COPY与ADD的区别在于:COPY的只能是本地文件,其他用法一致。

6、Dockerfile指令总结

Dockerfile常用指令介绍

指令

作用

FROM

指定基础镜像,必须是第一个命令,放在最前面

MAINTAINERR

指明镜像维护者的名字和邮箱地址

RUN

容器构建时需要运行的命令

ADD

将宿主机目录下的文件拷贝到镜像,ADD命令会自动处理URL和解压tar压缩包(网络压缩资源不会被解压),可以访问网络资源

COPY

功能类似ADD,拷贝文件和目录到镜像中,将从构建上下文目录中<源路径>的文件/目录复制到新的一层镜像内的<目标路径>位置。但是不会自动解压文件,也不能访问网络资源

CMD

指定一个容器启动时要运行的命令。Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被dockerr run之后的参数替换

ENTRYPOINT

指定一个容器启动时要运行的命令。ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序以及参数

LABEL

用于为镜像添加元数据

ENV

在构建镜像的过程中设置环境变量

EXPOSE

指定当前容器与外界交互的端口,可以是多个

VOLUME

用于指定数据持久化的目录

WORKDIR

指定容器创建之后,终端默认登陆进入的工作目录,相当于一个落脚点

USER

用于指定镜像为什么用户去运行

ARG

用于指定传递给构建运行时的变量

ONBUILD

用于设置镜像触发器。当一个镜像被用作其他镜像的基础镜像时,这个触发器会被执行。当子镜像被构建时,会插入触发器中的指令

简单介绍一下部分指令的意义,以便于加深理解:

(1)FROM:基础镜像。谁创造了我?吃水不忘挖井人。

(2)MAINTAINERR:维护者信息。告诉别人,谁创造了它。

(3)RUN:把命令前面加上RUN。唯命是从,想让他干啥。

(4)ADD:拷贝文件,自动解压。添加内容到容器。

(5)WORKDIR:当前工作目录。我是cd,今天化了妆。

(6)VOLUME:目录挂载。存放东西的地方。

(7)EXPOSE:端口。要打开哪扇门才能与之通信。

(8)RUN:进程要一直运行下去。生命不息,奋斗不止。精诚所至,金石为开。