具有 dotnet restore 的并行 Docker 构建会破坏彼此的缓存



tl;dr:是否可以在同一个NuGet缓存上同时构建多个.NET应用程序?

我有一个包含多个库和多个应用程序的Visual Studio解决方案,这些库和应用程序将托管在Docker中。我希望我的构建尽可能快,所以我需要尽可能多的缓存。这可以通过两种方式实现:首先,只将必要的.csproj文件复制到它们各自的目录中,只要我不编辑任何.csproj.就可以缓存该层。不能对该副本使用通配符,因为下一步我们需要BuildKit。

然后在每个RUN指令之前使用--mount=type=cache,id=nuget,target=/root/.nuget/packages,以便在构建之间在主机上拥有可重复使用的NuGet缓存:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
# Copy all required (transitive) project references
COPY src/SampleApp.Common/SampleApp.Common.csproj ./SampleApp.Common/SampleApp.Common.csproj
COPY src/SampleApp.Data/SampleApp.Data.csproj ./SampleApp.Data/SampleApp.Data.csproj
# Then copy ourselves
COPY src/SampleApp.Api/SampleApp.Api.csproj ./SampleApp.Api/SampleApp.Api.csproj
# Restore as distinct layers
# Mount NuGet cache dir as cache in Docker for faster subsequent builds.
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages 
dotnet restore SampleApp.Api --runtime linux-x64
# Copy all required (transitive) project sources
COPY src/SampleApp.Common ./SampleApp.Common
COPY src/SampleApp.Data ./SampleApp.Data
# Then copy ourselves
COPY src/SampleApp.Api SampleApp.Api
# Build and publish the release (needs cached packages to copy on build)
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages 
dotnet publish SampleApp.Api 
--no-restore 
--runtime linux-x64 
--self-contained false 
--configuration Release 
--output ./Publish/SampleApp.Api/
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /app/Publish/SampleApp.Api .
ENTRYPOINT ["dotnet", "SampleApp.Api.dll"]

现在这是可行的,尽管为更大的项目维护Dockerfile变得很麻烦(添加一个新的库项目需要编辑多个Dockerfile中的多行(,但当需要同时构建多个项目时会出现问题。

命令:

docker compose up -d --build

有多个像这样的Dockerfiles项目,一段时间后显示:

=>错误[sampleapp_api build env 16/30]RUN--mount=type=cache,id=nuget,target=/root/.nuget/packages dotnet restore sampleapp.api--运行时linux-x64

#12 34.56/usr/share/dotnet/sdk/6.0.302/NuGet.targets(130,5(:错误:找不到文件"/root/.NuGet/packages/microsoft.aspnetcore.app.runtime.linux-x64/6.0.7/fgp1y1vi.2tu'。[/app/SampleApp.Api/SampleApp.Api.csproj]

第二次运行时,两个项目同时失败:

/usr/share/dotnet/sdk/6.0.302/NuGet.targets(130,5>

可能是因为第三个项目正在恢复该库。这是因为多个构建使用相同的NuGet缓存:

--mount=type=cache,id=nuget,target=/root/.nuget/packages

由于NuGet在每个构建中都使用随机文件名提取包的内容,从而清除已经存在的内容,因此在并行构建中,一个构建会清除另一个构建的NuGet缓存,从而导致上述构建(还原(错误。

现在有几个变通办法,我都不喜欢:

  1. 让每个项目都有自己的NuGet缓存(--mount=type=cache,id=nuget-sampleapp-api...,id=nuget-sampleapp-web,…(。这会破坏磁盘上的缓存,一些Docker构建会很高兴地吞噬一些GB。NVMe空间非常昂贵
  2. 遇到此错误时,请逐个手动构建项目映像(docker compose build sampleapp_api... sampleapp_web…(。每次更新共享项目时,我都必须为每个应用程序映像执行此操作。或者编写脚本。更多的脚本,更多的维护,更多的非标准构建步骤,都不喜欢它。而且它违背了并行构建的目的
  3. 运行构建几次。是的,但不是

还有其他建议吗?

在解决这个确切的问题很长一段时间后,我终于明白了为什么这个问题不起作用以及如何解决它

这条评论让我找到了解决方案:https://github.com/NuGet/Home/issues/7060#issuecomment-732065148

因此,如果有并发恢复操作正在运行,这些操作使用不同的临时路径,但它们共享相同的全局包缓存,则缓存并没有真正受到锁定机制的保护

NuGet使用一个临时文件夹(/tmp/NuGetScratch(,您可以在此处阅读该文件夹:https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders将这个临时目录与全局包缓存一起安装可以修复这个问题。

确切的问题似乎是,NuGet使用临时目录作为恢复进程之间的锁定机制,安装临时目录可以在从Docker并行恢复时工作。

在这一点上,我只是像这样装载所有的NuGet缓存文件夹,当做NuGet恢复:

RUN 
--mount=type=cache,target=/root/.nuget/packages 
--mount=type=cache,target=/root/.local/share/NuGet/v3-cache 
--mount=type=cache,target=/root/.local/share/NuGet/plugins-cache 
--mount=type=cache,target=/tmp/NuGetScratch 
dotnet restore

最新更新