我正在Docker(rust:1.33.0
)中构建一个Rust程序。
每次代码更改时,它都会重新编译(好),同时重新下载所有依赖项(坏)。
我想我可以通过添加VOLUME ["/usr/local/cargo"]
来缓存依赖项。edit 我也试过用CARGO_HOME
移动这个目录,但没有成功。
我认为将其作为卷可以持久保存下载的依赖项,这些依赖项似乎位于此目录中。
但它没有工作,他们仍然下载每一次。为什么?
停靠文件
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
7条答案
按热度按时间ni65a41a1#
Dockerfile中的卷在这里会产生反作用。这会在每个构建步骤中挂载一个匿名卷,并且在运行容器时再次挂载。每个构建步骤中的卷在该步骤完成后会被丢弃,这意味着您需要为需要这些依赖项的任何其他步骤再次下载整个内容。
标准模型是复制依赖项规范,运行依赖项下载,复制代码,然后编译或运行代码,分4个独立的步骤。这让docker以一种高效的方式缓存层。我对rust或cargo并不熟悉,但我相信看起来像这样:
另一个选择是打开BuildKit(available in 18.09, released 2018-11-08)的一些实验性特性,这样Docker就可以将这些依赖项保存在类似于构建的命名卷中。该目录可以在构建之间重用,但不会添加到映像本身,这使得它对于下载缓存之类的东西很有用。
请注意,上面假设cargo将文件缓存在/root/. cargo中。您需要验证这一点并进行适当的调整。我也没有将mount语法与json exec语法混合,以了解这部分是否有效。您可以在此处阅读更多关于BuildKit实验特性的信息:https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
从18.09和更高版本打开BuildKit就像
export DOCKER_BUILDKIT=1
一样简单,然后从该shell运行您的构建。smdncfj32#
我会说,更好的解决方案是求助于docker multi-stage build,正如这里和那里指出的那样
这样,您可以为自己创建第一个映像,该映像将构建应用程序和依赖项,然后仅在第二个映像中使用第一个映像中的依赖项文件夹
这是由您对@杰克·戈尔的回答的评论和上面链接的两个问题的评论所启发的。
yhuiod9q3#
这里有一个可能性的概述。(向下滚动我的原始答案。)
main.rs
/lib.rs
,然后编译依赖项。然后删除假的源代码并添加真正的源代码。[缓存依赖项,但有几个假的文件带有工作区]。main.rs
/lib.rs
,然后编译依赖项。然后创建一个新的依赖层,并从那里继续。[类似于上面]。--mount
]。RUN --mount=type=cache,target=/the/path cargo build
。[缓存所有内容,似乎是一个好方法,但目前对我来说太新了。可执行文件不是图像的一部分。编辑:请参阅此处以获取解决方案。]VOLUME ["/the/path"]
不起作用,这仅针对每层(每个命令)。注意:可以在Dockerfile中设置
CARGO_HOME
和ENV CARGO_TARGET_DIR
来控制下载缓存和编译输出的位置。另请注意:
cargo fetch
至少可以缓存依赖项的下载,尽管不进行编译。Cargo工作区需要手动添加每个Cargo文件,对于某些解决方案,需要生成一打伪
main.rs
/lib.rs
。对于只有一个Cargo文件的项目,这些解决方案效果更好。通过添加以下内容,我可以使缓存为我的特定情况工作
其中
/code
是我装载代码的目录。这是外部挂载的,而不是来自Dockerfile。
EDIT 1:我很困惑为什么会这样,但是@ b.enoit.be和@BMitch澄清了这是因为在Dockerfile中声明的卷只存在于一个层(一个命令)中。
v1l68za44#
你不需要使用一个显式的Docker卷来缓存你的依赖项。Docker会自动缓存你的图像的不同“层”。基本上,Docker文件中的每个命令对应于图像的一个层。你所面临的问题是基于
Docker
图像层缓存的工作原理。Docker遵循的图像层缓存规则在官方的documentation中列出:
一旦缓存失效,所有后续Dockerfile命令都会生成新图像,并且不会使用缓存。
所以问题出在
Dockerfile
中COPY src/ ./src/
命令的位置上。只要源文件中有一个更改,缓存就会失效,所有后续命令都不会使用缓存。因此,cargo build
命令不会使用Docker
缓存。要解决您的问题,只需将
Docker
文件中的命令重新排序为:这样做,只有当
Cargo.toml
发生变化时,依赖项才会被重新安装。希望这对你有帮助。
clj7thdc5#
随着BuildKit与Docker的集成,如果你能够利用强大的BuildKit后端,现在就可以mount a cache volume during a RUN command,恕我直言,这已经成为缓存货物构建的最佳方式。缓存卷会保留以前运行时写入的数据。
要使用BuildKit,您将挂载两个缓存卷,一个用于cargo dir,它缓存外部crate源文件,另一个用于target dir,它缓存所有构建的工件,包括外部crate以及项目bin和libs。
如果您的基础映像是
rust
,$CARGO_HOME设置为/usr/local/cargo,则您的命令如下所示:如果你的基本映像是其他的东西,你将需要改变
/usr/local/cargo
位为任何$CARGO_HOME
的值,或者添加一行ENV CARGO_HOME=/usr/local/cargo
。作为一个侧记,聪明的做法是设置字面上的target=$CARGO_HOME
,让Docker来做扩展,但它似乎并不工作正确-扩展发生了,但是当您这样做时,buildkit仍然不能在运行中保持相同的卷。实现Cargo构建缓存的其他选项(包括sccache和
cargo wharf
项目)在this github issue中描述。yws3nbqq6#
我想出了如何让这个也工作在货物工作区,使用romac的cargo-build-deps的fork。
此示例具有
my_app
和两个工作区:utils
和db
中的一个或多个。cgh8pdjw7#
我相信你可以调整这个代码来使用一个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
,然后将以下代码放入其中: