首页 > 解决方案 > Docker 中的 .NET 包还原与构建分开缓存

问题描述

如何构建 .NET 5/C# 应用程序的 Docker 映像,以便正确缓存恢复的 NuGet 包?通过适当的缓存,我的意思是当源(但不是项目文件)发生更改时,包含恢复包的层仍然在docker build.

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

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

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

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"]

我还尝试在恢复后将 build-env 阶段分成两部分,将 /root/.nuget/packages 和 /src 复制到构建阶段,但这也无济于事。

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

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

标签: docker.net-corenuget-package-restore

解决方案


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

在问题得到解决之前,如果您不需要BuildKit 的任何功能,请禁用它。任何一个

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

在 Docker Desktop 中,可以在 Settings > Docker Engine 中轻松访问配置。Docker Desktop 3.2.0 发行说明推荐了这种关闭功能的方法,其中 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 对相关问题的回答底部的解决方案。答案还提到了Moby 问题 #15858,该问题对此主题进行了有趣的讨论。

路径 fixup 也有一个dotnet 工具,但我还没有测试过。

不需要禁用 BuildKit的替代解决方案是在清理复制的文件后立即将原始阶段分成两部分,即在恢复之前(而不是之后!)。

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=...) 的解决方案,但我还没有测试过。


推荐阅读