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

问题描述 投票:0回答:2

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

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

命令:

docker compose up -d --build

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

=> 错误 [sampleapp_api build-env 16/30] RUN --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

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

还有其他建议吗?

.net docker nuget
2个回答
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

0
投票

为了避免竞争条件,我们需要缓存 nuget 使用的所有文件夹。要找出 nuget 正在使用哪些文件夹,请运行

dotnet nuget locals all --list
。在 Linux Docker 容器中,运行
docker run -it mcr.microsoft.com/dotnet/sdk:6.0.414-jammy dotnet nuget locals all --list
时,您将得到以下结果:

http-cache: /root/.local/share/NuGet/http-cache
global-packages: /root/.nuget/packages/
temp: /tmp/NuGetScratchroot
plugins-cache: /root/.local/share/NuGet/plugin-cache

这些是需要缓存的路径。

在我的构建脚本中,我以防御性的方式解决了这个问题。我使用 ENV 设置了控制上述路径的环境变量。我们还挂载整个 /root/.local/NuGet 文件夹。最终结果如下所示:

ENV NUGET_PACKAGES=/root/.nuget/packages
ENV NUGET_HTTP_CACHE_PATH=/root/.local/share/NuGet/v3-cache
ENV NUGET_SCRATCH=/tmp/NuGetScratchroot
ENV NUGET_PLUGINS_CACHE_PATH=/root/.local/share/NuGet/plugins-cache
RUN \
    --mount=type=cache,target=/root/.nuget/packages                  \
    --mount=type=cache,target=/root/.local/share/NuGet               \
    --mount=type=cache,target=/tmp/NuGetScratch                      \
    dotnet restore
© www.soinside.com 2019 - 2024. All rights reserved.