使用Docker构建缓存Rust依赖关系

时间:2019-10-20 13:42:27

标签: docker rust dockerfile actix-web

我在Rust + Actix-web中有一个Hello World Web项目。我有几个问题。首先是代码的每次更改都会导致重新编译整个项目,包括下载和编译每个板条箱。我想像正常开发一样工作-这意味着缓存已编译的板条箱,仅重新编译我的代码库。第二个问题是它不会暴露我的应用程序。通过网络浏览器无法访问

Dockerfile:

FROM rust

WORKDIR /var/www/app

COPY . .

EXPOSE 8080

RUN cargo run

docker-compose.yml:

version: "3"
services:
  app:
    container_name: hello-world
    build: .
    ports:
      - '8080:8080'
    volumes:
      - .:/var/www/app
      - registry:/root/.cargo/registry

volumes:
  registry:
    driver: local

main.rs:

extern crate actix_web;

use actix_web::{web, App, HttpServer, Responder};

fn index() -> impl Responder {
    "Hello world"
}

fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(web::resource("/").to(index)))
        .bind("0.0.0.0:8080")?
        .run()
}

Cargo.toml:

[package]
name = "hello-world"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
actix-web = "1.0"

7 个答案:

答案 0 :(得分:7)

使用(仍处于实验阶段的)Docker Buildkit,您最终可以在docker build步骤中正确缓存构建文件夹:

Dockerfile:

# syntax=docker/dockerfile:experimental
from rust
ENV HOME=/home/root
WORKDIR $HOME/app
[...]
RUN --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/home/root/app/target \
    cargo build --release

然后运行:

DOCKER_BUILDKIT=1 docker build . --progress=plain

后续的docker构建将重用缓存中的cargo和target文件夹,从而大大加快了构建速度。

要清除docker缓存安装:docker builder prune --filter type=exec.cachemount

如果看不到正确的缓存:如果看不到正确的缓存,请确保在docker映像中确认货物/注册表和目标文件夹的位置。

最低工作示例:https://github.com/benmarten/sccache-docker-test/tree/no-sccache

答案 1 :(得分:2)

您可以使用cargo-chef通过多阶段构建来利用Docker层缓存。

FROM rust as planner
WORKDIR app
# We only pay the installation cost once, 
# it will be cached from the second build onwards
RUN cargo install cargo-chef 
COPY . .
RUN cargo chef prepare  --recipe-path recipe.json

FROM rust as cacher
WORKDIR app
RUN cargo install cargo-chef
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json

FROM rust as builder
WORKDIR app
COPY . .
# Copy over the cached dependencies
COPY --from=cacher /app/target target
RUN cargo build --release --bin app

FROM rust as runtime
WORKDIR app
COPY --from=builder /app/target/release/app /usr/local/bin
ENTRYPOINT ["./usr/local/bin/app"]

它不需要Buildkit,并且可以用于简单的项目和工作区。 您可以在release announcement中找到更多详细信息。

答案 2 :(得分:1)

似乎并非只有您一个人通过docker build进程来缓存rust依赖项。这是一篇很棒的文章,可以帮助您:https://blog.mgattozzi.dev/caching-rust-docker-builds/

要点是,您首先需要一个dummy.rs和您的Cargo.toml,然后构建它以缓存依赖项,然后再复制您的应用程序源,以免每次构建时都使缓存无效。

Dockerfile

import React,{Component} from 'react';
import Cardlist from './Cardlist';
import {robots} from './robots';
import SearchBox from './SearchBox';

class App extends Component{
    constructor(){
        super();
        this.state={
            robots:robots,
            searchfield:''
        }
    }
    onSearchChange=(event)=> {
        console.log(event);
    }
    render(){
        return(
        <div className='tc'>
        <h1> RoboFriends </h1>
        <SearchBox searchChange={this.onSearchChange}/>
        <Cardlist robots={this.state.robots} />
        </div>
        );
    }
}
export default App;

CMD应用程序名称“ app”基于您在Cargo.toml中为二进制文件指定的名称。

dummy.rs

FROM rust
WORKDIR /var/www/app
COPY dummy.rs .
COPY Cargo.toml .
RUN sed -i 's#src/main.rs#dummy.rs#' Cargo.toml
RUN cargo build --release
RUN sed -i 's#dummy.rs#src/main.rs#' Cargo.toml
COPY . .
RUN cargo build --release
CMD ["target/release/app"]

Cargo.toml

fn main() {}

src / main.rs

[package]
name = "app"
version = "0.1.0"
authors = ["..."]
[[bin]]
name = "app"
path = "src/main.rs"

[dependencies]
actix-web = "1.0.0"

答案 3 :(得分:0)

我认为问题在于您的volumes定义未进行绑定安装。我相信您当前的配置是将HOST ./registry/复制到DOCKER /root/.cargo/registry/,写入DOCKER /root/.cargo/registry/,并在关闭容器时丢弃内容。

相反,您需要在卷上指定bind类型:

version: "3"
services:
  app:
    container_name: hello-world
    build: .
    environment:
      - CARGO_HOME=/var/www/
    ports:
      - '8080:8080'
    volumes:
      - .:/var/www/app
      - type: bind
        source: ./registry
        target: /root/.cargo/registry

但是,请记住,还会创建一个/root/.cargo/.package-cache文件,但是这里没有保存。相反,您可以将source更改为./.cargo并定位到/root/.cargo


对于我自己(主要是cli)的rust项目,我喜欢使用drop-in replacement I've written for cargo,我已确认在两次构建之间缓存软件包,从而大大减少了构建时间。可以将其复制到/usr/local/bin以便全局使用,也可以在单个项目中以./cargo build的身份运行。但是请记住,此特定脚本假定应用程序位于容器内的/usr/src/app处,因此可能需要根据您的使用情况进行调整。

答案 4 :(得分:0)

这就是我的工作,并且与构建脚本兼容。这是一个多阶段构建,因此生成的图像很小,但是将构建的依赖项缓存在第一张图像中。

FROM rust:1.43 AS builder

RUN apt-get update
RUN cd /tmp && USER=root cargo new --bin <projectname>
WORKDIR /tmp/<projectname>

# cache rust dependencies in docker layer
COPY Cargo.toml Cargo.lock ./
RUN touch build.rs && echo "fn main() {println!(\"cargo:rerun-if-changed=\\\"/tmp/<projectname>/build.rs\\\"\");}" >> build.rs
RUN cargo build --release

# build the real stuff and disable cache via the ADD
ADD "https://www.random.org/cgi-bin/randbyte?nbytes=10&format=h" skipcache
COPY ./build.rs ./build.rs

# force the build.rs script to run by modifying it
RUN echo " " >> build.rs
COPY ./src ./src
RUN cargo build --release

FROM rust:1.43
WORKDIR /bin
COPY --from=builder /tmp/<projectname>/target/release/server /bin/<project binary>
RUN chmod +x ./<project binary>
CMD ./<project binary>

答案 5 :(得分:0)

尽管electronix384128的回答很好。我想通过添加.cargo/git的缓存(使用git进行任何依赖都需要)并添加一个多阶段docker示例来对其进行扩展。

使用rust-musl-builder和Docker Buildkit功能,该功能现在在Docker Desktop 2.4中是默认设置。在其他版本上,您可能仍需要通过以下方式启用它:DOCKER_BUILDKIT=1 docker build .

rusl-musl-builder的工作目录是/home/rust/src
尝试在--mount上设置uid / gid,但由于目标中的权限问题而无法编译锈。

# syntax=docker/dockerfile:experimental
FROM ekidd/rust-musl-builder:stable AS builder

COPY . .
RUN --mount=type=cache,target=/home/rust/.cargo/git \
    --mount=type=cache,target=/home/rust/.cargo/registry \
    --mount=type=cache,target=/home/rust/src/target \
    sudo chown -R rust: target /home/rust/.cargo && \
    cargo build --release && \
    # Copy executable out of the cache so it is available in the final image.
    cp target/x86_64-unknown-linux-musl/release/my-executable ./my-executable

FROM alpine
COPY --from=builder /home/rust/src/my-executable .
USER 1000
CMD ["./my-executable"]

答案 6 :(得分:0)

根据@ckaserer的回答,您可以nose.cfg在构建应用之前构建依赖项。

首先只复制您的 RUN echo "fn main() {}" > ./src/main.rsCargo.toml 文件并构建虚拟 main.rs 文件:

Cargo.lock

然后你可以复制你真正的 src 目录并再次运行构建:

FROM rust as rust-builder
WORKDIR /usr/src/app

# Copy Cargo files
COPY ./Cargo.toml .
COPY ./Cargo.lock .

# Create fake main.rs file in src and build
RUN mkdir ./src && echo 'fn main() { println!("Dummy!"); }' > ./src/main.rs
RUN cargo build --release

然后我们可以将我们的文件复制到 debain 的精简版。 这是完整的 docker 文件:

# Copy source files over
RUN rm -rf ./src
COPY ./src ./src

# The last modified attribute of main.rs needs to be updated manually,
# otherwise cargo won't rebuild it.
RUN touch -a -m ./src/main.rs

RUN cargo build --release