使用 dotnet Restore 进行并行 Docker 构建会破坏彼此的缓存

Cod*_*ter 7 .net nuget docker

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

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

--mount=type=cache,id=nuget,target=/root/.nuget/packages然后在每条指令之前使用RUN,以便在构建之间在主机上拥有可重用的 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"]
Run Code Online (Sandbox Code Playgroud)

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

命令:

docker compose up -d --build
Run Code Online (Sandbox Code Playgroud)

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

=> 错误 [sampleapp_api build-env 16/30] 运行 --mount=type=cache,id=nuget,target=/root/.nuget/packages dotnet Restore SampleApp.Api --runtime 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):错误:找不到文件'/root/.nuget/packages/runtime.any.system.runtime.interopservices/4.1.0 /q0z4zwua.tf3 /usr/share/dotnet/sdk/6.0.302/NuGet.targets(130,5):错误:找不到文件“/root/.nuget/packages/runtime.any.system.runtime.interopservices /4.1.0/ xpvko1tv.2o3

可能是因为第三个项目正在那个时间恢复该库。这是因为多个构建使用相同的 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. 运行构建几次。是的,但没有。

还有其他建议吗?

Mor*_*sen 8

在与这个确切的问题斗争了很长时间之后,我终于弄清楚为什么这不起作用以及如何解决它。

此评论引导我找到解决方案: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
Run Code Online (Sandbox Code Playgroud)