【问题标题】:What causes a cache invalidation when building a Dockerfile?构建 Dockerfile 时导致缓存失效的原因是什么?
【发布时间】:2023-03-05 07:31:01
【问题描述】:

我一直在阅读文档Best practices for writing Dockerfiles。我遇到了一些小错误(恕我直言),进一步阅读后含义很清楚:

在 RUN 语句中单独使用 apt-get update 会导致缓存问题 以及随后的 apt-get 安装说明失败

我想知道为什么会失败。后来解释了他们所说的“失败”:

因为 apt-get update 没有运行,你的构建可能会得到 curl 和 nginx 软件包的过时版本。

但是,对于以下内容,我仍然无法理解“如果不是,则缓存无效。”的含义:

从已经在缓存中的父图像开始,下一个 将指令与从该指令派生的所有子图像进行比较 基本图像以查看其中一个是否是使用完全相同的 操作说明。如果不是,则缓存失效。

在一些关于 SO 的答案中提到了这部分,例如How does Docker know when to use the cache during a build and when not? 总的来说,缓存失效的概念对我来说很清楚,我读过以下内容:

When does Docker image cache invalidation occur? Which algorithm Docker uses for invalidate cache?

但是“如果不是”是什么意思呢?起初我确信这句话的意思是如果没有找到这样的图像。那将是矫枉过正 - 使缓存无效,这可能在以后对其他构建有用。实际上,如果我在下面尝试时没有找到图像,它不会失效:

$ docker build -t alpine:test1 - <<HITTT
> FROM apline
> RUN echo "test1"
> RUN echo "test1-2"
> HITTT
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM apline
pull access denied for apline, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
(base) nb0408:docker a.martianov$ docker build -t alpine:test1 - <<HITTT
> FROM alpine
> RUN echo "test1"
> RUN echo "test1-2"
> HITTT
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM alpine
 ---> 965ea09ff2eb
Step 2/3 : RUN echo "test1"
 ---> Running in 928453d33c7c
test1
Removing intermediate container 928453d33c7c
 ---> 0e93df31058d
Step 3/3 : RUN echo "test1-2"
 ---> Running in b068bbaf8a75
test1-2
Removing intermediate container b068bbaf8a75
 ---> daeaef910f21
Successfully built daeaef910f21
Successfully tagged alpine:test1

$ docker build -t alpine:test1-1 - <<HITTT
> FROM alpine
> RUN echo "test1"
> RUN echo "test1-3"
> HITTT
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM alpine
 ---> 965ea09ff2eb
Step 2/3 : RUN echo "test1"
 ---> Using cache
 ---> 0e93df31058d
Step 3/3 : RUN echo "test1-3"
 ---> Running in 74aa60a78ae1
test1-3
Removing intermediate container 74aa60a78ae1
 ---> 266bcc6933a8
Successfully built 266bcc6933a8
Successfully tagged alpine:test1-1

$ docker build -t alpine:test1-2 - <<HITTT
> FROM alpine
> RUN "test2"
> RUN 
(base) nb0408:docker a.martianov$ docker build -t alpine:test2 - <<HITTT
> FROM alpine
> RUN echo "test2"
> RUN echo "test1-3"
> HITTT
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM alpine
 ---> 965ea09ff2eb
Step 2/3 : RUN echo "test2"
 ---> Running in 1a058ddf901c
test2
Removing intermediate container 1a058ddf901c
 ---> cdc31ac27a45
Step 3/3 : RUN echo "test1-3"
 ---> Running in 96ddd5b0f3bf
test1-3
Removing intermediate container 96ddd5b0f3bf
 ---> 7d8b901f3939
Successfully built 7d8b901f3939
Successfully tagged alpine:test2

$ docker build -t alpine:test1-3 - <<HITTT
> FROM alpine
> RUN echo "test1"
> RUN echo "test1-3"
> HITTT
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM alpine
 ---> 965ea09ff2eb
Step 2/3 : RUN echo "test1"
 ---> Using cache
 ---> 0e93df31058d
Step 3/3 : RUN echo "test1-3"
 ---> Using cache
 ---> 266bcc6933a8
Successfully built 266bcc6933a8
Successfully tagged alpine:test1-3

缓存再次用于上次构建。 docs 中的“如果不是”是什么意思?

【问题讨论】:

  • 这个短语“如果不是”是指使用与先前运行相同的指令构建层的命令。简单地说,如果命令不同,则层缓存失效从该点开始,并沿 Dockerfile 向下流动。
  • @halfer,但是缓存并没有失效,只是没有用于这个构建,这与cache invalidation的常用用法有很大不同
  • 缓存不是每个容器,而是每个容器层。每个命令都会创建一个位于旧层之上的新层。因此,层会因更改的命令以及之后的所有内容而失效。更改命令之前缓存中的层仍在构建中使用。
  • 您可以在第二次构建中看到这一点 - Step 2/3 : RUN echo "test1" 已缓存,但 Step 3/3 : RUN echo "test1-3" 未缓存。

标签: docker dockerfile


【解决方案1】:

让我们专注于您的原始问题(关于apt-get update),以使事情变得更容易。以下示例不基于任何最佳实践。它只是说明了您试图理解的观点。

假设你有以下 Dockerfile:

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx

您使用docker build -t myimage:latest . 构建第一个图像

会发生什么:

  • ubuntu 镜像如果不存在则拉取
  • 创建并缓存一个层以运行apt-get update
  • 创建一个层并缓存运行apt install -y nginx

现在假设您将 Docker 文件修改为

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx openssl

然后您使用与以前相同的命令再次运行构建。会发生什么:

  • 本地已经有一个ubuntu镜像所以不会被拉取(除非你用--pull强制)
  • 已经使用命令apt-get update 针对现有本地图像创建了一个图层,因此它使用缓存的图像
  • 下一个命令已更改,因此创建了一个新层来安装nginx openssl。由于 apt 数据库是在上一层创建并从缓存中获取的,因此如果此后发布了新的 nginx 和/或 openssl 版本,您将看不到它们,您将安装过时的。

这是否有助于您掌握缓存层的概念?

在这个特定的例子中,最好的处理是在一个层中完成所有事情,确保你自己清理:

FROM ubuntu:18.04

RUN apt-get update  \
    && apt-get install -y nginx openssl \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

【讨论】:

  • 但是 apt-get 的 RUN 命令使每个构建中的所有进一步层都无效......至少我认为这是发生在我身上的事情
  • 我的上一个示例中只有一层(即单个 RUN 命令)。构建后,如果您不更改影响它的命令或参数(上一层,ARG 值更改...),则使用缓存
  • 好的,我明白了,我认为问题出在另一个地方,因为我总是收到消息“为 phpdockerio/7.4 下载了更新的图像,所以我认为这是真正的问题
【解决方案2】:

这句话的措辞会更好:

如果没有,则缓存未命中,并且缓存不用于此构建步骤以及 Dockerfile 的此阶段的任何后续构建步骤。

这有点冗长,因为多阶段 Dockerfile 可能无法在一个阶段找到缓存匹配,然后在另一个阶段找到匹配。不同的构建都可以使用缓存。对于特定的构建过程,缓存“无效”,缓存本身不会从 docker 主机中删除,并且它继续可用于未来的构建。

【讨论】:

  • 是的,我自己也这么认为,只是想确定一下。是否值得以某种方式进行贡献编辑(以前从未这样做过)或者可能只有我感到困惑?
  • @AlexeiMartianov 由于像您这样的其他人提出的问题,我进行了相当多的编辑。如果您不想这样做,我会非常乐意以更好的措辞发送 PR。
  • 我很乐意自己尝试这样做,docs.docker.com/opensource 说可以点击编辑页面来做到这一点。
  • BMitch,嗨!我做了这个小修改,现在已经过去了将近一个月,它仍在等待审核。正常吗? (github.com/docker/docker.github.io/pull/10011)。
  • @AlexeiMartianov 查看其他 PR 提交给该 repo 以了解正常情况。
猜你喜欢
  • 1970-01-01
  • 2012-07-05
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 2014-08-01
相关资源
最近更新 更多