Docker Compose并发构建.NET应用时dotnet restore缓存冲突问题
我有两个独立的Docker Compose文件,用于构建.NET应用。对应的Dockerfile包含以下指令片段:
RUN --mount=type=cache,sharing=locked,id=nuget,target=/root/.nuget/packages \ dotnet restore # 其他构建步骤 RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \ dotnet publish "MyApp.csproj" -c:Release -o:/app/publish --no-restore
按顺序启动Compose项目一切正常;缓存已存在时并发启动也没问题。但执行docker builder prune -a -f清空所有缓存后并发启动,其中一个或两个项目会在发布阶段因缺失包失败,错误示例:
/usr/share/dotnet/sdk/8.0.407/Sdks/Microsoft.NET.Sdk/targets/Microsoft.PackageDependencyResolution.targets(266,5): error NETSDK1064: Package Microsoft.CodeAnalysis.Analyzers, version 3.3.3 was not found. It might have been deleted since NuGet restore. Otherwise, NuGet restore might have only partially completed, which might have been due to maximum path length restrictions. [/src/MyApp.csproj]
注意restore步骤始终成功,仅发布阶段失败,我怀疑是并发执行restore导致的问题。
我试过带或不带sharing=locked的挂载方式,还参考过方案额外缓存/tmp/NuGetScratchroot等目录,但偶尔成功,多数情况还是报错。
补充上下文:给restore命令添加了--verbosity detailed,相关包的执行序列如下(无法区分具体Compose项目,本次运行中第一个构建的应用失败,第二个成功):
#9 9.866 GET https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/index.json #9 10.90 OK https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/index.json 1032ms #9 10.90 GET https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/3.3.3/microsoft.codeanalysis.analyzers.3.3.3.nupkg #9 11.26 OK https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/3.3.3/microsoft.codeanalysis.analyzers.3.3.3.nupkg 353ms #9 22.69 Acquiring lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.3.3 #9 22.69 Acquired lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.3.3 #9 23.06 PackageSignatureVerificationLog: PackageIdentity: Microsoft.CodeAnalysis.Analyzers.3.3.3 Source: https://api.nuget.org/v3/index.json PackageSignatureValidity: True #9 23.14 Installed Microsoft.CodeAnalysis.Analyzers 3.3.3 from https://api.nuget.org/v3/index.json to /root/.nuget/packages/microsoft.codeanalysis.analyzers/3.3.3 with content hash j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==. #18 2.565 /usr/share/dotnet/sdk/8.0.407/Sdks/Microsoft.NET.Sdk/targets/Microsoft.PackageDependencyResolution.targets(266,5): error NETSDK1064: Package Microsoft.CodeAnalysis.Analyzers, version 3.3.3 was not found. It might have been deleted since NuGet restore. Otherwise, NuGet restore might have only partially completed, which might have been due to maximum path length restrictions. [/src/MyApp/MyApp.csproj] #10 25.90 GET https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/index.json #18 2.565 /usr/share/dotnet/sdk/8.0.407/Sdks/Microsoft.NET.Sdk/targets/Microsoft.PackageDependencyResolution.targets(266,5): error NETSDK1064: Package Microsoft.CodeAnalysis.Analyzers, version 3.3.3 was not found. It might have been deleted since NuGet restore. Otherwise, NuGet restore might have only partially completed, which might have been due to maximum path length restrictions. [/src/MyLib/MyLib.csproj] #10 26.97 OK https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/index.json 1072ms #10 26.98 GET https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/3.11.0/microsoft.codeanalysis.analyzers.3.11.0.nupkg #10 27.25 OK https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/3.11.0/microsoft.codeanalysis.analyzers.3.11.0.nupkg 274ms #10 40.76 GET https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/3.3.4/microsoft.codeanalysis.analyzers.3.3.4.nupkg #10 40.77 OK https://api.nuget.org/v3-flatcontainer/microsoft.codeanalysis.analyzers/3.3.4/microsoft.codeanalysis.analyzers.3.3.4.nupkg 11ms #10 55.92 Acquiring lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.11.0 #10 55.92 Acquired lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.11.0 #10 56.05 PackageSignatureVerificationLog: PackageIdentity: Microsoft.CodeAnalysis.Analyzers.3.11.0 Source: https://api.nuget.org/v3/index.json PackageSignatureValidity: True #10 56.10 Acquiring lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.11.0 #10 56.38 Installed Microsoft.CodeAnalysis.Analyzers 3.11.0 from https://api.nuget.org/v3/index.json to /root/.nuget/packages/microsoft.codeanalysis.analyzers/3.11.0 with content hash v/EW3UE8/lbEYHoC2Qq7AR/DnmvpgdtAMndfQNmpuIMx/Mto8L5JnuCfdBYtgvalQOtfNCnxFejxuRrryvUTsg==. #10 56.40 Lock not required - Package already installed Microsoft.CodeAnalysis.Analyzers 3.11.0 #10 56.54 Acquiring lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.3.4 #10 56.57 Acquired lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.3.4 #10 56.57 Acquiring lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.3.4 #10 57.00 PackageSignatureVerificationLog: PackageIdentity: Microsoft.CodeAnalysis.Analyzers.3.3.4 Source: https://api.nuget.org/v3/index.json PackageSignatureValidity: True #10 57.03 Acquiring lock for the installation of Microsoft.CodeAnalysis.Analyzers 3.3.4 #10 57.16 Installed Microsoft.CodeAnalysis.Analyzers 3.3.4 from https://api.nuget.org/v3/index.json to /root/.nuget/packages/microsoft.codeanalysis.analyzers/3.3.4 with content hash AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==. #10 57.18 Lock not required - Package already installed Microsoft.CodeAnalysis.Analyzers 3.3.4 #10 57.22 Lock not required - Package already installed Microsoft.CodeAnalysis.Analyzers 3.3.4
从日志能看出,第一个构建任务(#9)已经成功安装了Microsoft.CodeAnalysis.Analyzers 3.3.3,但第二个构建任务(#10)在安装同包其他版本时,可能触发了NuGet缓存目录的元数据更新(比如registry.dat索引文件)。Docker的sharing=locked只能保证文件级互斥,但NuGet的索引更新是全局操作,并发场景下会出现索引不一致:第一个任务的restore阶段写入了包文件,但索引还没更新完成,就被第二个任务的索引操作覆盖,导致发布阶段读取索引时找不到对应包。
1. 发布阶段也启用sharing=locked
目前发布阶段的缓存挂载没有加锁,可能和其他任务的restore操作同时访问缓存目录。修改Dockerfile的发布指令:
RUN --mount=type=cache,sharing=locked,id=nuget,target=/root/.nuget/packages \ dotnet restore # 其他构建步骤 RUN --mount=type=cache,sharing=locked,id=nuget,target=/root/.nuget/packages \ dotnet publish "MyApp.csproj" -c:Release -o:/app/publish --no-restore
这样发布阶段也会锁定缓存目录,避免并发访问导致的索引不一致,改动最小且保留缓存共享的优势。
2. 为每个项目使用独立NuGet缓存
给两个项目设置唯一的缓存ID,完全隔离缓存目录,避免互相干扰:
# 项目A的Dockerfile RUN --mount=type=cache,sharing=locked,id=nuget-projectA,target=/root/.nuget/packages \ dotnet restore RUN --mount=type=cache,id=nuget-projectA,target=/root/.nuget/packages \ dotnet publish "MyApp.csproj" -c:Release -o:/app/publish --no-restore # 项目B的Dockerfile RUN --mount=type=cache,sharing=locked,id=nuget-projectB,target=/root/.nuget/packages \ dotnet restore RUN --mount=type=cache,id=nuget-projectB,target=/root/.nuget/packages \ dotnet publish "MyApp.csproj" -c:Release -o:/app/publish --no-restore
缺点是会重复下载相同包,占用更多磁盘空间,但能彻底解决并发冲突。
3. 限制Docker BuildKit并行构建数
通过环境变量强制两个项目顺序构建:
BUILDKIT_PARALLEL_LIMIT=1 docker-compose up --build
也可以在Docker daemon配置中全局设置,但会影响所有构建任务的并行性。
4. 提前预热NuGet缓存
在并发构建前手动执行一次restore预热缓存:
# 任选一个项目执行restore,缓存所需包 docker-compose -f docker-compose-projectA.yml build --target restore # 再并发启动两个项目 docker-compose -f docker-compose-projectA.yml up --build & docker-compose -f docker-compose-projectB.yml up --build &
缓存存在后,并发构建不会触发包下载和安装,也就不会有冲突。
内容的提问来源于stack exchange,提问作者Miral

