docker - Docker 中的 .NET 包还原与构建分开缓存

如何构建 .NET 5/C# 应用程序的 Docker 镜像,以便正确缓存恢复的 NuGet 包? 我所说的正确缓存是指当源(但不是项目文件)是更改后,包含恢复包的层仍然在 docker build 期间从缓存中获取。

Docker 中的最佳做法是在添加完整源和构建应用程序本身之前执行包恢复,因为它可以单独缓存恢复,从而显着加快构建速度。我知道不仅是packages目录,还有个别项目的binobj目录都要从dotnet restore中保存dotnet publish --no-restore 以便一切都协同工作。我也知道,一旦缓存被破坏,所有后续层都会重新构建。

我的问题是我无法想出一种方法来仅复制 *.csproj。如果我复制的不仅仅是 *.csproj,源更改会破坏缓存。我可以将它们复制到 docker build 之外的一个地方,然后在构建中简单地复制它们,但我希望能够手动甚至在管道之外构建图像,使用一个相当简单的命令。 (这是一个不合理的要求吗?)

对于在非常标准的文件夹结构 src/*/*.csproj 中包含多个项目的 Web 应用程序,我想出了这个尝试来补偿被复制到的太多文件图像(仍然破坏缓存):

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /src
COPY NuGet.Config NuGet.Config
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
    && find -depth -type d -empty -exec rmdir '{}' \;
RUN dotnet restore src/Company.Product.Component.App/Company.Product.Component.App.csproj
COPY src/ src/
RUN dotnet publish src/Company.Product.Component.App/Company.Product.Component.App.csproj -c Release --no-restore -o /out

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS run-env
WORKDIR /app
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "Company.Product.Component.App.dll"]

我还尝试在还原后将构建环境阶段一分为二,将/root/.nuget/packages 和/src 复制到构建阶段,但这也无济于事。

第一个 RUN 行和紧接在前的行应该替换为仅复制 *.csproj 的内容,但我不知道那是什么。 明显费力的解决方案是为每个 *.csproj 设置一个单独的 COPY 行,但这感觉不对,因为项目往往会被添加和删除,因此它使 Dockerfile难以维护。我试过 COPY src/*/*.csproj src/ 然后修复扁平路径,这是我用谷歌搜索的一个技巧,但它对我不起作用,因为我的 Docker 处理通配 rune 件名并从字面上解释目录名,为不存在的 src/* 目录发出错误。我正在使用 Docker Desktop 3.5.2 (66501),它使用 BuildKit 后端构建图像,但如果有帮助,我愿意更改工具。

这让我对如何满足相对简单的一组要求一无所知。我的选择似乎用尽了。 我错过了什么吗?我是否必须接受权衡并放弃我的一些要求?

最佳答案

不支持目录名称中的通配符可能是 BuildKit 中缺少的功能。该问题已在 moby/buildkit GitHub as #1900 上报告.

直到问题得到解决,disable BuildKit如果您不需要它的任何功能。要么

  1. 将环境变量 DOCKER_BUILDKIT 设置为零 (0),或者
  2. 编辑 Docker 守护进程配置,将“buildkit”功能设置为 false,然后重新启动守护进程。

在 Docker Desktop 中,可以在“设置”>“Docker 引擎”中轻松访问配置。 Docker Desktop 3.2.0 release notes 推荐这种关闭功能的方法默认情况下首先启用 BuildKit。

禁用 BuildKit 后,替换

COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
    && find -depth -type d -empty -exec rmdir '{}' \;

COPY src/*/*.csproj src/
RUN for from in src/*.csproj; do to=$(echo "$from" | sed 's/\/\([^/]*\)\.csproj$/\/\1&/') \
    && mkdir -p "$(dirname "$to")" && mv "$from" "$to"; done

COPY 将在不破坏缓存的情况下成功,RUN 将修复路径。它依赖于项目位于“src”目录中的事实,每个项目都位于与项目文件同名的单独目录中。

这基本上是VonC's answer to a related question底部的解决方案.答案还提到Moby issue #15858 ,其中对该主题进行了有趣的讨论。

有一个dotnet tool for the paths fixup也是,不过我没测试过。

不需要禁用 BuildKit 的替代解决方案是 split the original stage in two在清理复制的文件之后,即在恢复之前(而不是之后!)。

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS projects-env
WORKDIR /src
COPY NuGet.Config NuGet.Config
COPY src/ src/
RUN find . -name NuGet.Config -prune -o \! -type d \! -name \*.csproj -exec rm -f '{}' + \
    && find . -depth -type d -empty -exec rmdir '{}' \;

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /src
COPY --from=projects-env /src /src
RUN dotnet restore src/Company.Product.Component.App/Company.Product.Component.App.csproj
COPY src/ src/
RUN dotnet publish src/Company.Product.Component.App/Company.Product.Component.App.csproj -c Release --no-restore -o /out

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS run-env
WORKDIR /app
COPY --from=build-env /out .
ENTRYPOINT ["dotnet", "Company.Product.Component.App.dll"]

sources-env 中的 COPY src/src/ 层因源更改而失效,但缓存失效针对每个阶段单独进行。由于复制到 build-env 的文件在构建中是相同的,COPY --from=projects-env 缓存不会失效,因此 RUN dotnet restore 层也是从缓存中获取的。

我怀疑还有其他解决方案使用 BuildKit mounts (RUN --mount=...),但我还没有测试过。

https://stackoverflow.com/questions/68597982/

相关文章:

r - 如何过滤列中包含少于 3 个空格的行? (右)

python - 遍历 for 循环并将检索到的数据保存在每个循环的唯一 csv 文件中 | Pyt

python - 在列表和元组列表之间查找公共(public) id

google-app-engine - F4_1G 是否属于 google app engine f

c++ - 来自具有大小的 char * 缓冲区的双向迭代器

c++ - 如何将 C 类型可变参数转换为 C++ 样式可变参数类型?

c# - 为什么要使用通用类型然后将其限制为具有 "where"的一种类型?

clojure - 如何在 Clojure 中链接函数调用?

vim - 在每个数字中插入一个减号

rust - 是否可以在不复制数据的情况下将 Vec<&[u8]> 展平为 &[u8] ?