微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决Docker 中的 .NET 包还原与构建分开缓存

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

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

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

对于由非常标准的文件夹结构 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 的东西,但我不知道那是什么。 明显费力的解决方是为每个 *.csproj 设置一个单独的 copY 行,但这感觉不对,因为项目往往会被添加删除,从而使 Dockerfile 难以维护。我试过 copY src/*/*.csproj src/ 然后修复扁平路径,这是我用谷歌搜索一个技巧,但它对我不起作用,因为我的 Docker 只处理文件名中的通配符并逐字解释目录名,发出错误不存在的 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 引擎中轻松访问配置。默认情况下首次启用 BuildKit 的 Docker Desktop 3.2.0 release notes 推荐使用这种关闭功能的方法。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。