在上一讲中,我们通过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)构建过程
- docker会从Dockerfile文件头FROM指定的基础镜像运行一个容器.
- 执行一条指令并对容器作出修改。
- 执行类似docker commit的操作,提交一个新的镜像层,创建出新的镜像层。
- docker在基于刚提交的镜像运行一个新的容器。
- 执行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指令总结
指令 | 作用 |
---|---|
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:进程要一直运行下去。生命不息,奋斗不止。精诚所至,金石为开。