我正在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
答案 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
] RUN --mount=type=cache,target=/the/path cargo build
。 [缓存所有内容,这似乎是一个好方法,但目前对我来说太新了。可执行文件,不是图像的一部分] VOLUME ["/the/path"]
不起作用,这仅适用于每个层(每个命令)。注意:可以在Dockerfile中设置CARGO_HOME
和ENV 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 build和here指向的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
和两个工作区:utils
和db
。
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