关于Dockerfile

Dockerfile 是 Docker 中⽤于定义镜像⾃动化构建流程的配置⽂件,在 Dockerfile 中,包含了构建镜像过程中需要执⾏的命令和其他操作。通过 Dockerfile 我们可以更加清晰、明确的给定 Docker 镜像的制作过程,⽽由于其仅是简单、⼩体积的⽂件,在⽹络等其他介质中传递的速度极快,能够更快的帮助我们实现容器迁移和集群部署。

Dockerfile 的定义就是针对⼀个名为 Dockerfile 的⽂件,其虽然没有扩展名,但本质就是⼀个⽂本⽂件
Dockerfile 的内容很简单,主要以两种形式呈现,⼀种是注释⾏,另⼀种是指令⾏(由指令和参数组成)。

# Comment
INSTRUCTION arguments
Dockerfile的结构

可以将Dockerfile理解为一个由上而下执行指令的脚本文件
Dockerfile的指令可以分为五大类,这五大类未必会都出现在一个Dockerfile里,但却会给基于这个Dockerfile构建的镜像形成不同的影响:

  • 基础指令:⽤于定义新镜像的基础和性质
  • 控制指令:是指导镜像构建的核⼼部分,⽤于描述镜像在构建过程中需要执⾏的命令。
  • 引⼊指令:⽤于将外部⽂件直接引⼊到构建镜像内部。
  • 执⾏指令:能够为基于镜像所创建的容器,指定在启动时需要执⾏的脚本或命令。
  • 配置指令:对镜像以及基于镜像所创建的容器,可以通过配置指令对其⽹络、⽤户等内容进⾏配置。
常见的Dockerfil指令

FROM
通常来说,我们不会从零开始搭建⼀个镜像,⽽是会选择⼀个已经存在的镜像作为我们新镜像的基础,这种⽅式能够⼤幅减少我们的时间。
在 Dockerfile ⾥,我们可以通过 FROM 指令指定⼀个基础镜像,接下来所有的指令都是基于这个镜像所展开的。在镜像构建的过程中,Docker 也会先获取到这个给出的基础镜像,再从这个镜像上进⾏构建操作。

FROM 指令⽀持三种形式,不管是哪种形式,其核⼼逻辑就是指出能够被 Docker 识别的那个镜像,好让 Docker 从那个镜像之上开始构建⼯作

FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

既然选择⼀个基础镜像是构建新镜像的根本,那么 Dockerfile 中的第⼀条指令必须是 FROM 指令,因为没有了基础镜像,⼀切构建过程都⽆法开展。当然,⼀个 Dockerfile 要以 FROM 指令作为开始并不意味着 FROM 只能是 Dockerfile 中的第⼀条指令。在 Dockerfile 中可以多次出现 FROM 指令,当 FROM 第⼆次或者之后出现时,表⽰在此刻构建时,要将当前指出镜像的内容合并到此刻构建镜像的内容⾥。这对于我们直接合并两个镜像的功能很有帮助。

RUN
镜像的构建虽然是按照指令执⾏的,但指令只是引导,最终⼤部分内容还是控制台中对程序发出的命令,⽽ RUN 指令就是⽤于向控制台发送命令的指令。

在 RUN 指令之后,直接拼接上需要执⾏的命令,在构建时,Docker 就会执⾏这些命令,并将它们对⽂件系统的修改记录下来,形成镜像的变化。

RUN <command>
RUN ["executable", "param1", "param2"]

RUN 指令是⽀持 \ 换⾏的,如果单⾏的长度过长,建议对内容进⾏切割,⽅便阅读。⽽事实上,我们会经常看到 \ 分割的命令,例如在上⾯我们贴出的 Redis 镜像的 Dockerfile ⾥。

ENTRYPOINT 和 CMD
基于镜像启动的容器,在容器启动时会根据镜像所定义的⼀条命令来启动容器中进程号为 1 的进程。⽽这个命令的定义,就是通过 Dockerfile 中的 ENTRYPOINT 和 CMD 实现的。

ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2

ENTRYPOINT 指令和 CMD 指令的⽤法近似,都是给出需要执⾏的命令,并且它们都可以为空,或者说是不在 Dockerfile ⾥指出。
当 ENTRYPOINT 与 CMD 同时给出时,CMD 中的内容会作为 ENTRYPOINT 定义命令的参数,最终执⾏容器启动的还是 ENTRYPOINT 中给出的命令。

EXPOSE
Docker在未做特殊定义的前提下,我们直接连接容器⽹络,只能访问容器明确暴露的端口。⽽我们之前介绍的是在容器创建时通过选项来暴露这些端口。

由于我们构建镜像时更了解镜像中应⽤程序的逻辑,也更加清楚它需要接收和处理来⾃哪些端口的请求,所以在镜像中定义端口暴露显然是更合理的做法。

通过 EXPOSE 指令就可以为镜像指定要暴露的端口。

EXPOSE <port> [<port>/<protocol>...]

当我们通过 EXPOSE 指令配置了镜像的端口暴露定义,那么基于这个镜像所创建的容器,在被其他容器通过 --link 选项连接时,就能够直接允许来⾃其他容器对这些端口的访问了。

VOLUME
在⼀些程序⾥,我们需要持久化⼀些数据,⽐如数据库中存储数据的⽂件夹就需要单独处理。在之前的⼩节⾥,我们提到可以通过数据卷来处理这些问题。

但使⽤数据卷需要我们在创建容器时通过 -v 选项来定义,⽽有时候由于镜像的使⽤者对镜像了解程度不⾼,会漏掉数据卷的创建,从⽽引起不必要的⿇烦。

还是那句话,制作镜像的⼈是最清楚镜像中程序⼯作的各项流程的,所以它来定义数据卷也是最合适的。所以在 Dockerfile ⾥,提供了 VOLUME 指令来定义基于此镜像的容器所⾃动建⽴的数据卷。

VOLUME ["/data"]

在 VOLUME 指令中定义的⽬录,在基于新镜像创建容器时,会⾃动建⽴为数据卷,不需要我们再单独使⽤ -v 选项来配置了。

COPY 和 ADD
在制作新的镜像的时候,我们可能需要将⼀些软件配置、程序代码、执⾏脚本等直接导⼊到镜像内的⽂件系统⾥,使⽤ COPY 或 ADD 指令能够帮助我们直接从宿主机的⽂件系统⾥拷贝内容到镜像⾥的⽂件系统中。

COPY [--chown=<user>:<group>] <src>...<dest>
ADD [--chown=<user>:<group>] <src>...<dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

COPY 与 ADD 指令的定义⽅式完全⼀样,需要注意的仅是当我们的⽬录中存在空格时,可以使⽤后两种格式避免空格产⽣歧义。
对⽐ COPY 与 ADD,两者的区别主要在于 ADD 能够⽀持使⽤⽹络端的 URL 地址作为 src 源,并且在源⽂件被识别为压缩包时,⾃动进⾏解压,⽽ COPY 没有这两个能⼒。

虽然看上去 COPY 能⼒稍弱,但对于那些不希望源⽂件被解压或没有⽹络请求的场景,COPY 指令是个不错的选择。

构建镜像

构建镜像的命令为 docker build

docker build ./webapp

该命令可以接受一个参数,这个参数是一个URL但是并不是Dockerfile文件的路径。在 docker build ⾥,这个我们给出的⽬录会作为构建的环境⽬录,我们很多的操作都是基于这个⽬录进⾏的。例如,在我们使⽤ COPY 或是 ADD 拷贝⽂件到构建的新镜像时,会以这个⽬录作为基础⽬录。

在默认情况下, docker build 也会从这个⽬录下寻找名为 Dockerfile 的⽂件,将它作为 Dockerfile 内容的来源。如果我们的 Dockerfile ⽂件路径不在这个⽬录下,或者有另外的⽂件名,我们可以通过 -f选项单独给出 Dockerfile ⽂件的路径。同时,可以通过-t来生成镜像名称

docker build -t webapp:latest -f ./webapp/a.Dockerfile ./webapp