构建 Dockerfile 时是什么导致缓存失效?

Ale*_*nov 5 docker dockerfile

我一直在阅读文档编写 Dockerfiles 的最佳实践。我遇到了小错误(恕我直言),在进一步阅读后,其含义很清楚:

在 RUN 语句中单独使用 apt-get update 会导致缓存问题并且随后的 apt-get install 指令失败

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

由于 apt-get 更新未运行,因此您的构建可能会获得过时版本的 curl 和 nginx 包。

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

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

这部分在 SO 的一些答案中提到,例如Docker 如何知道在构建期间何时使用缓存,何时不使用?总的来说,缓存失效的概念对我来说很清楚,我已经阅读了以下内容:

Docker镜像缓存失效什么时候发生? Docker 使用哪种算法来使缓存失效?

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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

Zei*_*tor 5

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

假设您有以下 Dockerfile:

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y nginx
Run Code Online (Sandbox Code Playgroud)

您使用构建第一个图像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
Run Code Online (Sandbox Code Playgroud)

然后使用与之前相同的命令再次运行构建。发生的情况是:

  • 本地已经有一个 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/*
Run Code Online (Sandbox Code Playgroud)


BMi*_*tch 4

这句话的措辞最好这样说:

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

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