ASP.NET 8改进多平台容器支持
原文信息
文章:Improving multi-platform container support
作者:Richard Lander(.NET 团队项目经理)
链接:https://devblogs.microsoft.com/dotnet/improving-multiplatform-container-support/
发布时间:2023年3月29日
Docker 使构建针对特定硬件体系结构(Arm 和 x64)的容器映像变得简单。如果您有 Arm64 M1/M2/M3 Apple Mac 并希望为 x64 云服务构建容器,这将特别有用。我们鼓励采用新模式为多个体系结构构建容器映像。它具有更好的性能,并且更好地与 BuildKit 构建模型 (opens new window)集成。这种方法还为使用交换机更好地优化多平台映像打开了大门。如果您的开发、CI 和生产硬件匹配(所有 x64 或所有 Arm64),则这些更改可能并不重要。如果您的硬件是 Arm64 和 x64 的混合,那么这些更改可能会带来可喜的改进。docker buildx build--platform
你什么时候需要这个?例如,在 Arm64 计算机上生成 x64 容器映像时。
docker build --platform linux/amd64 -t app .
此 Dockerfile (opens new window) 演示了我们采用的模式。
这些改进将包含在 .NET SDK 的 .NET 8 预览版 3 (#30762 (opens new window)) 和 7.0.300 (#31319 (opens new window)) 中。此新模式的各个方面适用于现有版本。
实际上有两种不同的场景在起作用,我称之为“多平台”。
- 为特定体系结构(不同于计算机)生成容器映像。
- 一次为多个体系结构构建多个容器映像。
我们将要研究的所有内容都适用于这两种情况。
# 多平台构建
让我们从 Docker 多平台模型 (opens new window)开始。我们假设用户使用的是 Apple Arm64 硬件。事实上,我是在MacBook Air M1笔记本电脑上编写这一切的,这将使我很容易演示这种行为。
Docker 有一个开关,可用于控制映像的输出。我将向您展示该系统如何使用Alpine工作,以使解释尽可能简单。--platform
下面是一个简单的 Dockerfile 来测试行为。该标签是一个多平台标签(稍后会详细介绍)。alpine
FROM alpine
让我们构建它并验证结果。同样,我在 Arm64 机器上。
% docker build -t image .
% docker inspect image -f "{{.Os}}/{{.Architecture}}"
linux/arm64
正如预期的那样,我们有一个 Arm64 映像。
我们可以使用开关以 x64 为目标。--platform
% docker build -t image --platform linux/amd64 .
% docker inspect image -f "{{.Os}}/{{.Architecture}}"
linux/amd64
现在我们有一个 x64 映像。如果我们运行图像,我们可以看到它是 x64 — 通过字符串 — 但由于图像(将被模拟 (opens new window))和隐式平台不匹配而出现警告。x86_64
% docker run --rm image uname -a
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Linux 188008b850d3 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 Linux
--platform
可用于删除该错误消息。
% docker run --rm --platform linux/amd64 image uname -a
Linux 40b8d36a90f8 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 Linux
开关有点微妙。我会解释的。--platform
第一个要点是始终设置,隐式设置为您的机器平台或显式指定的平台。--platform
更重要的一点是影响(A)多平台标签(如)和(B)最终映像的配置。--platformalpine
我们可以看到这是一个多平台标签,支持多种架构:alpine
% docker manifest inspect alpine | grep architecture
"architecture": "amd64",
"architecture": "arm",
"architecture": "arm",
"architecture": "arm64",
"architecture": "386",
"architecture": "ppc64le",
"architecture": "s390x",
平台值(隐式或显式指定)用于选择要拉取的特定于体系结构的映像。其中每一个都是一个不同的映像,具有自己单独编译的Alpine Linux副本,所有这些都是从标签中引用的。alpine
Alpine 团队还发布了特定于架构的图像,例如 arm64v8/alpine (opens new window)。
% docker manifest inspect -v arm64v8/alpine | grep architecture
"architecture": "arm64",
我们仍然可以使用此标记的开关,但会得到不正确的结果。--platform
% echo "FROM arm64v8/alpine" > Dockerfile
% docker build -t image .
% docker inspect image -f "{{.Os}}/{{.Architecture}}"
linux/arm64
% docker build -t image --platform linux/amd64 .
% docker inspect image -f "{{.Os}}/{{.Architecture}}"
linux/amd64
% docker run --rm -it image uname -a
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Linux d9abfaec07a1 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 Linux
如果仔细观察,您会发现此映像配置错误。它是一个图像,但标记为 。这不是很有帮助。aarch64linux/amd64
如果你在 bash 输出中错过了它,我们的 Dockerfile 现在如下所示。
FROM arm64v8/alpine
您可以安全正确地将标记与混合了多平台和特定于平台的标记的 Dockerfile 一起使用,但是,最后阶段必须引用多平台映像。在我们更新的 Dockerfile 中,我们有一个带有特定于平台的标记的语句,因此使用开关是用户错误,预计会导致错误图像。--platformFROM--platform
我们几乎完成了背景上下文。Docker 还定义了一堆值,如下面的 Dockerfile (opens new window) 所示。ARG
FROM alpine
ARG TARGETPLATFORM
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
ARG BUILDPLATFORM
ARG BUILDOS
ARG BUILDARCH
ARG BUILDVARIANT
RUN echo "Building on $BUILDPLATFORM, targeting $TARGETPLATFORM"
RUN echo "Building on ${BUILDOS} and ${BUILDARCH} with optional variant ${BUILDVARIANT}"
RUN echo "Targeting ${TARGETOS} and ${TARGETARCH} with optional variant ${TARGETVARIANT}"
我们可以构建针对 x64 的 Dockerfile(在 Arm64 上)。
% docker build -t image --platform linux/amd64 .
[+] Building 1.7s (8/8) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 428B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/alpine:latest 0.0s
=> [1/4] FROM docker.io/library/alpine 0.0s
=> [2/4] RUN echo "Building on linux/arm64, targeting linux/amd64" 0.2s
=> [3/4] RUN echo "Building on linux and arm64 with optional variant " 0.3s
=> [4/4] RUN echo "Targeting linux and amd64 with optional variant " 0.3s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:c70637a6942e07f3a00eb03c1c6bf1cd8a709e91cd627 0.0s
=> => naming to docker.io/library/image
请务必密切注意这些线条。RUN echo
在下一节中,当我们查看 .NET 解决方案时,这些环境变量将证明很有用。
# 为任何体系结构生成 .NET 映像
现在,我们将了解如何使用类似于我们刚刚在 Alpine 中看到的技术来构建 .NET 映像。
我们想要一些特征:
- 单个 Dockerfile 应该适用于多个体系结构。
- 结果应该是最佳的。
- SDK 应始终使用本机体系结构运行,因为它速度更快,也因为 .NET 不支持 QEMU (opens new window)。 我们将使用 Dockerfile.alpine-non-root (opens new window)。此 Dockerfile 已针对 .NET 8 进行了更新,既支持非根托管 (opens new window),也支持多平台目标。
这个 Dockerfile 中有几行做了一些特别的事情来帮助我们实现这些特征。
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/nightly/sdk:8.0-preview-alpine AS build
Dockerfile 格式允许为语句指定开关,并使用内置函数提供值。在这种情况下,我们说应该始终使用(又名本地机器架构)。在 Arm64 计算机上,这将始终是 Arm64,即使面向 x64。--platformFROMARG$BUILDPLATFORM
此模式实际上并不需要 .NET 8。它适用于任何 .NET 版本。事实上,它可以与任何多平台标签一起使用,例如 Node.js 或 Java。这只是一个Docker功能。
但是,我们在此处使用图像,以便我们可以利用接下来的功能。该功能需要在 .NET 8 中进行两项不同的更改。发布预览版 3 后,将不需要映像。nightly-a $TARGETARCHnightly
RUN dotnet restore -a $TARGETARCH
# copy everything else and build app
COPY aspnetapp/. .
RUN dotnet publish -a $TARGETARCH --no-restore -o /app
我们现在使用另一个内置参数还原和发布目标体系结构的应用。
在幕后,SDK 正在创建一个运行时 ID (RID)。这与指定参数相同。该参数是一条捷径。建议在容器中将应用发布为特定于 RID (opens new window)。某些 NuGet 包包含本机依赖项的多个副本。通过将应用发布为 RID 特定 (opens new window),你将只获得这些依赖项的一个副本(减小容器大小),与目标环境匹配。-r-a
如果要构建 Alpine 映像并使用此模式,请确保使用基于 Alpine 的 SDK,以便在用于指定体系结构时推断出该 SDK。有关详细信息,请阅读 dotnet/sdk #30369 (opens new window)。linux-musl-a
您可能习惯于在 Dockerfiles 中看到。我们已将其设为 .NET 8 的默认设置,使其成为可选。我们一直在努力改进 .NET SDK 默认值。-c Releasedotnet publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview-alpine
第二个(也是最后一个)语句是多平台标记。它将受到交换机(从 )的影响。这就是我们想要的。FROM--platformdocker build
让我们尝试一下。
% pwd
/Users/rich/git/dotnet-docker/samples/aspnetapp
% docker build --pull -t aspnetapp -f Dockerfile.alpine-non-root .
% docker inspect aspnetapp -f "{{.Os}}/{{.Architecture}}"
linux/arm64
我们看到一个 Arm64 图像。
让我们尝试以 x64 为目标。
% docker build --pull -t aspnetapp -f Dockerfile.alpine-non-root --platform linux/amd64 .
% docker inspect aspnetapp -f "{{.Os}}/{{.Architecture}}"
linux/amd64
% docker run --rm --platform linux/amd64 --entrypoint ash aspnetapp -c "uname -a"
Linux c61047789bbd 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 Linux
这看起来很棒。我们有一个 x64 映像。
这些更改首先显示在 .NET 8 预览版 3 中,但你现在可以使用我们的夜间存储库进行尝试(上面列出了声明)。你可以尝试用于 .NET 8 应用,也可以尝试 .NET 6 和 7 应用。.NET 8 SDK 可以使用相同的方法为这些早期版本生成应用。如果使用适用于 .NET 8 应用的内部版本,则需要添加 .NET 8 nuget配置 (opens new window)。如果您不想为此烦恼,请等待预览版3。 FROMnightly
# 使用 构建多平台映像docker buildx
Buildx (opens new window)是Docker工具的一个很好的补充。我认为它是“完整的构建工具包”。就我们的目的而言,它允许指定多个平台一次构建并将它们打包为多平台标签。它甚至会将它们推送到您的注册表,所有这些都只需一个命令。
我们首先需要设置 buildx。
% docker buildx create
whimsical_sanderson
现在,我们可以为应用构建多平台映像。
% docker buildx build --pull -t aspnetapp -f Dockerfile.alpine-non-root --platform linux/arm64,linux/arm,linux/amd64 .
在这里,我们正在构建三种架构。在某些环境中,您还可以仅指定体系结构作为简写,避免重复“linux”。
使用该命令,你将看到以下警告。
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
如果要将映像推送到注册表,则需要添加参数并为参数使用完全指定的注册表名称。或者,您可以使用 将映像导出到 Docker 缓存。但是,仅在一次面向一个体系结构时才有效。--push-t--load--load
让我们试试(使用我的注册表;您需要切换到自己的注册表)。--push
% docker buildx build --pull --push -t dotnetnonroot.azurecr.io/aspnetapp -f Dockerfile.alpine-non-root --platform linux/arm64,linux/arm,linux/amd64 .
该命令将 3 个映像和 1 个标记推送到注册表。
我现在可以尝试在我的苹果笔记本电脑上拉取图像。它在我的树莓派上也能正常工作。
% docker run --rm -d -p 8080:8080 dotnetnonroot.azurecr.io/aspnetapp
08968dcce418db4d6f746bfa3a5f2afdcf66570bc8a726c4f5a4859e8666e354
% curl http://localhost:8080/Environment
{"runtimeVersion":".NET 8.0.0-preview.2.23128.3","osVersion":"Linux 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022","osArchitecture":"Arm64","user":"app","processorCount":4,"totalAvailableMemoryBytes":4124512256,"memoryLimit":0,"memoryUsage":29548544}%
% docker exec 08968dcce418db4d6f746bfa3a5f2afdcf66570bc8a726c4f5a4859e8666e354 uname -a
Linux 5d4a712c32b9 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 Linux
% docker kill 08968dcce418db4d6f746bfa3a5f2afdcf66570bc8a726c4f5a4859e8666e354
我现在将在 x64 计算机上尝试相同的映像。
$ docker run --rm -d -p 8080:8080 dotnetnonroot.azurecr.io/aspnetapp
6dac425acc325da1c085608d503d6c884610cfa5b2a7dd93575f20355daec1a2
$ curl http://localhost:8080/Environment
{"runtimeVersion":".NET 8.0.0-preview.2.23128.3","osVersion":"Linux 4.4.180+ #42962 SMP Tue Sep 20 22:35:50 CST 2022","osArchitecture":"X64","user":"app","processorCount":8,"totalAvailableMemoryBytes":8096030720,"memoryLimit":9223372036854771712,"memoryUsage":94019584}
$ docker exec 6dac425acc325da1c085608d503d6c884610cfa5b2a7dd93575f20355daec1a2 uname -a
Linux 6dac425acc32 4.4.180+ #42962 SMP Tue Sep 20 22:35:50 CST 2022 x86_64 Linux
$ docker kill 6dac425acc325da1c085608d503d6c884610cfa5b2a7dd93575f20355daec1a2
结果看起来不错,过程很简单。
注意:我不会长时间保持我的注册表。它只是为了演示目的并展示什么是可能的。
# 总结
这种适用于 .NET 应用的新 Dockerfile 模式使在所需的任何计算机上为所需的任何体系结构构建容器变得更加简单、轻松和快捷。我们过去在使用 QEMU 进行 x64 仿真时遇到过困难 (opens new window)。这种方法完全避开了 QEMU,避免了我们遇到的问题,同时提高了性能。
我们想知道团队将如何使用多平台容器功能。您会在开发人员桌面还是在 CI 中面向多个体系结构?特别是,我们想知道团队是否会将 Arm64 和 x64 容器映像推送到他们的注册表,即使云容器服务只需要其中一个架构。当本地计算机与云服务的体系结构不匹配时,推送多个体系结构是否有助于开发人员调查生产问题?请告诉我们您是否有关于该主题的计划或现有做法。
我们希望这种新模式对您有用,它使定位多个平台变得更加容易,并且您喜欢拥有更直接的选择。docker build --platformdocker buildx build --platform