三、Dockerfile
dockerfile 是一个文本格式的配置文件, 用户可以使用 Dockerfile 来快速创建自定义的镜像。
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。
指令:
包括 “配置指令" (配置镜像信息)和 “操作指令" (具体执行操作)
配置指令:
ARG (定义创建镜像过程中使用的变量)
格式:ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。
FROM:(指定所创建镜像的基础镜像,必备)*
格式:FROM <镜像> [AS <别名>]
必须是第一条指令,如果一个Dockerfile中创建多个镜像时,可以使用多个FROM指令(每个镜像一次)。
除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
LABEL:(添加镜像元数据标签信息)
格式:LABEL <键>=<值> <键>=<值> ...
如:LABEL author="xxx" date="xxx"
EXPOSE(声明镜像内服务监听的端口)
格式: EXPOSE <端口1> [<端口2>/<协议>...]
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务(即不会自动完成端口映射)。
在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P(大写) 时,会自动随机映射 EXPOSE 的端口。
要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
ENV(设置环境变量)
格式有两种:
-
ENV <key> <value>
-
ENV <key1>=<value1> <key2>=<value2>...
无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。在镜像启动的容器中也会存在。指令指定的环境变量在运行时可以被覆盖掉, 如 docker run --env <key>=<value> built_image 。
注意当一条 ENV 指令中同时为多个环境变量赋值并且值也是从环境变量读取时, 会为变量都赋值后再更新。
如:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"
下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。
ENTRYPOINT(指定镜像的默认入口命令)
格式有两种:
-
ENTRYPOINT ["executable", "param1", "param2"] exec调用执行;
-
ENTRYPOINT command param1 param2 shell中执行;
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。
该入口命令令会在启动容器时作为根命令执行, 所有传人值作为该命令的参数。
每个 Dockerfile 中只能有一个 ENTRYPOINT, 当指定多个时, 只有最后一个起效。
在运行时, 可以被 --entrypoint 参数覆盖掉, 如 docker run --entrypoint。
VOLUME(创建一个数据卷挂载点)
格式为:
-
VOLUME ["<路径1>", "<路径2>"...]
-
VOLUME <路径>
对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷中, 为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
USER(指定运行容器时的用户名或UID)
格式:USER <用户名>[:<用户组>]
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。
这个用户必须是事先建立好的,否则无法切换。
要临时获取管理员权限可以使用 gosu 命令
WORKDIR (为后续的RUN、CMD、ENTRYPOINT 指定配置工作目录)
格式: WORKDIR <工作目录路径>
可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径, 则会基于之前命令指定的路径(合并路径) 。为了避免出错,推荐 WORKDIR 指令中只使用绝对路径。
ONBUILD(指定当基于所生成镜像创建子镜像时,自动执行的操作指令)
格式:ONBUILD <其它指令>
ONBUILD 是一个特殊的指令,它后面跟的是其它指令, 而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。
HEALTHCHECK(配置所启动容器如何进行健康检查)
格式:
-
HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
-
HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常。
STOPSIGNAL(指定所创建镜像启动的容器接收退出的信号值)
STOPSIGNAL signal
SHELL(指定其他命令使用 shell 时的默认 shell 类型)
SHELL ["executable", "parameters"]
默认值为 ["/bin/sh","-c"]
对于 windows 系统, Shell 路径中使用了“\”作为分隔符,建议在 Dockerfile 开头添- 加# escap= ' 来指定转义符
操作指令:
RUN(运行指定命令)
其格式有两种:
1、shell 格式: RUN <命令>,就像直接在命令行中输入的命令一样。
2、exec 格式: RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。
每一个 RUN 的行为,就和手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。
Dockerfile 中每一个指令都会建立一层,每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。
镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。 镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。
Dockerfile 支持 Shell 类的行尾添加 的命令换行方式,以及行首 # 进行注释的格式。
CMD(指定启动容器时默认执行的命令)
CMD 指令的格式和 RUN 相似
格式:
-
shell 格式:CMD <命令>
-
exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
-
CMD ["参数1", "参数2"...] 提供给 ENTRYPOINT的默认参数
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
每个 Dockerfile 只能有一条 CMD 命令,如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候手动指定了运行的命令(作为 run 命令的参数),则会覆盖掉CMD 指定的命令。
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。
ADD(添加内容到镜像,更高级的复制文件)
格式: ADD <源路径> <目标路径>
ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
<源路径> 可以是 Dockerfile 所在目录的一个相对路径(文件或目录);也可以是一个URL ;还可以是一个 tar 文件。如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
原则: 尽可能的使用 COPY, 最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
COPY(复制内容到镜像)
格式:
-
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
-
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,且指的是上下文(context) 目录下的路径,因此 COPY 这类指令中的源文件的路径都是相对路径。
COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。
<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。
创建镜像
基本的格式: docker build [OPTIONS] PATH |URL | -
该命令将读取指定路径下(包括子目录)的 Dockerfile ,并将该路径下所有数据作为上下文( Context )发送给 Docker 服务端 Docker 服务端在校验 Dockerfile 格式通过后,逐条执行其中定义的指令,碰到 ADD 、COPY、 RUN 指令会生成一层新的镜像。 最终如果创建镜像成功,会返回最终镜像的 ID。要指定生成镜像的标签信息,可以通过 -t 选项
docker build 的工作原理:
Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。
虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。 当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。
实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile。
上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎(注意看上图第二行)。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。 写一个 .dockerignore 用于剔除不需要作为上下文传递给 Docker 引擎的。