docker build 的 cache 机制

本文深入探讨DockerBuild命令的缓存机制,解析如何利用缓存提高镜像构建效率,节省存储空间。通过实例说明Docker如何识别并复用已有镜像层,避免重复构建。

docker build 简介

众所周知,一个 Dockerfile 唯一的定义了一个 Docker 镜像。如此依赖,Docker 必须提供一种方式,将 Dockerfile 转换为 Docker 镜像,采用的方式就是 docker build 命令。以如下的 Dockerfile 为例:

FROM ubuntu:14.04  
RUN apt-get update  
ADD run.sh /  
VOLUME /data  
CMD ["./run.sh"]  

一般此 Dockerfile 的当前目录下,必须包含文件 run.sh。通过执行以下命令

docker build -t="my_new_image" .  

即可将当前目录下的 Dockerfile 构建成一个名为 my_new_image 的镜像,镜像的默认 tag 为 latest。对于以上的docker build请求,Docker Daemon 新创建了 4 层镜像,除了 FROM 命令,其余的 RUN、ADD、VOLUME 以及 CMD 命令都会创建一层新的镜像。

镜像 cache 机制介绍

Dockerfile 可以通过docker build命令构建为一个新的镜像,Dockerfile 中每一条命令都会构建出一个新的镜像层。既然如此,构建成功后宿主机上的镜像层是否会不断增多,导致磁盘空间资源逐渐缩小?另外,一个 Dockerfile 如果构建多次,对于 Dockerfile 中的某一指定命令,是否会出现产生多个对应镜像层的情况呢?

镜像层的增多自然是毋庸置疑,然而并非每一次构建的每一条 Dockerfile 命令都会产生一个全新的镜像层。谈及原因,那我们必须谈谈 docker build 的 cache 机制。

docker build 的 cache 机制: Docker Daemnon 通过 Dockerfile 构建镜像时,当发现即将新构建出的镜像与已有的某镜像重复时,可以选择放弃构建新的镜像,而是选用已有的镜像作为构建结果,也就是采取本地已经 cache 的镜像作为结果。

反复阅读以上解释,细心的朋友肯定会有两点疑惑:

1. “即将构建出的镜像”属于仍未构建完成的镜像,通过何种方式来标识此镜像?

2. 涉及到镜像比较,重复时选择放弃构建,那镜像比较时重复的标准是什么?

cache 机制实现原理

Docker 镜像,由镜像层文件系统内容镜像 json文件组成,而这两者都含有一个相同的镜像 ID 。还记得我们之前谈及的父镜像子镜像的概念吗?此处也会大量运用镜像间的父子关系。

还是以上文中的 Dockerfile 为例,我们结合下图,着重分析命令 FROM ubuntu:14.04 和 RUN apt-get update

image

FROM ubuntu:14.04: FROM 命令是 Dockerfile 中唯一不可缺少的命令,它为最终构建出的镜像设定了一个基础镜像(base image)。docker build命令解析 Dockerfile 的 FROM 命令时,可以立即获悉在哪一个镜像基础上完成下一条命令RUN apt-get update的镜像构建。此时,Docker Daemon 获取 ubuntu:14.04 镜像的镜像 ID,并提取该镜像 json 文件中的内容,以备下一条命令构建时使用。

RUN apt-get update:RUN 命令是在上一层镜像(即 ubuntu:14.04 镜像)之上运行 apt-get update,所有对文件系统内容有更新的文件,都会保留于新构建的镜像层中,同时更新上一层镜像的 json 文件,更新镜像 json 文件的 Cmd 属性为"/bin/sh -c apt-get update"。注意:镜像 json 文件的 Cmd 属性与镜像 json 文件中 config 属性的 Cmd 属性,详见下图 RUN 命令所对应镜像(镜像 ID 为:0aaab7ef57ee)中两个 Cmd 的区别:

image

完成一条非 FROM 命令的构建,即产生一个新的镜像,新的镜像为其上一条命令产生镜像的子镜像。基于此以及以上的知识,我们可以提出这样的一个猜想:

“是否可以在构建 Dockerfile 某一命令前,就预知即将构建出新一层镜像的形态?”

围绕此问题,我们继续分析。未构建命令 RUN apt-get update 前,我们可以肯定的事实有以下几点:

  • 镜像关系:对于命令RUN apt-get update的构建,一定将会产生一个新镜像,新镜像的父镜像 ID 为 ubuntu:14.04 的镜像 ID,即 8251da35e7a7。

  • 镜像 json 文件更新:运行命令 apt-get update 后产生新镜像,新镜像 json 文件仅仅更新 ubuntu:14.04 镜像 json 文件的 Cmd 属性,其它如 config 属性均不会进行修改。

  • 镜像层文件系统内容更新:运行 apt-get update 后,对于容器可读写层的内容更新,全部将被打包进新镜像的镜像层文件系统内容。

基于这 3 个事实,我们再提出一个假设:如果在构建命令 RUN apt-get update 前,Docker Daemon 已经存在一个镜像满足以下两点:

  • 此镜像的父镜像为 ubuntu:14.04
  • 此镜像的 json 文件仅仅将 ubuntu:14.04 镜像 json 文件的 Cmd 属性更新为 apt-get update

那么是否可以认为:即将新构建的镜像与此镜像完全一致,不需要另行构建,只需复用此镜像即可?

如果你认可以上假设,那么 cache 机制的核心就接近浮出水面了:遍历本地所有镜像,发现镜像与即将构建出的镜像一致时,将找到的镜像作为 cache 镜像,复用 cache 镜像作为构建结果。

cache 机制注意事项

可以说,cache 机制很大程度上做到了镜像的复用,降低存储空间的同时,还大大缩短了构建时间。然而,不得不说的是,想要用好 cache 机制,那就必须了解利用 cache 机制时的一些注意事项。

1. ADD 命令与 COPY 命令:Dockerfile 没有发生任何改变,但是命令ADD run.sh / 中 Dockerfile 当前目录下的 run.sh 却发生了变化,从而将直接导致镜像层文件系统内容的更新,原则上不应该再使用 cache。那么,判断 ADD 命令或者 COPY 命令后紧接的文件是否发生变化,则成为是否延用 cache 的重要依据。Docker 采取的策略是:获取 Dockerfile 下内容(包括文件的部分 inode 信息),计算出一个唯一的 hash 值,若 hash 值未发生变化,则可以认为文件内容没有发生变化,可以使用 cache 机制;反之亦然。

2. RUN 命令存在外部依赖:一旦 RUN 命令存在外部依赖,如RUN apt-get update,那么随着时间的推移,基于同一个基础镜像,一年的 apt-get update 和一年后的 apt-get update, 由于软件源软件的更新,从而导致产生的镜像理论上应该不同。如果继续使用 cache 机制,将存在不满足用户需求的情况。Docker 一开始的设计既考虑了外部依赖的问题,用户可以使用参数 --no-cache 确保获取最新的外部依赖,命令为docker build --no-cache -t="my_new_image" .

3. 树状的镜像关系决定了,一次新镜像的成功构建将导致后续的 cache 机制全部失效:这一点很好理解,一旦产生一个新的镜像,同时意味着产生一个新的镜像 ID,而当前宿主机环境中肯定不会存在一个镜像,此镜像 ID 的父镜像 ID 是新产生镜像的ID。这也是为什么,书写 Dockerfile 时,应该将更多静态的安装、配置命令尽可能地放在 Dockerfile 的较前位置。

总结

docker build 的 cache 机制实现了镜像的复用,不仅节省了镜像的存储空间,也为镜像构建节省了大量的时间。 同时,如何命中 cache 镜像,也是衡量 Dockerfile 书写是否合理的重要标准之一。

参考:「Allen 谈Docker 系列」docker build 的cache 机制 - 社区 - DaoCloud

<think>我们正在回答用户关于“docker build常用命令”的问题。根据之前的对话历史,用户已经提供了多个引用内容,这些引用中包含了一些docker build命令的示例和使用方法。我们需要从这些引用中提取有用的信息,并结合docker build的常见用法来回答用户。 引用内容分析: 引用[1]:提到了docker-compose build命令,与docker build不同,但可以类比参考。 引用[2]:提供了三个docker build的示例,分别展示了使用当前目录Dockerfile、指定目录Dockerfile和URL创建镜像。 引用[3]:展示了docker run命令中使用环境变量更改配置的例子,虽然与build不直接相关,但说明了环境变量的使用,而build中可以通过--build-arg传递构建参数。 引用[4]:提供了两个docker build示例,第一个是同时设置多个标签(-t),第二个是指定Dockerfile路径(-f)。 引用[5]:解释了-f参数的含义,用于指定Dockerfile的名称。 根据上述引用,我们可以总结出docker build的常用命令包括: 1. 基本构建命令:docker build [选项] 上下文路径 2. 指定镜像名称和标签:-t 或 --tag 3. 指定Dockerfile文件:-f 或 --file 4. 传递构建参数:--build-arg 另外,根据docker的常规使用,我们还可以补充一些其他常用选项,例如: 5. 不使用缓存:--no-cache 6. 删除中间层:--rm(默认true) 7. 目标构建阶段:--target 现在,我们将为用户整理docker build的常用命令,并给出示例。 注意:用户要求使用中文回答,并且按照指定的格式(行内公式用$...$,独立公式用$$...$$)。由于docker build命令不涉及数学公式,因此我们主要关注命令的展示。 此外,用户还要求生成相关问题,我们将在回答末尾提出。 </think>### Docker Build 常用命令详解 以下是 `docker build` 的核心用法总结(基于引用内容): #### 1. **基础构建命令** ```bash # 使用当前目录的 Dockerfile 构建镜像 (-t 指定镜像名和标签) docker build -t myimage:v1 . # 使用指定目录的 Dockerfile 构建镜像 (-f 指定文件路径) docker build -f /path/to/Dockerfile -t myimage:v1 . ``` 引用示例:`# 使用当前目录的 Dockerfile 创建镜像 docker build -t demo:v1 .` [^2] #### 2. **多标签构建** ```bash # 为同一镜像设置多个标签(常用于版本迭代) docker build -t myapp:latest -t myapp:v2.1 . ``` 引用示例:`docker build -t champagne/myProject:latest -t champagne/myProject:v2.1 .` [^4] #### 3. **远程仓库构建** ```bash # 直接从 Git 仓库构建(自动拉取 Dockerfile) docker build github.com/user/project ``` 引用示例:`docker build github.com/creack/docker-firefox` [^2] #### 4. **构建参数传递** ```bash # 通过 --build-arg 传递参数到 Dockerfile docker build --build-arg APP_PORT=8080 -t webapp . ``` Dockerfile 中需声明:`ARG APP_PORT`,运行时可通过 `-e MYPORT=10080` 覆盖 [^3] #### 5. **调试模式构建** ```bash # 指定调试专用 Dockerfile docker build -f dockerfiles/Dockerfile.debug -t myapp_debug . ``` 引用示例:`docker build -f dockerfiles/Dockerfile.debug -t myapp_debug .` [^4] #### 6. **压缩包构建** ```bash # 从压缩包中的 Dockerfile 构建 docker build -t myproject:latest - < context.tar.gz ``` 引用说明:`context.tar.gz 包中的根路径 Dockerfile` [^4] --- ### 关键参数解析 | 参数 | 作用 | 示例 | |------------|-------------------------------|--------------------------| | `-t` / `--tag` | 设置镜像名称和标签 | `-t app:v1` | | `-f` / `--file` | 指定 Dockerfile 路径 | `-f ./debug.Dockerfile` [^5] | | `--build-arg` | 传递构建参数 | `--build-arg NODE_ENV=prod` | | `--no-cache` | 禁用构建缓存 | `docker build --no-cache` | --- ### 典型场景示例 **场景:构建带环境变量的 Web 服务** ```bash # Dockerfile FROM nginx ARG PORT=80 ENV LISTEN_PORT=$PORT EXPOSE $PORT # 构建命令 (设置端口为 10080) docker build --build-arg PORT=10080 -t web:v1 . ``` 运行验证:`docker exec web cat /etc/test.conf` 输出 `PORT=10080` [^3] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值