我正在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_HOME
和ENV 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
,并且两个工作区: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
我敢肯定,您可以调整此代码以供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 "$@"