docker - Docker 中的 .NET 包还原与构建分开缓存
问题描述
如何构建 .NET 5/C# 应用程序的 Docker 映像,以便正确缓存恢复的 NuGet 包?通过适当的缓存,我的意思是当源(但不是项目文件)发生更改时,包含恢复包的层仍然在docker build
.
在 Docker 中的最佳实践是在添加完整源和构建应用程序之前执行包恢复,因为它可以单独缓存恢复,从而显着加快构建速度。我知道不仅packages
目录,而且各个项目的目录bin
和obj
目录都必须保留,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 后端来构建图像,但如果有帮助,我愿意更改工具。
这让我对如何满足我的一组相对简单的要求一无所知。我的选择似乎已经用尽了。我错过了什么吗?我必须接受权衡并放弃我的一些要求吗?
解决方案
目录名称中缺乏对通配符的支持可能是BuildKit 中缺少的功能。该问题已在moby/buildkit GitHub 上报告为 #1900。
在问题得到解决之前,如果您不需要BuildKit 的任何功能,请禁用它。任何一个
- 将环境变量 DOCKER_BUILDKIT 设置为零 (
0
),或 - 编辑 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=...
) 的解决方案,但我还没有测试过。
推荐阅读
- android - 如何在Android中将mutableMap添加到拦截器
- javascript - 在服务器端渲染的上下文中,“渲染”是什么意思?
- r - 使用 ggplot2 在堆积条形图上的透明层
- datetime - Netsuite 中的 FTL 日期格式
- maven - 如何在 Maven 中找到工件版本的来源?
- reactjs - 抽屉导航器 React Native 中的条件渲染
- powershell - 将bat文件转换为powershell脚本
- python - 具有多列的 Pandas 数据框条件验证
- javascript - 尝试使用 PHP 和 JS 向自己提交 HTML 表单,但它不起作用
- excel - 是否可以使用创建的函数从另一张表中获取特定数据?