.NET包在Docker中的恢复与构建分开缓存



如何构建.NET 5/C#应用程序的Docker映像,以便正确缓存还原的NuGet包所谓适当的缓存,我的意思是,当源(而不是项目文件)发生更改时,在docker build期间,包含已恢复包的层仍然从缓存中获取。

Docker的最佳做法是在添加完整源代码和构建应用程序之前执行包还原,因为这可以单独缓存还原,从而大大加快构建速度。我知道,从dotnet restoredotnet publish --no-restore,不仅packages目录,而且各个项目的binobj目录都必须保留,这样一切才能协同工作。我还知道,一旦缓存被破坏,接下来的所有层都会重新构建。

我的问题是,我无法想出只复制*.csproj的方法。如果我复制的不仅仅是*.csproj,那么源更改会破坏缓存。我可以将它们复制到docker build之外的一个位置,然后简单地在构建中复制它们,但我希望能够用一个相当简单的命令手动构建映像,即使是在管道之外。(这是一个不合理的要求吗?)

对于在一个非常标准的文件夹结构src/*/*.csproj中由多个项目组成的web应用程序,我提出了这样的尝试,试图弥补复制到图像中的过多文件(这仍然会破坏缓存):

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的行,但我不知道那是什么。显而易见的费力解决方案是为每个*.csproj都有一个单独的COPY行,但这感觉不太好,因为项目往往会被添加和删除,因此很难维护Dockerfile。我尝试过COPY src/*/*.csproj src/,然后修复扁平路径,这是我在谷歌上搜索过的一个技巧,但它对我不起作用,因为我的Docker只处理文件名中的通配符,并从字面上解释目录名,从而为不存在的src/*目录发出错误。我使用的是Docker Desktop 3.5.2(66501),它使用BuildKit后端构建图像,但如果有帮助,我愿意更改工具。

这让我对如何满足相对简单的一组需求一无所知。我的选择似乎已经用尽了我错过什么了吗?我是否必须接受一个折衷方案并放弃一些要求

目录名中不支持通配符很可能是BuildKit中缺少的功能。这个问题已经在moby/buildkit GitHub上被报道为#1900。

在问题解决之前,如果您不需要BuildKit的任何功能,请禁用它。任一

  1. 将环境变量DOCKER_BUILDKIT设置为零(0),或
  2. 编辑Docker守护进程配置;buildkit";特性设置为false并重新启动守护进程

在Docker Desktop中,可以在设置中轻松访问配置>Docker引擎。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期,这期杂志对这个话题进行了有趣的讨论。

还有一个用于路径修复的网络工具,但我还没有测试过

不需要禁用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"]

源环境中的COPY src/ src/层因源更改而无效,但缓存无效对每个阶段都单独起作用。由于复制到构建环境中的文件在不同的构建中是相同的,因此COPY --from=projects-env缓存不会失效,所以RUN dotnet restore层也会从缓存中取出。

我怀疑还有其他使用BuildKit挂载(RUN --mount=...)的解决方案,但我还没有测试过。

以下是解决问题的另一种方法。

首先,复制.sln.csproj文件(取决于解决方案文件夹结构)

COPY *.sln ./
COPY **/*.csproj ./
COPY **/**/**/*.csproj ./

之后运行以下脚本:

RUN dotnet sln list | grep ".csproj" 
| while read -r line; do  
mkdir -p $(dirname $line); 
mv $(basename $line) $(dirname $line); 
done;

脚本只是将.csproj文件移动到它们在主机文件系统中的相同位置。

有一个更现代的版本可以使用buildkit,不依赖于文件夹结构和文件名是否相同,也不依赖于dotnet sln list的输出。它确实取决于基础映像中可用的find命令,但对于任何安装了dotnet SDK的东西,这应该没有问题。

RUN --mount=type=bind,target=/docker-context 
cd /docker-context/; 
find ./ -mindepth 0 -maxdepth 4 ( -name "*.sln" -o -name "*.csproj" -o -iname "nuget.config" ) -exec cp --parents "{}" / ;

它的作用分解:

  1. RUN:显然运行了一个命令
  2. --mount=type=bind,target=/docker-context:将当前构建目录(即在运行构建的机器上)装载到/docker-context的Docker容器中
  3. cd /docker-context/:更改docker容器中的装载目录
  4. find ./ -mindepth 0 -maxdepth 4在该目录中搜索(具有最小/最大子目录深度,根据您自己的要求进行调整)
  5. ( -name "*.sln" -o -name "*.csproj" -o -iname "nuget.config" ):告诉find命令匹配任何类似*.sln*.csprojnuget.config的内容(使用-iname进行不区分大小写的匹配,因为NuGet.config的大小写通常不同)
    我特别喜欢包含sln和nuget文件,因为它们可能会对以后的包恢复和构建YMMV产生影响
  6. -exec cp --parents "{}" /将项及其所有父项(即keep结构)复制到/,其中/是docker环境中的当前工作目录

这一行足以使用通配符复制所有项目,同时保留目录结构。

根据@Joost的回答。谢谢

最新更新