-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
A friend of mine asked for an update on what the story was on using .NET container images on Apple M1, assuming your goal was to deploy to x64 services in Azure (or pick your cloud).
There are various challenges, some specific to .NET and others more general. Windows Arm64 (with x64 emulation) is likely identical to this.
Here's what you want:
- High-fidelity dev experience with prod.
- High performance dev experience.
- Intuitive docker gestures.
Today, it is very easy to fall into an Arm64-centric experience on M1 (for any app plat, not just .NET) and then push an Arm64 image to your registry, and then realize it won't work on your x64 cloud hardware. And then you are not sure what happened. Ughh.
Docker desktop defaults to Arm64
Docker on Apple M1 defaults to native architecture, which is Arm64. If the images you are pulling are multi-arch, you'll pull Arm64. If they are single-arch (x64 or Arm64), you'll pull whatever that arch is. That's the right design choice, but also confusing if your deployment target in x64.
All .NET images are multi-arch. We want to enable using the same tags in a variety of environments. That's all good. It however means that you'll always get Arm64 images by default, on Apple M1 machines. Other app platforms will be the same.
I'll show you, using dotnetapp:
% docker build --pull -t dotnetapp .
% docker run --rm dotnetapp
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.5
Debian GNU/Linux 11 (bullseye)
OSArchitecture: Arm64
ProcessorCount: 4
TotalAvailableMemoryBytes: 3.84 GiBThat's clearly Arm64. We can force building as x64.
% docker build --pull --platform linux/amd64 -t dotnetapp .
% docker run --rm dotnetapp
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.5
Debian GNU/Linux 11 (bullseye)
OSArchitecture: X64
ProcessorCount: 4
TotalAvailableMemoryBytes: 3.84 GiBThat's x64. That's what we wanted for this scenario.
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
That error is coming from Docker. It's there to tell that you're using emulation.
If you want to live the x64 lifestyle on your Apple M1 machine, you need to use the --platform linux/amd64 flag. Perhaps there is a way to enable amd64 as the default. I don't know.
.NET isn't supported in QEMU
The bigger issue is that .NET isn't supported in QEMU. QEMU is the emulator that Docker Desktop uses for emulation, on both x64 (to emulate Arm64) and Arm64 (to emulate x64) machines.
I'll show you the problem, using aspnetapp (building and running as x64):
% docker build --pull --platform linux/amd64 -t aspnetapp .
% docker run --rm -p 8000:80 aspnetapp
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Unhandled exception. System.IO.IOException: Function not implemented
at System.IO.FileSystemWatcher.StartRaisingEvents()
at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed()
at System.IO.FileSystemWatcher.set_EnableRaisingEvents(Boolean value)
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.TryEnableFileSystemWatcher()
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.CreateFileChangeToken(String filter)
at Microsoft.Extensions.FileProviders.PhysicalFileProvider.Watch(String filter)
at Microsoft.Extensions.Configuration.FileConfigurationProvider.<.ctor>b__1_0()
at Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func`1 changeTokenProducer, Action changeTokenConsumer)
at Microsoft.Extensions.Configuration.FileConfigurationProvider..ctor(FileConfigurationSource source)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationSource.Build(IConfigurationBuilder builder)
at Microsoft.Extensions.Configuration.ConfigurationManager.AddSource(IConfigurationSource source)
at Microsoft.Extensions.Configuration.ConfigurationManager.Microsoft.Extensions.Configuration.IConfigurationBuilder.Add(IConfigurationSource source)
at Microsoft.Extensions.Configuration.ConfigurationExtensions.Add[TSource](IConfigurationBuilder builder, Action`1 configureSource)
at Microsoft.Extensions.Configuration.JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder builder, IFileProvider provider, String path, Boolean optional, Boolean reloadOnChange)
at Microsoft.Extensions.Configuration.JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder builder, String path, Boolean optional, Boolean reloadOnChange)
at Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.<>c__DisplayClass11_0.<ConfigureDefaults>b__1(HostBuilderContext hostingContext, IConfigurationBuilder config)
at Microsoft.AspNetCore.Hosting.BootstrapHostBuilder.RunDefaultCallbacks(ConfigurationManager configuration, HostBuilder innerBuilder)
at Microsoft.AspNetCore.Builder.WebApplicationBuilder..ctor(WebApplicationOptions options, Action`1 configureDefaults)
at Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(String[] args)
at Program.<Main>$(String[] args) in /source/aspnetapp/Program.cs:line 1
qemu: uncaught target signal 6 (Aborted) - core dumpedThat's not pretty. We're hoping this QEMU issue gets resolved.
Workaround 1
Use the multi-arch tags as intended, just like in the aspnetapp Dockerfile. Run .NET in containers as Arm64 in Apple M1. .NET has excellent fidelity across architectures so you will get an experience. If you want to deploy images to a registry, either build with --platform linux/amd64 or build in a CI service like GitHub Actions. They will naturally build as x64.
This approach 100% works. You get the best performance on Apple M1 and straightforward gestures. It's the no-compromises option, since emulated QEMU will always be a lot slower than native architecture.
Workaround 2
This workaround forces your Dockerfile to produce x64 assets by default. The SDK runs in whatever arch is chosen for docker build. It enables you to pivot the final image pretty easily with a --build-arg. The intent of this is that docker build will produce an asset that runs in your x64 cloud by default.
I modified the aspnetapp Dockerfile a bit:
ARG ARCH=amd64
ARG TAG=6.0-bullseye-slim-$ARCH
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /source
# copy csproj and restore as distinct layers
COPY *.sln .
COPY aspnetapp/*.csproj ./aspnetapp/
RUN dotnet restore
# copy everything else and build app
COPY aspnetapp/. ./aspnetapp/
WORKDIR /source/aspnetapp
RUN dotnet publish -c release -o /app --no-restore
# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:$TAG
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "aspnetapp.dll"]I added the two ARG lines at the start and added $TAG in the last FROM statement. By default, this Dockerfile will produce x64 images by default, even when built on Apple M1 machines.
However, if you build that image and docker run it, it will still fail, as I demonstrated earlier. If you want a good dev experience, you can opt into building and running it as Arm64.
Like this:
% docker build --pull --build-arg ARCH=arm64v8 -t aspnetapp .
% docker run --rm -p 8000:80 aspnetappThat approach allows you to build an image on your M1 machine -- x64 by default -- and then push to a registry. For some folks, that's might be what they are looking for.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status