Rust 开发加速神器:用cargo-chef
打造极速 Docker 构建流程
缓存您的 Rust 项目的依赖,并加速您的 Docker 构建。
cargo-chef
最初是为Zero to Production In Rust[1]的部署章节开发的,这是一个使用 Rust 编程语言进行后端开发的实践介绍。
如何安装
您可以从crates.io[2]使用以下命令安装cargo-chef
:
cargo install cargo-chef --locked
如何使用
cargo-chef 不打算在本地运行
它的主要用途是通过在实际源代码复制之前运行来加速容器构建。不要在现有代码库上运行它,以避免文件被覆盖。
cargo-chef
提供了两个命令:prepare
和cook
:
cargo chef --help
cargo-chef
用法:
cargo chef <子命令>
子命令:
cook 根据`cargo chef prepare`识别的最小项目骨架重新构建并构建以缓存依赖
prepare 分析当前项目以确定构建它并缓存依赖所需的最小文件子集(Cargo.lock和Cargo.toml清单)
prepare
检查您的项目并构建一个recipe,它捕获了构建依赖所需的信息集。
cargo chef prepare --recipe-path recipe.json
这里没有太多神秘的事情,您可以检查recipe.json
文件:它包含了您项目的骨架(例如所有带有相对路径的Cargo.toml
文件,Cargo.lock
文件可用)以及一些额外的信息。特别是它确保所有库和二进制文件即使在规范的默认位置找到(对于二进制文件是src/main.rs
,对于库是src/lib.rs
),也要在它们各自的Cargo.toml
文件中明确声明。
recipe.json
等同于 Python 的requirements.txt
文件 - 它是cargo chef cook
命令的唯一输入,该命令将构建我们的依赖:
cargo chef cook --recipe-path recipe.json
如果您想以--release
模式构建:
cargo chef cook --release --recipe-path recipe.json
您还可以选择覆盖使用哪个 Rust 工具链。例如,强制使用nightly
工具链:
cargo +nightly chef cook --recipe-path recipe.json
cargo-chef
旨在在 Dockerfile 中使用:
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# 构建依赖 - 这是缓存的Docker层!
RUN cargo chef cook --release --recipe-path recipe.json
# 构建应用程序
COPY . .
RUN cargo build --release --bin app
# 我们不需要Rust工具链来运行二进制文件!
FROM debian:bookworm-slim AS runtime
WORKDIR /app
COPY --from=builder /app/target/release/app /usr/local/bin
ENTRYPOINT ["/usr/local/bin/app"]
我们使用三个阶段:第一个计算配方文件,第二个缓存我们的依赖并构建二进制文件,第三个是我们的运行时环境。
只要您的依赖不变,recipe.json
文件将保持不变,因此cargo chef cook --release --recipe-path recipe.json
的结果将被缓存,大大加快您的构建速度(在一些商业项目中测量高达 5 倍)。
预构建的镜像
我们提供lukemathwalker/cargo-chef
作为预构建的 Docker 镜像,配备了 Rust 和cargo-chef
。
标签方案是<cargo-chef version>-rust-<rust version>
。
例如,0.1.22-rust-1.56.0
。
您可以选择获取cargo-chef
或rust
的最新版本,使用:
latest-rust-1.56.0
(使用特定 Rust 版本的最新cargo-chef
);0.1.22-rust-latest
(使用特定cargo-chef
版本的最新 Rust)。 您可以在Dockerhub[3]上找到所有可用的标签[4]。
您必须在所有阶段使用相同的 Rust 版本
如果您在其中一个阶段使用不同的 Rust 版本 缓存将无法按预期工作。
不使用预构建的镜像
如果您不想使用lukemathwalker/cargo-chef
镜像,您可以简单地在 Dockerfile 中安装 CLI:
FROM rust:1 AS chef
# 我们只支付一次安装成本,
# 从第二次构建开始将被缓存
RUN cargo install cargo-chef
WORKDIR app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# 构建依赖 - 这是缓存的Docker层!
RUN cargo chef cook --release --recipe-path recipe.json
# 构建应用程序
COPY . .
RUN cargo build --release --bin app
# 我们不需要Rust工具链来运行二进制文件!
FROM debian:bookworm-slim AS runtime
WORKDIR app
COPY --from=builder /app/target/release/app /usr/local/bin
ENTRYPOINT ["/usr/local/bin/app"]
在 Alpine 上运行二进制文件
如果您想使用alpine
发行版运行您的应用程序,您需要创建一个完全静态的二进制文件。
推荐的方法是用`muslrust`[5]为x86_64-unknown-linux-musl
目标构建。cargo-chef
适用于x86_64-unknown-linux-musl
,但我们是交叉编译 - 必须明确指定目标工具链。
一个示例 Dockerfile 如下所示:
# 使用`rust-musl-builder`作为基础镜像,而不是
# 官方的Rust工具链
FROM clux/muslrust:stable AS chef
USER root
RUN cargo install cargo-chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# 注意我们指定了--target标志!
RUN cargo chef cook --release --target x86_64-unknown-linux-musl --recipe-path recipe.json
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl --bin app
FROM alpine AS runtime
RUN addgroup -S myuser && adduser -S myuser -G myuser
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/app /usr/local/bin/
USER myuser
CMD ["/usr/local/bin/app"]
优势与限制
cargo-chef
已经在一些开源项目和一些商业项目上进行了测试,但我们的测试肯定没有穷尽cargo build
定制的可能性范围,我们确信还有一些粗糙的边缘需要平滑 - 请在GitHub[6]上提交问题。
cargo-chef
的优势:
一种常见的替代方法是将最小的main.rs
加载到带有Cargo.toml
和Cargo.lock
的容器中,以构建一个只包含您的依赖的 Docker 层(更多信息在这里[7])。与cargo-chef
相比,这是脆弱的,cargo-chef
将:
自动拾取工作空间中的所有 crates(以及添加的新 crates) 当文件或 crates 移动时仍然工作,这将需要手动编辑使用“手动”方法的 Dockerfile
生成较少的中间 Docker 层(对于工作空间)
限制和注意事项:
cargo chef cook
和cargo build
必须从相同的工作目录执行。如果您检查使用cat
在target/debug/deps
下的一个项目的*.d
文件,您会注意到它们包含引用项目target
目录的绝对路径。如果移动,cargo
将不会将它们作为缓存的依赖利用;cargo build
将从头开始构建本地依赖(项目之外),即使它们没有改变,这是由于它依赖于时间戳的指纹逻辑(请参见`cargo`存储库上的这个*长*问题[8]);
Zero to Production In Rust: https://zero2prod.com
[2]crates.io: https://crates.io
[3]Dockerhub: https://hub.docker.com/r/lukemathwalker/cargo-chef
[4]所有可用的标签: https://hub.docker.com/r/lukemathwalker/cargo-chef
[5]muslrust
: https://github.com/clux/muslrust
GitHub: https://github.com/LukeMathWalker/cargo-chef
[7]更多信息在这里: https://www.lpalmieri.com/posts/fast-rust-docker-builds/#caching-rust-builds
[8]cargo
存储库上的这个长问题: https://github.com/rust-lang/cargo/issues/2644