Docker量中的缓存货物依赖性



我正在docker( rust:1.33.0)中构建生锈程序。

每次代码更改时,都会重新编译(良好),它还重新下载所有依赖关系(糟糕)。

我认为我可以通过添加VOLUME ["/usr/local/cargo"]来缓存依赖关系。 edit 我也尝试使用CARGO_HOME移动此DIR而不运气。

我以为将此卷制作会持续下载的依赖项,这些依赖项似乎在此目录中。

但是它不起作用,他们仍然每次都会下载。为什么?


dockerfile

FROM rust:1.33.0
VOLUME ["/output", "/usr/local/cargo"]
RUN rustup default nightly-2019-01-29
COPY Cargo.toml .
COPY src/ ./src/
RUN ["cargo", "build", "-Z", "unstable-options", "--out-dir", "/output"]

仅使用docker build .

cargo.toml

[package]
name = "mwe"
version = "0.1.0"
[dependencies]
log = { version = "0.4.6" }

代码:Just Hello World

更改main.rs后第二次运行的输出:

...
Step 4/6 : COPY Cargo.toml .
---> Using cache
---> 97f180cb6ce2
Step 5/6 : COPY src/ ./src/
---> 835be1ea0541
Step 6/6 : RUN ["cargo", "build", "-Z", "unstable-options", "--out-dir", "/output"]
---> Running in 551299a42907
Updating crates.io index
Downloading crates ...
Downloaded log v0.4.6
Downloaded cfg-if v0.1.6
Compiling cfg-if v0.1.6
Compiling log v0.4.6
Compiling mwe v0.1.0 (/)
Finished dev [unoptimized + debuginfo] target(s) in 17.43s
Removing intermediate container 551299a42907
---> e4626da13204
Successfully built e4626da13204

dockerfile内部的音量在这里适合生产。这将在每个构建步骤中安装一个匿名卷,然后在运行容器时再次安装。该步骤完成后,每个构建步骤期间的卷都会丢弃,这意味着您需要再次下载所有需要这些依赖项的其他步骤。

标准模型是复制依赖项规范,运行依赖项下载,复制代码,然后以4个单独的步骤编译或运行代码。这使Docker可以有效地缓存层。我不熟悉生锈或货物,但我相信这看起来像:

FROM rust:1.33.0
RUN rustup default nightly-2019-01-29
COPY Cargo.toml .
RUN cargo fetch # this should download dependencies
COPY src/ ./src/
RUN ["cargo", "build", "-Z", "unstable-options", "--out-dir", "/output"]

另一种选择是使用BuildKit(在18.09,发布,2018-11-08发布)打开一些实验功能,以便Docker将这些依赖性保存在类似于您的构建的命名卷中。该目录可以在构建中重复使用,但永远不会添加到图像本身中,使其对于下载缓存等功能很有用。

# syntax=docker/dockerfile:experimental
FROM rust:1.33.0
VOLUME ["/output", "/usr/local/cargo"]
RUN rustup default nightly-2019-01-29
COPY Cargo.toml .
COPY src/ ./src/
RUN --mount=type=cache,target=/root/.cargo 
    ["cargo", "build", "-Z", "unstable-options", "--out-dir", "/output"]

请注意,以上假设货物是在/root/.cargo中缓存文件。您需要验证此问题并适当调整。我也没有将MOUNT语法与JSON Exec语法混合,以了解该部分是否有效。您可以在此处阅读有关BuildKit实验功能的更多信息:https://github.com/moby/buildkit/blob/master/master/frontend/dockerfile/docs/docs/experiment.md

从18.09打开BuildKit,较新版本与export DOCKER_BUILDKIT=1一样容易,然后从该外壳运行您的构建。

我会说,不错解决方案是求助于Docker多阶段构建,如此处和那里的指向

这样,您可以创建第一个图像,可以同时构建您的应用程序和依赖项,然后仅在第二张图像中使用第一个图像中的依赖关系文件夹

这是您对@Jack Gore答案的评论的启发,以及上面链接的两个问题。

FROM rust:1.33.0 as dependencies
WORKDIR /usr/src/app
COPY Cargo.toml .
RUN rustup default nightly-2019-01-29 && 
    mkdir -p src && 
    echo "fn main() {}" > src/main.rs && 
    cargo build -Z unstable-options --out-dir /output
FROM rust:1.33.0 as application
# Those are the lines instructing this image to reuse the files 
# from the previous image that was aliased as "dependencies" 
COPY --from=dependencies /usr/src/app/Cargo.toml .
COPY --from=dependencies /usr/local/cargo /usr/local/cargo
COPY src/ src/
VOLUME /output
RUN rustup default nightly-2019-01-29  && 
    cargo build -Z unstable-options --out-dir /output

ps:只有一次运行将减少您生成的层数;更多信息此处

这是可能性的概述。(向下滚动以获取我的原始答案。)

  • 添加货物文件,创建伪造的main.rs/lib.rs,然后编译依赖项。之后删除假源并添加真实的源。[缓存依赖性,但有几个带有工作空间的假文件]。
  • 添加货物文件,创建伪造的main.rs/lib.rs,然后编译依赖项。之后创建一个具有依赖关系的新层,然后从那里继续。[类似于上述]。
  • 外部安装一个用于CACHE DIR的音量。[缓存所有内容,依靠呼叫者通过--mount]。
  • 在新Docker版本中使用RUN --mount=type=cache,target=/the/path cargo build。[缓存一切,似乎是一种好方法,但目前太新了,无法对我有用。可执行文件不是图像的一部分。编辑:有关解决方案的信息。]
  • 在另一个容器或主机上运行SCCACHE,然后在构建过程中连接到该sccace。请参阅《货物》第2644页中的此评论。
  • 使用货物建造Deps。[可能对某些人有用,但不支持货物工作空间(2019年)。
  • 等待货物问题2644。[愿意将其添加到货物中,但尚无具体解决方案]。
  • 在dockerfile中使用 VOLUME ["/the/path"] 不起作用,这是每层(每个命令)。

注意:可以在Dockerfile中设置CARGO_HOMEENV CARGO_TARGET_DIR,以控制下载缓存和编译输出的位置。

还注意:cargo fetch至少可以缓存下载依赖项,尽管不编译。

货物工作区不得不手动添加每个货物文件,对于某些解决方案,必须生成十二个伪造的main.rs/lib.rs。对于具有单个货物文件的项目,解决方案效果更好。


通过添加

,我有缓存可以为我的特定情况工作
ENV CARGO_HOME /code/dockerout/cargo
ENV CARGO_TARGET_DIR /code/dockerout/target

/code是我安装代码的目录。

这是外部安装的,而不是来自码头。

edit1 :我很困惑为什么这起作用了,但是 @b.enoit.be和@bmitch清除了这是因为dockerfile在dockerfile内部声明的卷仅用于一层(一个命令)。<<<<<<<<<<<<。/p>

您不需要使用显式码头卷来缓存依赖项。Docker将自动缓存图像的不同"层"。基本上,Dockerfile中的每个命令都对应于图像的一层。您面临的问题基于Docker图像层缓存的工作方式。

官方文档中列出了Docker遵循的图像层缓存的规则:

  • 从缓存中的父映像开始,下一个 将指示与从中衍生的所有儿童图像进行比较 基本图像以查看其中一个是否是使用完全相同的 操作说明。如果没有,缓存无效。

  • 在大多数情况下,只需将Dockerfile中的说明与 儿童图像之一就足够了。但是,某些说明 需要更多的检查和解释。

  • 用于添加和复制说明,文件中的文件中的内容 检查图像并为每个文件计算校验和计算。这 文件的最后修饰和最后的时间不是 在这些校验和中考虑。在缓存查找期间,校验和 将与现有图像中的校验和进行比较。如果有什么 在文件中已更改,例如内容和元数据,然后 缓存无效。

  • 除了添加和复制命令外,缓存检查不查看 容器中的文件以确定缓存匹配。例如, 处理运行apt -get -y更新时命令更新的文件 没有检查容器以确定是否存在缓存击中。在 这种情况只有命令字符串本身用于查找匹配。

一旦缓存无效,所有随后的Dockerfile命令 生成新图像,并且不使用缓存。

因此,问题在于Dockerfile中命令COPY src/ ./src/的定位。每当您的一个源文件发生更改时,缓存将无效,所有后续命令都不会使用缓存。因此,您的cargo build命令将不使用Docker缓存。

为了解决您的问题,它将像在Docker文件中重新排序命令一样简单,以下内容:

FROM rust:1.33.0
RUN rustup default nightly-2019-01-29
COPY Cargo.toml .
RUN ["cargo", "build", "-Z", "unstable-options", "--out-dir", "/output"]
COPY src/ ./src/

这样做,只有在Cargo.toml发生更改时,您的依赖项才会重新安装。

希望这会有所帮助。

将BuildKit集成到Docker中,如果您能够利用自己的Superior BuildKit后端,现在就有可能在运行命令期间安装缓存量,恕我直言,这已成为缓存货物构建的最佳方法。缓存卷保留了以前运行中写入的数据。

要使用buildKit,您将安装两个高速缓存量,一个用于货物箱,该货物箱源是外部板条箱源,另一个用于目标dir,它可以缓存所有已建造的工件,包括外部板条箱和项目箱和项目箱,libs。

如果您的基本图像为rust,则将$ CARGO_HOME设置为/usr/local/cargo,因此您的命令看起来像这样:

RUN --mount=type=cache,target=/usr/local/cargo,from=rust,source=/usr/local/cargo 
    --mount=type=cache,target=target 
    cargo build

如果您的基本图像是其他的,则需要将/usr/local/cargo位更改为$CARGO_HOME的值,否则添加ENV CARGO_HOME=/usr/local/cargo行。附带说明,聪明的事情是从字面上设置target=$CARGO_HOME并让Docker进行扩展,但是似乎无法正常工作 - 扩展发生,但是当您这样做时,BuildKit仍然不会在整个运行中持续相同的音量。

在此GitHub问题中描述了实现货物构建缓存(包括SCCACHE和cargo wharf项目)的其他选项。

我想出了如何使用romac的货物建造叉子来实现这也可以与货物工作区一起使用。

此示例具有my_app,并且两个工作区:utilsdb

FROM rust:nightly as rust
# Cache deps
WORKDIR /app
RUN sudo chown -R rust:rust .
RUN USER=root cargo new myapp
# Install cache-deps
RUN cargo install --git https://github.com/romac/cargo-build-deps.git
WORKDIR /app/myapp
RUN mkdir -p db/src/ utils/src/
# Copy the Cargo tomls
COPY myapp/Cargo.toml myapp/Cargo.lock ./
COPY myapp/db/Cargo.toml ./db/
COPY myapp/utils/Cargo.toml ./utils/
# Cache the deps
RUN cargo build-deps
# Copy the src folders
COPY myapp/src ./src/
COPY myapp/db/src ./db/src/
COPY myapp/utils/src/ ./utils/src/
# Build for debug
RUN cargo build

我敢肯定,您可以调整此代码以供DockerFile使用,但是我为cargo编写了一个Dockerized drop-in替换,您可以将其保存到包装和包裹作为./cargo build --release运行。此仅适用于(大多数)开发(使用rust:latest),但不是为CI或其他任何设置。

用法:./cargo build./cargo build --release

它将使用当前的工作目录并将缓存保存到./.cargo。(您可以忽略版本控件中的整个目录,并且不需要事先存在。)

在您的项目文件夹中创建一个名为 cargo的文件,在其上运行chmod +x ./cargo,然后将以下代码放入其中:

#!/bin/bash
# This is a drop-in replacement for `cargo`
# that runs in a Docker container as the current user
# on the latest Rust image
# and saves all generated files to `./cargo/` and `./target/`.
#
# Be sure to make this file executable: `chmod +x ./cargo`
#
# # Examples
#
# - Running app: `./cargo run`
# - Building app: `./cargo build`
# - Building release: `./cargo build --release`
#
# # Installing globally
#
# To run `cargo` from anywhere,
# save this file to `/usr/local/bin`.
# You'll then be able to use `cargo`
# as if you had installed Rust globally.
sudo docker run 
    --rm 
    --user "$(id -u)":"$(id -g)" 
    --mount type=bind,src="$PWD",dst=/usr/src/app 
    --workdir /usr/src/app 
    --env CARGO_HOME=/usr/src/app/.cargo 
    rust:latest 
    cargo "$@"

最新更新