将货物依赖项缓存在Docker卷中

时间:2019-03-01 21:59:47

标签: docker compilation rust-cargo docker-volume cargo

我正在Docker(rust:1.33.0)中构建Rust程序。

每次代码更改时,它都会重新编译(良好),并且还会重新下载所有依赖项(错误)。

我认为我可以通过添加VOLUME ["/usr/local/cargo"]来缓存依赖项。 edit 我也尝试过使用CARGO_HOME来移动此目录,但是没有运气。

我认为将其作为卷可以保留下载的依赖项,这些依赖项似乎位于此目录中。

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


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" }

代码:世界你好

更改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

6 个答案:

答案 0 :(得分:2)

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

在官方documentation中列出了Docker遵循的用于图像层缓存的规则:

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

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

  •   
  • 对于ADD和COPY指令,文件中的内容   检查图像并为每个文件计算校验和。的   文件的上次修改时间和上次访问时间不是   在这些校验和中考虑。在缓存查找期间,校验和   与现有图像中的校验和进行比较。如果有什么   已更改文件(例如内容和元数据),然后   缓存无效。

  •   
  • 除了ADD和COPY命令外,缓存检查不会查看   容器中的文件以确定缓存是否匹配。例如,   处理RUN apt-get -y update命令时,文件在   不检查容器以确定是否存在缓存命中。在   在这种情况下,只是使用命令字符串本身来查找匹配项。

  •   
     

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

因此问题出在COPY src/ ./src/中命令Dockerfile的位置。只要您的源文件之一发生更改,高速缓存就会失效,并且所有后续命令都将不使用高速缓存。因此,您的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中发生更改时,才会重新安装依赖项。

希望这会有所帮助。

答案 1 :(得分:2)

EDIT2 :以下是各种可能性的概述。向下滚动以获取原始答案。

  • 添加货运文件,创建伪造的main.rs / lib.rs,然后编译依赖项。之后,删除假来源并添加真实来源。 [缓存依赖关系,但带有工作空间的几个伪文件]
  • 添加货运文件,创建伪造的main.rs / lib.rs,然后编译依赖项。然后,使用依赖关系创建一个新层,然后从那里继续。 [类似于以上]
  • 从外部为缓存目录装入卷。 [缓存所有内容,依靠调用者传递--mount]
  • 在新Docker版本的Dockerfile中使用RUN --mount=type=cache,target=/the/path cargo build。 [缓存所有内容,这似乎是一个好方法,但目前对我来说太新了。可执行文件,不是图像的一部分]
  • 使用cargo-build-deps。 [可能适用于某些设备,但尚不支持货运工作区]。
  • 等待Cargo issue 2644。 [愿意将其添加到货运中,但尚无具体解决方案]。
  • 在Dockerfile中使用VOLUME ["/the/path"] 不起作用,这仅适用于每个层(每个命令)。

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

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

货物工作区必须手动添加每个货物文件,而对于某些解决方案,必须生成十几个伪造的main.rs / lib.rs。对于具有单个Cargo文件的项目,解决方案可以更好地工作。


通过添加

,我可以为我的特殊情况使用缓存
ENV CARGO_HOME /code/dockerout/cargo
ENV CARGO_TARGET_DIR /code/dockerout/target

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

这是从外部安装的,而不是从Dockerfile安装的。

EDIT1 :我很困惑为什么这行得通,但是@ b.enoit.be和@BMitch弄清楚了这是因为在Dockerfile中声明的卷只存在一层(一个命令)。 / p>

答案 2 :(得分:2)

我会说,更好的解决方案是使用multi-stage buildhere指向的docker there

这样,您可以创建自己的第一个映像,该映像将同时构建应用程序和依赖项,然后仅在第二个映像中使用第一个映像中的依赖项文件夹

这是受到您对@Jack Gore's answer的评论以及上面链接的两个问题评论的启发。

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:仅运行一次即可减少生成的层数;更多信息here

答案 3 :(得分:1)

通过将BuildKit集成到docker中,如果您能够利用高级BuildKit后端,现在可以mount a cache volume during a RUN command和恕我直言,这已成为缓存货物构建的最佳方法。缓存卷保留先前运行时写入其中的数据。

要使用BuildKit,您将装载两个缓存卷,一个装载目录,用于装载外部板条箱货源的货物目录,一个装载在目标目录中,用于缓存所有构建的构件,包括外部板条箱和项目箱以及库。

如果您的基本映像为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仍无法在整个运行期间保持相同的卷。

this github issue中介绍了用于实现Cargo构建缓存的其他选项(包括sccache和cargo wharf项目)。

答案 4 :(得分:0)

我确定您可以调整此代码以与Dockerfile一起使用,但是我wrote a dockerized drop-in replacement for cargo可以保存到软件包中并以./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 "$@"

答案 5 :(得分:0)

我想出了如何使用cargo-build-deps的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