24条Dockerfile及指令最佳实践

构建缓存在镜像的构建过程中,Docker会根据Dockerfile指定的顺序执行每个指令 。Dockerfile的每条指令都会将结果提交为新的镜像 。然后,下一条指令基于上一条指令的镜像进行构建 。
在执行每条指令之前,Docker都会在缓存中查找是否已经存在可重用的镜像,如果存在就使用现存的镜像,不再重复创建 。
因此,为了有效地利用缓存,尽量保持Dockerfile一致 , 并且尽量在末尾修改:
FROM ubuntuMAINTAINER author <somebody@company.com>RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"RUN apt-get updateRUN apt-get upgrade -y更改MAINTAINER指令会使Docker强制执行run指令来更新apt,而不是使用缓存 。
如不希望使用缓存,在执行 docker build 时需加上参数--no-cache=true 。
Docker中 , 构建缓存遵循的基本规则如下:

  1. 从缓存中存在的基础镜像(FROM指令指定)开始,下一条指令将和该基础镜像的所有子镜像进行匹配,检查这些子镜像被创建时使用的指令是否和被检查的指令完全一样 。如果不是,则缓存失效 。
  2. 多数情况中,使用其中一个子镜像来比较Dockerfile中的指令是足够的 。然而 , 特定的指令需要做更多的判断 。
  3. 对于ADD和COPY指令 , 镜像中对应文件的内容也会被检查,每个文件都会计算出一个校验值 , 通常是检查文件的校验和(checksum) 。在缓存的查找过程中,会将这些校验和已存在镜像中的文件校验值进行对比 。如果文件有任何改变,则缓存失效 。
  4. 除了ADD和COPY指令,缓存匹配检查并不检查临时容器中的文件 。例如,当使用RUN apt-get -y update命令更新了容器中的文件 , 并不会被缓存检查策略作为缓存匹配的依据 。
  5. 一旦缓存失效,所有后续的Dockerfile指令都将产生新的镜像,缓存不会被使用 。
使用多阶段构建多阶段构建可以大幅度减小最终的镜像大小 , 而不需要去想办法减少中间层和文件的数量 。因为镜像是在生成过程的最后阶段生成的,所以可以利用生成缓存来最小化镜像层 。
例如,如果构建包含多个层,则可以将它们从变化频率较低(以确保生成缓存可重用)到变化频率较高的顺序排序:
  • 安装构建应用程序所需的依赖工具
  • 安装或更新依赖项
  • 构建你的应用
比如构建一个Go应用程序的Dockerfile可能类似于这样:
FROM golang:1.11-alpine AS build# 安装项目需要的工具# 运行 `docker build --no-cache .` 来更新依赖RUN apk add --no-cache gitRUN go get Github.com/golang/dep/cmd/dep# 通过 Gopkg.toml 和 Gopkg.lock 获取项目的依赖# 仅在更新 Gopkg 文件时才重新构建这些层COPY Gopkg.lock Gopkg.toml /go/src/project/WORKDIR /go/src/project/# 安装依赖库RUN dep ensure -vendor-only# 拷贝整个项目进行构建# 当项目下面有文件变化的时候该层才会重新构建COPY . /go/src/project/RUN go build -o /bin/project# 将打包后的二进制文件拷贝到 scratch 镜像下面,将镜像大小降到最低FROM scratchCOPY --from=build /bin/project /bin/projectENTRYPOINT ["/bin/project"]CMD ["--help"]使用标签除非是在用Docker做实验,否则你应当通过 -t 选项来 docker build 新的镜像以便于标记构建的镜像 。一个简单可读的标签可以帮助管理每个创建的镜像 。
docker build -t="tuxknight/luckyPython/ target=_blank class=infotextkey>Python"始终通过 -t 标记来构建镜像 。
公开端口Docker的核心概念是可重复和可移植,镜像应该可以运行在任何主机上并运行尽可能多的次数 。在Dockerfile中可以映射私有和公有端口,但永远不要通过Dockerfile映射公有端口 。这样运行多个镜像的情况下会出现端口冲突的问题 。
EXPOSE 80:8080# 80映射到host的8080,不提倡这种用法 EXPOSE 80 # 80会被docker随机映射一个端口EXPOSE指令用于声明容器将监听的端口 。在EXPOSE指令中,端口号的格式为<容器端口>/<协议> 。其中,容器端口是指在容器内部应用程序监听的端口,而协议是可选的,默认为TCP 。
示例中,EXPOSE 80:8080表示容器将监听容器端口80 , 而宿主机可以使用端口8080来访问容器的80端口 。也就是,容器的80端口映射到了宿主机的8080端口 。


推荐阅读