代码之家  ›  专栏  ›  技术社区  ›  Mark

在Docker卷中缓存货物依赖项

  •  0
  • Mark  · 技术社区  · 6 年前

    我正在Docker建立一个锈迹程序( rust:1.33.0 ).

    每次代码更改时,它都会重新编译(好),这也会重新下载所有依赖项(坏)。

    我想我可以通过添加 VOLUME ["/usr/local/cargo"] . 编辑 我也尝试过用 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 . .

    货物汤姆

    [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 回复  |  直到 5 年前
        1
  •  7
  •   Jacob Marble Ray Donnelly    4 年前

    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启用一些实验性功能( available in 18.09, released 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"]
    

    注意,上面假设cargo正在/root/中缓存文件。货物您需要对此进行验证,并根据需要进行调整。我还没有将挂载语法与json exec语法混合使用,以了解该部分是否有效。您可以在此处阅读有关BuildKit实验功能的更多信息: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md

    从18.09和更新版本中打开BuildKit非常简单 export DOCKER_BUILDKIT=1 然后从这个外壳运行你的构建。

        2
  •  6
  •   β.εηοιτ.βε grigorevp    6 年前

    我会说,更好的解决办法是求助于docker multi-stage build 正如所指出的 here 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
  •  3
  •   Mark    5 年前

    下面是对各种可能性的概述。(向下滚动查看我的原始答案。)

    • 添加货物文件,创建假文件 main.rs / lib.rs ,然后编译依赖项。然后移除假源并添加真实源。[缓存依赖项,但有几个假文件与工作区]。
    • 添加货物文件,创建假文件 主要的rs / lib。rs ,然后编译依赖项。然后用依赖项创建一个新层,并从那里继续。[与上文类似]。
    • 从外部装载缓存目录的卷。[缓存所有内容,依靠呼叫者传递 --mount ].
    • 使用 RUN --mount=type=cache,target=/the/path cargo build 在新Docker版本的Docker文件中。[缓存所有内容,似乎是一种不错的方式,但目前对我来说太新了。可执行文件不是图像的一部分]
    • 在另一个容器或主机上运行sccache,然后在构建过程中连接到该容器。看见 this comment 在货运问题2644中。
    • 使用 cargo-build-deps [可能对某些人有用,但不支持货运工作区(2019年)]。
    • 等待 Cargo issue 2644 [有人愿意将此添加到Cargo中,但还没有具体的解决方案]。
    • 使用 VOLUME ["/the/path"] 在Dockerfile里 不起作用 ,这仅适用于每层(每命令)。

    注:可以设置 CARGO_HOME ENV CARGO_TARGET_DIR 在Dockerfile中控制下载缓存和编译输出的位置。

    另请注意: cargo fetch 至少可以缓存依赖项的下载,尽管不是编译。

    Cargo工作区需要手动添加每个Cargo文件,对于某些解决方案,还需要生成十几个假文件 主要的rs / lib。rs .对于具有单个货物文件的项目,解决方案更有效。


    我必须通过添加

    ENV CARGO_HOME /code/dockerout/cargo
    ENV CARGO_TARGET_DIR /code/dockerout/target
    

    哪里 /code 是我装载代码的目录。

    这是外部安装的,不是从Dockerfile安装的。

    编辑1 :我不明白为什么会这样,但是@b.enoit。be和@BMitch澄清了这是因为Dockerfile中声明的卷只在一个层(一个命令)中有效。

        4
  •  2
  •   Jack Gore    6 年前

    您不需要使用显式Docker卷来缓存依赖项。Docker将自动缓存图像的不同“层”。基本上,Dockerfile中的每个命令都对应于图像的一层。你面临的问题取决于你如何 Docker 图像层缓存工作正常。

    Docker在图像层缓存中遵循的规则在官方文件中列出 documentation :

    • 从缓存中已有的父映像开始,下一个 将指令与从该指令派生的所有子图像进行比较 查看其中一个是否使用完全相同的 指示否则,缓存将失效。

    • 在大多数情况下,只需将Dockerfile中的指令与 其中一个子图像就足够了。然而,某些指示 需要更多的检查和解释。

    • 有关添加和复制说明,请参阅 检查图像并计算每个文件的校验和。这个 文件的上次修改和上次访问时间不确定 在这些校验和中考虑。在缓存查找过程中,校验和 与现有图像中的校验和进行比较。如果有的话 已在文件中更改,例如内容和元数据,然后 缓存无效。

    • 除了ADD和COPY命令外,缓存检查不会查看 用于确定缓存匹配的容器中的文件。例如 在处理RUN-apt get-y update命令时,文件会在 不检查容器以确定是否存在缓存命中。在里面 在这种情况下,只使用命令字符串本身来查找匹配项。

    缓存失效后,所有后续Dockerfile命令 生成新图像,但不使用缓存。

    所以问题在于命令的定位 COPY src/ ./src/ Dockerfile 。每当源文件发生更改时,缓存将失效,所有后续命令都不会使用缓存。所以你 cargo build 命令将不使用 码头工人 隐藏物

    要解决您的问题,只需在您的应用程序中重新排序命令即可 码头工人 文件,发送至:

    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 .

    希望这有帮助。

        5
  •  1
  •   masonk    5 年前

    随着BuildKit与docker的集成,如果您能够利用高级BuildKit后端,现在就可以 mount a cache volume during a RUN command 这已经成为缓存货物构建的最佳方式。缓存卷保留以前运行时写入的数据。

    要使用BuildKit,您将装载两个缓存卷,一个用于cargo目录,用于缓存外部板条箱源,另一个用于目标目录,用于缓存所有构建的工件,包括外部板条箱、项目箱和库。

    如果你的基本形象是 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仍然无法在不同的运行中保持相同的卷。

    实现货物构建缓存的其他选项(包括sccache和 cargo wharf 项目)中进行了描述 this github issue .

        6
  •  1
  •   dessalines    4 年前

    我用romac的叉子找到了如何在货物工作区也能使用它的方法 cargo-build-deps .

    这个例子有 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
    
        7
  •  0
  •   0b10011 Dragos.    5 年前

    我相信您可以调整此代码以用于Dockerfile,但我 wrote a dockerized drop-in replacement for cargo 可以保存到包中并作为 ./cargo build --release 很管用 用于(大多数)开发(使用) rust:latest ),但不是为CI或任何东西设置的。

    用法: ./cargo build , /货物建造-放行

    它将使用当前工作目录并将缓存保存到 ./.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 "$@"