玩转dockerfile

镜像的缓存特性

Docker 会缓存已有镜像的镜像层,构建新镜像时,若是某镜像层已经存在,就直接使用,无需从新建立。docker

举例说明。
在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:shell

① 确保 testfile 已存在。ubuntu

② 重点在这里:以前已经运行过相同的 RUN 指令,此次直接使用缓存中的镜像层 35ca89798937。centos

③ 执行 COPY 指令。
其过程是启动临时容器,复制 testfile,提交新的镜像层 8d02784a78f4,删除临时容器。缓存

在 ubuntu-with-vi-dockerfile 镜像上直接添加一层就获得了新的镜像 ubuntu-with-vi-dockerfile-2。bash

若是咱们但愿在构建镜像时不使用缓存,能够在 docker build 命令中加上 --no-cache 参数。网络

Dockerfile 中每个指令都会建立一个镜像层,上层是依赖于下层的。不管何时,只要某一层发生变化,其上面全部层的缓存都会失效。学习

也就是说,若是咱们改变 Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。ui

举例说明,好比交换前面 RUN 和 COPY 的顺序:spa

虽然在逻辑上这种改动对镜像的内容没有影响,但因为分层的结构特性,Docker 必须重建受影响的镜像层。

从上面的输出能够看到生成了新的镜像层 bc87c9710f40,缓存已经失效。

除了构建时使用缓存,Docker 在下载镜像时也会使用。例如咱们下载 httpd 镜像。

docker pull 命令输出显示第一层(base 镜像)已经存在,不须要下载。

由 Dockerfile 可知 httpd 的 base 镜像为 debian,正好以前已经下载过 debian 镜像,因此有缓存可用。经过 docker history 能够进一步验证。

调试 Dockerfile

包括 Dockerfile 在内的任何脚本和程序都会出错。有错并不可怕,但必须有办法排查,因此本节讨论如何 debug Dockerfile。

先回顾一下经过 Dockerfile 构建镜像的过程:

  1. 从 base 镜像运行一个容器。
  2. 执行一条指令,对容器作修改。
  3. 执行相似 docker commit 的操做,生成一个新的镜像层。
  4. Docker 再基于刚刚提交的镜像运行一个新容器。
  5. 重复 2-4 步,直到 Dockerfile 中的全部指令执行完毕。

从这个过程能够看出,若是 Dockerfile 因为某种缘由执行到某个指令失败了,咱们也将可以获得前一个指令成功执行构建出的镜像,这对调试 Dockerfile 很是有帮助。咱们能够运行最新的这个镜像定位指令失败的缘由。

咱们来看一个调试的例子。Dockerfile 内容以下:

执行 docker build

Dockerfile 在执行第三步 RUN 指令时失败。咱们能够利用第二步建立的镜像 22d31cc52b3e 进行调试,方式是经过 docker run -it 启动镜像的一个容器。

手工执行 RUN 指令很容易定位失败的缘由是 busybox 镜像中没有 bash。虽然这是个极其简单的例子,但它很好地展现了调试 Dockerfile 的方法。

到这里相信你们对 Dockerfile 的功能和使用流程有了比较完整的印象,但尚未系统学习 Dockerfile 的各类指令和实际用法,下节会开始这个主题。

Dockerfile 经常使用指令

是时候系统学习 Dockerfile 了。

下面列出了 Dockerfile 中最经常使用的指令,完整列表和说明可参看官方文档。

FROM

指定 base 镜像。

MAINTAINER

设置镜像的做者,能够是任意字符串。

COPY

将文件从 build context 复制到镜像。

COPY 支持两种形式:

  1. COPY src dest
  2. COPY ["src", "dest"]

注意:src 只能指定 build context 中的文件或目录。

ADD

与 COPY 相似,从 build context 复制文件到镜像。不一样的是,若是 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。

ENV

设置环境变量,环境变量可被后面的指令使用。例如:

...

ENV MY_VERSION 1.3

RUN apt-get install -y mypackage=$MY_VERSION

...

EXPOSE

指定容器中的进程会监听某个端口,Docker 能够将该端口暴露出来。咱们会在容器网络部分详细讨论。

VOLUME

将文件或目录声明为 volume。咱们会在容器存储部分详细讨论。

WORKDIR

为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工做目录。

RUN

在容器中运行指定的命令。

CMD

容器启动时运行指定的命令。

Dockerfile 中能够有多个 CMD 指令,但只有最后一个生效。CMD 能够被 docker run 以后的参数替换。

ENTRYPOINT

设置容器启动时运行的命令。

Dockerfile 中能够有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 以后的参数会被当作参数传递给 ENTRYPOINT。

下面咱们来看一个较为全面的 Dockerfile:

注:Dockerfile 支持以“#”开头的注释。

构建镜像:

① 构建前确保 build context 中存在须要的文件。

② 依次执行 Dockerfile 指令,完成构建。

运行容器,验证镜像内容:

① 进入容器,当前目录即为 WORKDIR。

若是 WORKDIR 不存在,Docker 会自动为咱们建立。

② WORKDIR 中保存了咱们但愿的文件和目录:

目录 bunch:由 ADD 指令从 build context 复制的归档文件 bunch.tar.gz,已经自动解压。

文件 tmpfile1:由 RUN 指令建立。

文件 tmpfile2:由 COPY 指令从 build context 复制。

③ ENV 指令定义的环境变量已经生效。

在上面这些指令中,RUN、CMD、ENTRYPOINT 很重要且容易混淆,下节专门讨论。

RUN vs CMD vs ENTRYPOINT

RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很相似很容易混淆。本节将经过实践详细讨论它们的区别。

简单的说

  1. RUN 执行命令并建立新的镜像层RUN 常常用于安装软件包。
  2. CMD 设置容器启动后默认执行的命令及其参数但 CMD 可以被 docker run 后面跟的命令行参数替换。
  3. ENTRYPOINT 配置容器启动时运行的命令。

下面咱们详细分析。

Shell 和 Exec 格式

咱们可用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令Shell 格式和 Exec 格式两者在使用上有细微的区别。

例如

当指令执行时shell 格式底层会调用 /bin/sh -c <command> 。

例以下面的 Dockerfile 片断

执行 docker run <image> 将输出

Hello, Cloud Man

注意环境变量 name 已经被值 Cloud Man 替换。

下面来看 Exec 格式。

例如

当指令执行时会直接调用 <command>不会被 shell 解析。

例以下面的 Dockerfile 片断

运行容器将输出

Hello, $name

注意环境变量“name”没有被替换。

若是但愿使用环境变量照以下修改

运行容器将输出

Hello, Cloud Man

CMD 和 ENTRYPOINT 推荐使用 Exec 格式由于指令可读性更强更容易理解。RUN 则两种格式均可以。

RUN

RUN 指令一般用于安装应用和软件包。

RUN 在当前镜像的顶部执行命令并经过建立新的镜像层。Dockerfile 中经常包含多个 RUN 指令。

RUN 有两种格式

  1. Shell 格式RUN
  2. Exec 格式RUN ["executable", "param1", "param2"]

下面是使用 RUN 安装多个包的例子

注意apt-get update 和 apt-get install 被放在一个 RUN 指令中执行这样可以保证每次安装的是最新的包。若是 apt-get install 在单独的 RUN 中执行则会使用 apt-get update 建立的镜像层而这一层多是好久之前缓存的。

CMD

CMD 指令容许用户指定容器的默认执行的命令。

此命令会在容器启动且 docker run 没有指定其余命令时运行。

  1. 若是 docker run 指定了其余命令CMD 指定的默认命令将被忽略。
  2. 若是 Dockerfile 中有多个 CMD 指令只有最后一个 CMD 有效。

CMD 有三种格式

  1. Exec 格式CMD ["executable","param1","param2"]
    这是 CMD 的推荐格式。
  2. CMD ["param1","param2"] 为 ENTRYPOINT 提供额外的参数此时 ENTRYPOINT 必须使用 Exec 格式。
  3. Shell 格式CMD command param1 param2

Exec 和 Shell 格式前面已经介绍过了。
第二种格式 CMD ["param1","param2"] 要与 Exec 格式 的 ENTRYPOINT 指令配合使用其用途是为 ENTRYPOINT 设置默认的参数。咱们将在后面讨论 ENTRYPOINT 时举例说明。

下面看看 CMD 是如何工做的。Dockerfile 片断以下

CMD echo "Hello world"

运行容器 docker run -it [image] 将输出

Hello world

但当后面加上一个命令好比 docker run -it [image] /bin/bashCMD 会被忽略掉命令 bash 将被执行

root@10a32dc7d3d3:/#

ENTRYPOINT

ENTRYPOINT 指令可以让容器以应用程序或者服务的形式运行。

ENTRYPOINT 看上去与 CMD 很像它们均可以指定要执行的命令及其参数。不一样的地方在于 ENTRYPOINT 不会被忽略必定会被执行即便运行 docker run 时指定了其余命令。

ENTRYPOINT 有两种格式

  1. Exec 格式ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。
  2. Shell 格式ENTRYPOINT command param1 param2

在为 ENTRYPOINT 选择格式时必须当心由于这两种格式的效果差异很大。

Exec 格式

ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数同时可经过 CMD 提供额外的参数。

ENTRYPOINT 中的参数始终会被使用而 CMD 的额外参数能够在容器启动时动态替换掉。

好比下面的 Dockerfile 片断

当容器经过 docker run -it [image] 启动时输出为

Hello world

而若是经过 docker run -it [image] CloudMan 启动则输出为

Hello CloudMan

Shell 格式

ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。

最佳实践

  1. 使用 RUN 指令安装应用和软件包构建镜像。
  2. 若是 Docker 镜像的用途是运行应用程序或服务好比运行一个 MySQL应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数同时可利用 docker run 命令行替换默认参数。
  3. 若是想为容器设置默认的启动命令可以使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。

到这里咱们已经具有编写 Dockerfile 的能力了。若是你们还以为没把握推荐一个快速掌握 Dockerfile 的方法去 dockerhub.com 上参考那些官方镜像的 Dockerfile。

 

调试Dockerfile

dockerfile编写的过程当中,不可避免会遇到运行构建新镜像错误的问题,那么咱们应该怎样调试dockerfile呢。其实,当咱们遇到某个指令失败时,咱们也可以获得前一个指令构建的镜像。所以,咱们能够进入到前一个临时镜像,调试下一个指令。

好比运行Dockerfile后,报错信息以下,在step3,即 RUN cp tmpfile tmpdir/ 时出现了错误。

[root@localhost debug-dockerfile]# docker build -t debug-dockerfile .
Sending build context to Docker daemon   2.56kB
Step 1/3 : FROM centos:7.4.1708
 ---> 295a0b2bd8ea Step 2/3 : RUN touch tmpfile  ---> Running in 7530981ccd45 Removing intermediate container 7530981ccd45  ---> 8408a48380c2 Step 3/3 : RUN cp tmpfile tmpdir/  ---> Running in a50d0a45ce94 cp: cannot create regular file 'tmpdir/': Not a directory The command '/bin/sh -c cp tmpfile tmpdir/' returned a non-zero code: 1 

这时,咱们能够进入到前面一个指令中获取到的临时镜像8408a48380c2,调试下一个指令。

[root@localhost debug-dockerfile]# docker run -it 8408a48380c2

经过ll命令,咱们能够看到上一个命令建立的文件tmpfile

注意事项

1. COPY/ADD文件夹时默认复制文件来中的文件

ADD go /usr/local/

您的本地目录内容复制到docker镜像go/usr/local/目录中。

要复制go正在/usr/local/使用目录

ADD go /usr/local/go

要么

COPY go /usr/local/go
相关文章
相关标签/搜索