如何构建.NET 5/C#应用程序的Docker映像,以便正确缓存还原的NuGet包所谓适当的缓存,我的意思是,当源(而不是项目文件)发生更改时,在docker build
期间,包含已恢复包的层仍然从缓存中获取。
Docker的最佳做法是在添加完整源代码和构建应用程序之前执行包还原,因为这可以单独缓存还原,从而大大加快构建速度。我知道,从dotnet restore
到dotnet publish --no-restore
,不仅packages
目录,而且各个项目的bin
和obj
目录都必须保留,这样一切才能协同工作。我还知道,一旦缓存被破坏,接下来的所有层都会重新构建。
我的问题是,我无法想出只复制*.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的任何功能,请禁用它。任一
- 将环境变量DOCKER_BUILDKIT设置为零(
0
),或 - 编辑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 "{}" / ;
它的作用分解:
RUN
:显然运行了一个命令--mount=type=bind,target=/docker-context
:将当前构建目录(即在运行构建的机器上)装载到/docker-context
的Docker容器中cd /docker-context/
:更改docker容器中的装载目录find ./ -mindepth 0 -maxdepth 4
在该目录中搜索(具有最小/最大子目录深度,根据您自己的要求进行调整)( -name "*.sln" -o -name "*.csproj" -o -iname "nuget.config" )
:告诉find
命令匹配任何类似*.sln
、*.csproj
或nuget.config
的内容(使用-iname
进行不区分大小写的匹配,因为NuGet.config
的大小写通常不同)
我特别喜欢包含sln和nuget文件,因为它们可能会对以后的包恢复和构建YMMV产生影响-exec cp --parents "{}" /
将项及其所有父项(即keep结构)复制到/
,其中/
是docker环境中的当前工作目录
这一行足以使用通配符复制所有项目,同时保留目录结构。
根据@Joost的回答。谢谢