也许我的Google Foo不够强大,但我找不到关于缓存中Docker映像何时失效的明确列表。具体来说,我至少对以下情况感兴趣:
- 由于 mtime 更改与校验和更改而失效。哪个适用于何时?它可以处理不同的源路径(例如,不同目录中存储库的克隆)吗?
- 由于更新了基本映像而失效。在什么时候,例如 Debian 的(安全)更新会向我冒泡?
- 是否有任何显式 API 可供持续集成工具用来告诉 Docker 哪些缓存的映像可以重用,哪些不能(例如,由于
wget foo.com/latest.gz
)?
1.8 开始,Docker 不再使用 mtime
使缓存失效(这在此拉取请求 #12031 中发生了变化)。
构建映像时;
- 对于本地内容(
ADD myfiles /somewhere
/COPY myfiles /somewhere
),docker 使用校验和更改使缓存失效 - 始终下载远程内容 (
ADD http://example.com/foobar /somewhere
),但构建缓存会根据校验和更改而失效 -
RUN
指令(如wget foo.com/latest.gz
)永远不会使缓存失效,除非指令被更改;即缓存基于指令中的文本。如果需要可重现的版本,请确保这些 URL 指向特定版本 (wget http://example.com/package-major.minor.patch.gz
)
Docker 1.9 引入了对构建时参数的支持,使您能够传递可在 Dockerfile 中使用的变量,这样您就不必编辑 Dockerfile 来破坏缓存或安装不同版本的包。
例如
FROM foobar
ARG MAJOR=1
ARG MINOR=0
ARG PATCH=0
ADD http://example.com/package-$MAJOR.$MINOR.$PATCH.gz /
默认情况下将添加 http://example.com/package-1.0.0.gz,但是,传递"主要"、"次要"或"补丁"构建时参数可以覆盖要下载的版本,并使缓存无效;
docker build --build-arg MINOR=2 . Sat Jan 16 13:22:40 2016
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu
---> 1c9b046c2850
Step 2 : ARG MAJOR=1
---> Using cache
---> a149d88772ba
Step 3 : ARG MINOR=0
---> Using cache
---> e3dae0189ffd
Step 4 : ARG PATCH=0
---> Using cache
---> 678d7ae33054
Step 5 : ADD http://example.com/package-$MAJOR.$MINOR.$PATCH.gz /
Get http://example.com/package-1.2.0.gz: dial tcp 127.0.0.1:80: getsockopt: connection refused
有关构建缓存的更多信息,请阅读文档中的构建缓存部分。
在什么时候,例如 Debian 的(安全)更新会向我冒泡?
Docker 不会自动下载更新的映像,也不会更新基于这些映像的映像。但是,如果docker pull yourbaseimage
,并且下载了较新的映像,则基于该映像的生成缓存将失效,因此下一个生成将不使用缓存。
对于 Docker 中心上的自动生成,可以确保在更新基础映像时自动重新生成映像,请参阅有关自动生成的文档
TL;DR
关于@thaJeztah的答案,我已经尝试过了。而--no-cache
不仅强制当前阶段重建,还强制任何从属阶段完全重建。这不是我们想要的。
但是有一种方法可以仅在特定阶段强制失效:使用命名ARG
并且不要在Dockerfile
中使用它。并将其作为--build-arg
传递给docker build
.
这将创建一个"不同的层",因此使后面的任何内容无效。
理由
这是我的最终 dockerfile 的摘录,分为 4 个阶段:
-
repo-sources-base
=> 下载我的库的基础 - 不要重建 -
repo-sources
=> 我的库 - 重建 -
base
=> Ubuntu 与我的 apache、php 等 - 不要重建 -
production
=> 我的项目和库的基础,用于生产服务器 - 重建
看这里:
#===========================================================================#
# Stage `repo-sources-base` #
# ------------------------- #
# This image installs git so we don't have to install git it each time we #
# rebuild the repo-sources. #
#===========================================================================#
FROM ubuntu:20.04 AS repo-sources-base
# Install git.
RUN
apt-get update &&
apt-get install -y git &&
:
# Scan the gitlab host key.
RUN
touch /root/.ssh/known_hosts &&
ssh-keyscan gitlab.com >> /root/.ssh/known_hosts &&
:
#===========================================================================#
# Stage `repo-sources` #
# -------------------- #
# This image contains the SSH private keys to make the clone of the #
# source code from gitlab. This key is passed via ARG to avoid hardcoding #
# it here inside. #
#===========================================================================#
FROM repo-sources-base AS repo-sources
# NOTE THIS ARG!!!!!!!
# Not used anywhere... just declared.
# But invalidates the docker build cache on purpose!
# See the answer text for explanation.
ARG INVALIDATE_CACHE_TIMESTAMP="0000-00-00T00:00:00.000000Z"
# Inspired here https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/
# Add credentials.
ARG SSH_PRIVATE_KEY
RUN
mkdir /root/.ssh &&
echo "${SSH_PRIVATE_KEY}" > /root/.ssh/id_rsa &&
chmod 600 /root/.ssh/id_rsa &&
:
# Clone the needed repos.
RUN mkdir -p whatever-path/repos
WORKDIR /whatever-path/repos
RUN git clone --quiet git@gitlab.com:my-nice-account/my-nice-project-1.git
RUN git clone --quiet git@gitlab.com:my-nice-account/my-nice-project-2.git maybe_deploy_dir_2
RUN git clone --quiet git@gitlab.com:my-nice-account/my-nice-project-3.git
RUN git clone --quiet git@gitlab.com:my-nice-account/my-nice-project-4.git
#===========================================================================#
# Stage `base` #
# ------------ #
# This image contains the base operating system to build the production #
# release on top of it. It is expected to mutate very slowly so we can have #
# the layers pre-cached when building. #
# TODO: Separate this in 2 bases: one for production and the other for #
# development or testing, like in here: #
# https://www.docker.com/blog/advanced-dockerfiles-faster-builds-and-smaller-images-using-buildkit-and-multistage-builds/ #
#===========================================================================#
FROM ubuntu:20.04 AS base
# Install apache, php, yarn or whatever "base production server"
# BUT NOT your source code, just "the base"
#===========================================================================#
# Stage `release` #
# --------------- #
# This image will be the one released to pre or prod and configured at #
# runtime via env-vars like backing-services, databases, etc. #
#===========================================================================#
FROM base AS release
# Copy the project files
COPY . /whatever-maybe-other-path/repos/app
# Copy the dependency files
COPY --from=repo-sources /whatever-path/repos /whatever-maybe-other-path/repos
# Continue with the "fine-tuning" after copying the source code, like static building, etc.
此处的 4 个目标以 2 个为块分组:
-
repo-sources-base
和repo-sources
适用于具有 SSH 密钥的本地下载,这些密钥永远不会被部署,也不会锁定到进入生产的中间层中。 -
base
和production
生成映像以投入生产。
问题是@Perseids所说的:运行docker build
只是忽略了缓存存储库的克隆,并且传递--no-cache
重建太多。
通过这种 4 目标结构,我们构建"一次"repo-sources-base
和base
,我们只想重建repo-sources
和production
.
这里的关键是名为INVALIDATE_CACHE_TIMESTAMP
的ARG
。
以下是行为:
- 如果您多次重建
--target repo-sources
,它会从缓存加载,我们不希望这样。 - 如果你重建
--target repo-sources --no-cache
它迫使重建repo-sources-base
,我们不希望这样。 - 如果重建
--target repo-sources --build-arg X=Y
则仅当Dockerfile
中引用 X 并且即使以后不使用 X 时,它才会强制重建。这就是为什么我在ARG
行中"命名"INVALIDATE_CACHE_TIMESTAMP
。
所以做
docker build --target repo-sources --build-arg INVALIDATE_CACHE_TIMESTAMP=first [...]
将建造它。
"再做一次"这个
docker build --target repo-sources --build-arg INVALIDATE_CACHE_TIMESTAMP=first [...]
使用缓存。
像这样更改值
docker build --target repo-sources --build-arg INVALIDATE_CACHE_TIMESTAMP=second [...]
强制重建
现在使用 first
或 second
将使用缓存,但新值将强制重建。
所以我在我的构建脚本中做了什么:
NOW=$(date --utc --iso-8601='ns' | sed 's/,/./' | cut -c 1-26 | sed 's/$/Z/')
docker build --target repo-sources --build-arg INVALIDATE_CACHE_TIMESTAMP=${NOW} [...]
docker build --target production [...]
因此,它"强制"再次构建repo-sources
,因此目标production
将从缓存中获取base
,并从我强制重建的最新缓存版本中获取repo-sources
。