了解多阶段 DockerFile 构建

Posted

技术标签:

【中文标题】了解多阶段 DockerFile 构建【英文标题】:Understanding multi stage DockerFile builds 【发布时间】:2020-12-19 18:15:42 【问题描述】:

我是 Docker 新手。我的项目有以下目录结构

docker-compose.yml
|-services
  |-orders
    |-DockerFile

我正在使用具有以下内容的标准 ASP.Net Core DockerFile:

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["Services/Orders/Orders.csproj", "Services/Orders/"]
RUN dotnet restore "Services/Orders/Orders.csproj"
COPY . .
WORKDIR "/src/Services/Orders"
RUN dotnet build "Orders.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Orders.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Orders.dll"]

我的 docker-compose.yml 文件有

# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP .NET Core service.
version: "3.4"

services:
  orders-api:
    image: orders-api
    build:
      context: .
      dockerfile: Services/Orders/Dockerfile
    ports:
      - "2222:80"

我对这两个文件有些混淆

问题一:WORKDIR /app 在第 2 行有什么用?

我的理解是,我们使用的是可以扩展的基础镜像 当我们在第 1 行导入基础图像然后设置 第 2 行和第 3 行中的 WORKDIR 和端口,它们将被以下命令使用 使用这张图片?

问题 2:为什么我们为 SDK 映像设置 WORKDIR /src 而不是 WORKDIR /app

问题 3:复制命令中的路径是否与 Dockerfile 或 Docker-compose.yml 文件相关?

在 COPY ["Services/Orders/Orders.csproj", "Services/Orders/"] 行中,我们指定的路径似乎与 docker-compose.yml 文件相关,而不是与 DockerFile 相关进一步嵌套在文件夹中。这是否意味着 Dockerfile 中的路径需要与 docker-compose.yml 相关?我问这个是因为我在想如果我使用这个 Dockerfile 运行 docker build 命令,那么我会收到一个错误,因为复制命令中的路径无效。

【问题讨论】:

【参考方案1】:

问题一:WORKDIR /app 在第 2 行有什么用?

这定义了您当前的工作目录。默认情况下,以下命令将从该位置运行,并且相对路径将从那里开始。

具有以下文件结构的示例:

/app/
  - README.md
  - folder1/
       - some.txt
WORKDIR /app
RUN ls

将打印

folder1
README.md

WORKDIR /app/folder1
RUN ls

将打印

some.txt

问题 2:为什么我们为 SDK 映像设置 WORKDIR /src 而不是 WORKDIR /app?

查看问题1的答案。下面的COPYdotnet restore命令在/src源目录下执行。 (构建是从/src 目录完成的,在/app/publish 中创建的工件稍后会在最后阶段复制到/app 目录,以从该/app 目录执行。)

问题 3:复制命令中的路径是否与 Dockerfile 或 Docker-compose.yml 文件相关?

复制有两条路径。第一个引用源(构建 docker 映像的上下文中的文件或文件夹),第二个引用目标(生成的 docker 映像中的文件顺序文件夹。)因此这些路径通常仅特定于 Dockerfile并且独立于您的 docker-compose 项目。

但是,在您的情况下,您的 docker 映像构建的上下文在此处的 docker-compose.yml 中定义:

build:
      context: .
      dockerfile: Services/Orders/Dockerfile

因此,您的 docker 镜像构建的上下文似乎是您的 docker-compose.yml 所在的目录。如果您只是在该文件夹中运行docker build -f Services/Orders/Dockerfile .,您可以构建相同的图像。 (它不是 docker-compose 特定的)

因此,您应该从您的 docker-compose.yml 所在的目录开始在 ./Services/Orders/ 中找到 Orders.csproj。然后,在您的第二个构建阶段将该文件复制到 /src/Services/Orders/Orders.csproj。 (/src 可以在 COPY 语句中提交,因为它是从您当前工作目录开始的相对路径,在上面的行中定义。 - 请参阅问题 1)

【讨论】:

【参考方案2】:

对于稍后来到此主题并面临类似问题的任何人,我将根据 Leo 的回答和其他来源分享我迄今为止所学到的知识。

Docker 有一个名为 multi-stage 构建的功能。这允许我们创建多个层并使用它们。

可以使用不同的图像构建 ASP.Net Core 应用程序。 SDK 映像较大,但为我们提供了在开发环境中构建和调试代码的额外工具。

但是,在生产中,我们不需要 SDK。我们只想利用 PROD 中的轻量级运行时。我们可以利用 Docker 的多阶段构建功能来创建我们的最终容器并使其保持轻量级。

来到 Dockerfile…

“From” 关键字开头的每个部分都定义了一个层。在我原来的 Dockerfile 中,你可以看到我正在创建 4 层 base、build、publishfinal。我们可以命名一个层,然后使用该名称在第一个层的基础上创建一个新层。

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80

上面的代码将基于包含轻量级运行时的 aspnet:3.1 图像创建一个名为 “base” 的层,并将用于托管我们的最终应用程序。 然后我们将工作目录设置为 /app 文件夹,这样当我们以后使用该层时,我们的命令如 COPY 将在该文件夹中运行。 接下来,我们只公开端口 80,这是 Web 服务器的默认端口。

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["Services/Orders/Orders.csproj", "Services/Orders/"]
RUN dotnet restore "Services/Orders/Orders.csproj"
COPY . .
WORKDIR "/src/Services/Orders"
RUN dotnet build "Orders.csproj" -c Release -o /app/build

接下来,我们下载用于构建应用程序的 SDK 映像并调用该层 “build”。 我们设置了以下 "COPY" 命令将使用的工作目录。

Copy 命令将文件从本地文件系统复制到容器中的指定位置。所以基本上我将我的 Orders.csproj 文件复制到容器中,然后运行 ​​"dotnet restore" 来恢复该项目所需的所有 Nuget 包。

仅复制 .csproj.sln 文件,然后在不复制整个代码的情况下恢复 NuGet 包效率更高,因为它利用了 here 中提到的缓存,并且是一种我不知道的被广泛采用的做法,我想知道为什么我们不能简单地复制所有内容并运行 "dotnet restore" 命令?

一旦我们恢复了 NuGet 包,我们就可以复制所有文件,然后运行 ​​"dotnet build" 命令来构建我们的项目。

FROM build AS publish
RUN dotnet publish "Orders.csproj" -c Release -o /app/publish

Nest,我们在之前的层 "build" 的基础上创建了一个名为 "publish" 的新层,并在 " 中使用发布配置简单地发布我们的项目app/publish" 目录。

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Orders.dll"]

最后一层使用 "base"(我们的轻量级运行时映像)。将文件从 publish 层的 /app/publish 位置复制到设置的工作目录,然后设置应用程序的入口点。

所以我们使用了 2 个单独的图像。 SDK 用于构建我们的应用程序,aspnet 映像用于在容器中托管我们的应用程序,但这里要注意的重要一点是,将生成的最终容器将仅包含aspnet 运行时,体积会更小。

所有这些东西都记录在案并且可以搜索,但我很挣扎,因为信息分散。希望它可以为将来接触 ASP.Net Core 和 Docker 的任何人节省一些时间。

【讨论】:

很好的解释 - 谢谢。问题:为什么在构建过程中创建了一个新的发布层,我们不能继续使用构建层吗?为什么要在文件顶部创建基础层,尽管它只在最后一步使用?【参考方案3】:

我们使用“多阶段”构建并使用多个图像和复制文件来继续而不是仅按顺序执行步骤的原因主要是由于试图保持图像大小较小。尽管磁盘空间可能足够,但以下因素也可能是相关的:

    构建/部署时间。图像越大意味着持续集成的时间越长,等待这些操作完成的时间就越长。 启动时间。在生产环境中运行您的应用时,下载所需的时间越长,新容器启动并运行的时间就越长。

因此,使用下面的多阶段方法,我们可以省略更大的 sdk,只需要构建不运行的应用程序:

请注意,我们在第二张图片中使用了 ENTRYPOINT,因为“dotnet run”仅在 sdk 中可用,因此我们稍作调整,直接运行 .dll 以获得相同的结果。

【讨论】:

以上是关于了解多阶段 DockerFile 构建的主要内容,如果未能解决你的问题,请参考以下文章

谈谈 Docker 镜像多阶段构建

Docker多阶段镜像构建Dockerfile脚本示例:构建nodejs前端项目

Docker多阶段构建最佳实践

使用 Docker 开发 - 使用多阶段构建镜像

dockerfile 多FROM

dockerfile多阶段构建