通过 Dockerfile 构建的镜像所启动的容器应该尽可能短暂(生命周期短)。「短暂」意味着可以停止和销毁容器,并且创建一个新容器并部署好所需的佳实践设置和配置工作量应该是极小的 。
使用 Dockerfile 构建镜像时最好是佳实践将 Dockerfile 放置在一个新建的空目录下。然后将构建镜像所需要的佳实践文件添加到该目录中
。为了提高构建镜像的佳实践效率,你可以在目录下新建一个 .dockerignore
文件来指定要忽略的文件和目录。.dockerignore
文件的佳实践排除模式语法和 Git 的 .gitignore 文件相似 。
多阶段构建指在Dockerfile中使用多个FROM语句,佳实践每个FROM指令都可以使用不同的基础镜像,并且是一个独立的子构建阶段 。
在 Docker 17.05 以上版本中,你可以使用多阶段构建来减少所构建镜像的佳实践大小。
为了降低复杂性 、减少依赖、佳实践减小文件大小 、佳实践节约构建时间,你应该避免安装任何不必要的佳实践包。例如,不要在数据库镜像中包含一个文本编辑器。佳实践
应该保证在一个容器中只运行一个进程。佳实践将多个应用解耦到不同容器中,保证了容器的佳实践横向扩展和复用 。例如 web 应用应该包含三个容器:web应用 、数据库、缓存。
如果容器互相依赖,你可以使用 Docker 自定义网络 来把这些容器连接起来。
你需要在 Dockerfile 可读性(也包括长期的可维护性)和减少层数之间做一个平衡。
将多行参数按字母顺序排序(比如要安装多个包时)。这可以帮助你避免重复包含同一个包,更新包列表时也更容易。也便于 PRs 阅读和审查。建议在反斜杠符号\
之前添加一个空格,以增加可读性 。
下面是来自 buildpack-deps 镜像的例子:
RUN apt-get update && apt-get install -y \bzr \cvs \git \mercurial \subversion
在镜像的构建过程中,Docker 会遍历 Dockerfile 文件中的指令,然后按顺序执行。在执行每条指令之前,Docker 都会在缓存中查找是否已经存在可重用的镜像,如果有就使用现存的镜像,不再重复创建
。如果你不想在构建过程中使用缓存,你可以在docker build
命令中使用--no-cache=true
选项
。
但是,如果你想在构建的过程中使用缓存,你得明白什么时候会,什么时候不会找到匹配的镜像,遵循的基本规则如下:
- 从一个基础镜像开始(FROM 指令指定),下一条指令将和该基础镜像的所有子镜像进行匹配,检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样。如果不是,则缓存失效。
- 在大多数情况下,只需要简单地对比 Dockerfile 中的指令和子镜像 。然而,有些指令需要更多的检查和解释 。
- 对于 ADD 和 COPY 指令,镜像中对应文件的内容也会被检查,每个文件都会计算出一个校验和 。文件的最后修改时间和最后访问时间不会纳入校验 。在缓存的查找过程中,会将这些校验和和已存在镜像中的文件校验和进行对比。如果文件有任何改变,比如内容和元数据,则缓存失效。
- 除了 ADD 和 COPY 指令,缓存匹配过程不会查看临时容器中的文件来决定缓存是否匹配。例如,当执行完 RUN apt-get -y update 指令后,容器中一些文件被更新,但 Docker 不会检查这些文件。这种情况下,只有指令字符串本身被用来匹配缓存 。
一旦缓存失效,所有后续的 Dockerfile 指令都将产生新的镜像,缓存不会被使用。
下面针对 Dockerfile 中各种指令的最佳编写方式给出建议 。
尽可能使用当前官方仓库作为你构建镜像的基础。推荐使用 Alpine 镜像,因为它被严格控制并保持最小尺寸(目前小于 5 MB),但它仍然是一个完整的发行版 。
你可以给镜像添加标签来帮助组织镜像、记录许可信息
、辅助自动化构建等。每个标签一行,由 LABEL 开头加上一个或多个标签对。下面的示例展示了各种不同的可能格式。#
开头的行是注释内容。
注意:如果你的字符串中包含空格,必须将字符串放入引号中或者对空格使用转义。如果字符串内容本身就包含引号,必须对引号使用转义。
# Set one or more individual labelsLABEL com.example.version="0.0.1-beta"LABEL vendor="ACME Incorporated"LABEL com.example.release-date="2015-02-12"LABEL com.example.version.is-production=""
一个镜像可以包含多个标签,但建议将多个标签放入到一个 LABEL 指令中。
# Set multiple labels at once, using line-continuation characters to break long linesLABEL vendor=ACME\ Incorporated \com.example.is-beta= \com.example.is-production="" \com.example.version="0.0.1-beta" \com.example.release-date="2015-02-12"
关于标签可以接受的键值对,参考 Understanding object labels
。
关于查询标签信息,参考 Managing labels on objects。
为了保持 Dockerfile 文件的可读性,可理解性,以及可维护性,建议将长的或复杂的 RUN 指令用反斜杠\
分割成多行。
apt-get
RUN 指令最常见的用法是安装包用的 apt-get。因为 RUN apt-get
指令会安装包,所以有几个问题需要注意:
RUN apt-get upgrade
或 dist-upgrade
,因为许多基础镜像中的「必须」包不会在一个非特权容器中升级
。如果基础镜像中的某个包过时了,你应该联系它的维护者。如果你确定某个特定的包,比如 foo,需要升级,使用 apt-get install -y foo
就行,该指令会自动升级 foo 包 。RUN apt-get update
和apt-get install
组合成一条 RUN 声明,例如:RUN apt-get update && apt-get install -y \package-bar \package-baz \package-foo
将apt-get update
放在一条单独的 RUN 声明中会导致缓存问题以及后续的 apt-get install
失败。比如,假设你有一个 Dockerfile 文件:
FROM ubuntu:14.04RUN apt-get updateRUN apt-get install -y curl
构建镜像后,所有的层都在 Docker 的缓存中。假设你后来又修改了其中的 apt-get install
添加了一个包:
FROM ubuntu:14.04RUN apt-get updateRUN apt-get install -y curl nginx
Docker 发现修改后的 RUN apt-get update
指令和之前的完全一样。所以,apt-get update
不会执行,而是使用之前的缓存镜像