Dockerfile
Dockerfile 是用来描述文件的构成的文本文档,其中包含了用户可以在使用行调用以组合 Image 的所有命令,用户还可以使用Docker build 实现连续执行多个命令指今行的自动构建。
通过编写 Dockerfile 生成镜像,可以为开发、测试团队提供基本一致的环境,从而提升开发、测试团队的效率,不用再为环境不统一而发愁,同时运维也能更加方便地管理我们的镜像。
Dockerfile 的语法非常简单,常用的只有 11 个:
命令 | 说明 |
FROM | 基于哪个镜像来实现 |
MAINTAINER | 镜像的作者 |
ENV | 声明环境变量 |
RUN | 执行命令 |
ADD | 添加宿主机文件到容器里,\n有需要解压的文件会自动解压 |
COPY | 添加宿主机文件到容器里 |
WORKDIR | 工作目录 |
EXPOSE | 容器内应用可使用端口 |
CMD | 容器启动后所执行的程序。\n如果执行 docker run 后面跟启动命令会被覆盖掉 |
ENTRYPOINT | 与 CMD 功能相同,但是 docker run 不会覆盖。\n如果需要覆盖可增加参数 --entrypoint 来覆盖 |
VOLUME | 将宿主机容器的目录挂载到容器里 |
1. docker build
Docker CLI 为我们提供了 docker build 命令,通过这个命令,我们能够以 Dockerfile 中约定的流程构建对应的镜像。
Docker 根据 Dockerfile 建立镜像的每一步操作都会生成一层镜像。在建立每一层镜像的时候, Docker 都会先查找本地的镜像库中是否含有需要构建的镜像,如果有,就会直接采用这个镜像,可以从构建过程的输出中看到 ---> Using cache
的字样,表示构建此镜像层时采用了本地己有的镜像。逐个对操作生成单独的镜像层,实现了拆分镜像和对镜像层高效利用的效果。
需要注意的是,在 docker build
命令接收的参数中,提供给 docker build
命令的 -f
选项应该 Dockerfile 路径名。如果 Dockerfile 文件就叫 Dockerfile
那么文件名可省略,如果 Dockerfile 文件就在当前目录下,那么 -f
选项可省略。
如果你的 Dockerfile 在别处,而且还不叫 Dockerfile 那么你的 docker build 命令就应该形如如下形式:
在上面的例子中,你的 Dockerfile 在 /xxx/yyy
下,名为 zzz
。
docker build 命令后 . 号的意思
在 docker build 命令中,最后的 .
号不是用来指定 Dockerfile 文件所在的位置的!-f
参数才是用来指定 Dockerfile 的路径的!
那么 .
号究竟是用来做什么的呢?
Docker 是 CS 结构,分为客户端和服务端(和 MySQL 一样)。我们日常对 Docker 的使用和操作,实际上一直都是在和 docker client 打交道,而 docker client 将我们输入的命令 “交给” docker server,等待 docker server 的执行,接收 docker server 的返回,然后再展现给我们看。
镜像的创建工作是由 Docker Server 创建的,而不是 Docker CLI 。那么如果在 Dockerfile 中使用了一些 COPY 等指令来操作文件,如何让 Docker Server 获取到这些文件呢?毕竟 Docker CLI 中的 “当前目录” 下有 xxx
文件,你不能保证 Docker Server 的 “当前目录” 下也有 xxx
文件啊。
这里就有了一个 “镜像构建上下文” 的概念。简单来说,它就是一个由用户指定的用于构建镜像的文件夹,这个文件夹中理应包含所有和构建有关的所有的内容,Docker CLI 再执行 docker build 命令时,会将这个文件夹都打包上传给 Docker Server ,这样 Docker Server 在构建镜像的过程中自然就能找到 Dockerfile 中所说的 xxx
文件了。
docker build
命令中的 .
号所在的 “那个位置” 所要放的就是一个你需要 “传” 给 Docker Server 的一个文件夹。.
表示就是把当前文件夹传给 Docker Server 用于镜像的构建。
2. 指令说明
2.1 基础指令
基础指令是表述 Dockerfile 整体性质的指令,能够为我们选择基础镜像,也能提供镜像的基本信息。
Docker 的镜像都是分层构建的,一个镜像实际上是多层 “累加、叠加” 的而成的。最低层被称为 bootfs 。当然,我们不必每次构建镜像都从最底层 bootfs 开始,我们可以直接在某个现成的镜像基础之上开始 “叠加”。
FROM 指令就是用来指定我们所要构建的镜像是基于哪个镜像建立的。它有两种形式:
通过 FROM 指定的镜像名称必须是一个已经存在的镜像,这个镜像称之为基础镜像。
注意,它必须是第一条非注释指令 。
以下是 mysql 的 Dockerfile 中的 FROM 片段:
MAINTAINER 是非必要指令,它逻辑上相当于是注释,是给人看的,而不是给 Docker Server 构建镜像用的。
MAINTAINER 指令的用处是提供镜像的作者信息,其实用格式为:
由于 MAINTAINER 是非必要指令,所有好多 Dockerfile 中根本就没它。
2.2 控制指令
控制指令是 Dockerfile 的核心部分,我们通过控制指令来描述整个镜像的构建过程。
用于指定构建镜像时运行的命令。由于在构建过程中进行各项操作是不可或缺的过程,可以说 RUN 指令是 Dockerfile 中最常用的指令,甚至没有之一。
RUN 指令有两种使用格式:
这两种写法虽有大同,更有小异,它俩适用于不同的场景
如果以 RUN command param1 param2 ...
这种形式来设置 RUN 指令,在构建镜像时,实际是以 Shell 程序来执行的。例如,RUN mkdir data
,实际执行的会是 /bin/sh -c mkdir data
。这种写法在执行命令时需要在 Shell 程序中中转一下,但是其有一个非常大的优势:支持书写换行 。
有时指令会比较冗长,如果写在一行中极不方便阅读和排查错误,在 Shell 程序中我们可以使用续行符 \
对命令进行拆行,例如:
使用续航符连接的方式进行书写可以让命令更清晰。
RUN ["executable", "param1", "param2", ...]
这种方式执行的命令可以有效规避某些基础镜像中没有 Shell 程序,或者用于需要临时切换 Shell 程序的时候:
上述 RUN 指令就是用 bash 替代了 sh 来执行 echo hello
。
总体上来说,RUN 命令还是更推荐使用 shell 写法(而非 exec 写法)。
多条 RUN 指令可以合并为一条,例如:
这样在构建的时候会减少产生中间层镜像。
以下是 mysql 的 Dockerfile 中的 RUN 片段:
WORKDIR 指令用于切换构建过程中的工作目录,如果我们在使用 RUN 指令、ADD 指令、COPY 指令,以及容器运行时才会执行的 CMD 指令、ENTRYPOINT 指令时,使用了相对目录,那么相对目录就是相对于当前的工作目录。
逻辑想,WORKDIR 相当于在执行 cd
命令。
WORKDIR 指令给出工作目录的方式可以是绝对目录,也可以是相对目录。你也可以在 WORKDIR 指令中使用环境变量。
2.3 引入指令
在很多场合下,我们希望将文件加入到即将构建成的镜像中,引入指令就能够帮助我们实现这个目的。
在构建容器的过程中,可以需要将一些软件源码、配置文件、执行脚本等导入到镜像的构建过程,这时可以使用 COPY 指令将文件从外部传递到镜像内部。
COPY 指令有以下 2 种使用形式:
以上两种形式并无太大差别,只是 COPY [ ... ]
形式可以解决文件路径中带空格的情况。
虽然,源路径可以有多个,但是通常常见的情况下,COPY 指令也就只有一个源路径、一个目的路径。要拷贝多个文件的话,可以写几个 COPY 指令,这样依赖便于阅读,另一方面也便于构建缓存。
如果,源路径是一个目录,则该目录下的所有内容(而不是整个目录整体)都将被加入到容器,但是该目录本身不会加入到容器中。这和 linux 的 copy 命令效果有点不同。
源路径中可以使用通配符,例如:
这里通配符的规则采用的是 Go 语言的文件名匹配规则,例如:*
代表任意多个字符;?
代表任意一个字符;[]
用来配置字符范围等。
源路径应该是一个相对路径,这样才能保证移动 Dockerfile 和其目录后不需要再进行修改。而相对路径是相对于镜像的构建路径而言的,也就是我们在 docker build 命令中所传入的路径,也就是 docker build 命令最后的那个 .
(或其他路径) 。按照一般惯例,这个路径就是 Dockerfile 所在的路径。另外,源路径还不能脱离 docker build 命令所给出的路径,也就是说,不能使用 ../xxx
或类似的路径来访问上级路径。
目标路径是镜像中的路径,可以是绝对路径,也可以是相对于 WORKDIR 的相对路径。另外,如果目标路径不存在,则会被自动创建。
以下是 nginx Dockerfile 中的 COPY 指令片段:
ADD 指令和 COPY 指令功能一样。它比 COPY 指令更高级的地方在于,它可以下载网络内容,还能够自动完成压缩文件的解压。
不过,一般情况下如果真需要从网络地址下载、解压文件,我们还是习惯性在 RUN 中使用专门的 wget、curl、tar 命令,而不是使用 ADD 。所以 COPY 指令比 ADD 指令要更常见一些。
2.4 执行指令
执行命令能够指定通过镜像创建的容器,在启动、运行时默认执行的命令。
简单来说就是, Docker 容器就是一个虚拟机,只不过这个虚拟机有且仅干一件事情,具体是什么事情,则由创建容器的镜像的 Dockerfile 中的 CMD 或 ENTRYPOINT 指定。
CMD 有 shell 模式和 exec 模式两种:
推荐使用 exec 模式。因为在这种模式下 docker 执行的是程序本身,所以容器运行绑定的也是程序本身,而非 shell 程序本质上,这里说的是谁是 PID=1 的进程。
CMD 命令指定的是 “默认” 情况下容器运行时的执行程序。如果我们在创建容器时,重新指定了引用程序的启动命令,那么容器中的 CMD 会被覆盖,不会生效。
例如,如果你执行 docker run -it nginx /bin/bash
,那么,容器启动后执行的是 /bin/bash
,而我们也因为 -it 指令而绑定到了 /bin/bash 程序上),可以在容器中进行操作,而 nginx 程序则没有运行。此时,原本的 nginx 程序则没有运行,容器的停止也以 /bin/bash 程序的结束为标准。
ENTRYPOINT 也有 shell 模式和 exec 模式两种:
推荐使用 exec 模式。因为在这种模式下 docker 执行的是程序本身,所以容器运行绑定的也是程序本身,而非 shell 程序本质上,这里说的是谁是 PID=1 的进程。
与 CMD 不同,ENTRYPOINT 不会被 docker run 中指定的命令覆盖,如果想覆盖 ENTRYPOINT,则需要在 docker run 中指定 --entrypoint 选项。
其实 CMD 就是 command 的意思。所以很显然,CMD 和 command 就是表达同一个作用,因此,当两者同时出现时,有一个会覆盖掉另一个是很显然的情况。
我们这里使用 linux 的 top 命令来验证效果。top 命令是 Linux 下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于 Windows 的任务管理器。
编写一个 Dockerfile:
用上述 Dockerfile 来创建镜像:
上述命令创建了名为 cmd_test 的镜像,你可以通过 docker images
命令在你的本地看到它。
根据 Dockerfile 的内容( CMD ["top", "-c", "-b"]
),我们知道,使用 cmd_test 镜像创建的容器 “默认情况” 下,在启动后会执行 cmd -c -b
命令。你可以通过看到这个效果:
借助 top 命令的逻辑,它 “碰巧” 能列举出它自己。
如果,你在执行 docker run 时,额外提供了 command ,那么,你指定的 command 会使 Dockerfile 中的 CMD 失效。你可以使用下述命令,看看它们的执行效果:
上述两个命令,第一个是用 echo 'hello world'
“覆盖” 掉了 Dockerfile 中 CMD 指定 top -c -b
命令;第二个是使用 top -c
“覆盖” 掉了 top -c -b
。
ENTRYPOINT 可以起到 CMD 命令同样的效果,但是我们不会简单地拿它来当作 CMD 的替代品,而是使用它更高级的特性。
例如:
使用上述 Dockerfile 创建镜像,再创建并启动容器:
可以看到效果和 CMD ["top", "-c", "-b"]
是一样的。
ENTRYPOINT 会影响到 Dockerfile 中的 CMD 以及 docker run 命令的 command (本来它俩也就是一回事)。
当 Dockerfile 中同时出现 ENTRYPOINT 和 CMD 时,CMD 的内容会拼接到 ENTRYPOINT 的后面,组成最终容器应该运行的命令。
现在拆分 ENTRYPOINT ,让 Dockerfile 中同时出现 ENTRYPOINT 和 CMD :
使用上述 Dockerfile 创建镜像,再创建并启动容器,可以看到和之前的 ENTRYPOINT ["top", "-c", "-b"]
效果是一样的。
一样,如果你在 docker run 命令中提供 command 部分,例如:
这次 command 中的 -c
就 “覆盖” 了 CMD 中的 -c -b
,和 ENTRYPOINT top
拼到了一起,即,容器执行的是 top -c
命令。
很多镜像的 Dockerfile 都利用到了 ENTRYPOINT 的这一点。通常,使用场景有 2 个:
当然,也有 2 者相结合的情况。
2.5 配置指令
指定运行该镜像的容器使用的端口,可以是多个。
使用这个指令的目的是告诉应用程序容器内应用程序会使用的端口,在运行时还需要使用 -p 参数指定映射端口。这是 docker 处于安全的目的,不会自动打开端口。
用于设置环境变量。有两种写法:
它类似于编程领域中的变量声明。设置环境变量的目的,自然是为了 “后面” 来使用这些环境变量(的值)。
以下是 Nginx 的 Dockerfile 中的 ENV 片段:
3. Dockerfile 示例
Dockerfile 文件格式如下:
Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令、容器启动执行指令 。