GNU/Linux >> LINUX-Kenntnisse >  >> Panels >> Docker

Optimieren der Größen von ASP.NET Core Docker-Images

Es gibt einen großartigen Beitrag von Steve Laster aus dem Jahr 2016 über die Optimierung der ASP.NET-Docker-Image-Größen. Seitdem hat Docker mehrstufige Build-Dateien hinzugefügt, sodass Sie mehr in einer Docker-Datei erledigen können ... was sich wie ein Schritt anfühlt, obwohl es keiner ist. Bei Containern geht es um eine einfache und zuverlässige Bereitstellung, und es geht auch um Dichte. Natürlich möchten Sie so wenig Speicher wie möglich verwenden, aber es ist auch schön, sie so klein wie möglich zu machen, damit Sie keine Zeit damit verbringen, sie im Netzwerk zu verschieben. Die Größe der Bilddatei kann sich auch auf die Startzeit des Containers auswirken. Außerdem ist es einfach aufgeräumt.

Ich habe diese Woche – wie Sie – einen kleinen 6-Knoten-Raspberry Pi (ARM) Kubenetes-Cluster auf meinem Schreibtisch erstellt, und mir ist aufgefallen, dass meine Bildgrößen etwas größer waren, als ich möchte. Dies ist ein größeres Problem, da es sich um ein relativ leistungsschwaches System handelt, aber warum sollten Sie x unnötige Megabyte mit sich herumschleppen, wenn Sie nicht müssen?

Alex Ellis hat einen großartigen Blog zum Erstellen von .NET Core-Apps für Raspberry Pi zusammen mit einem YouTube-Video. In seinem Video und Blog baut er eine „Console.WriteLine()“-Konsolen-App, die sich hervorragend für OpenFaas (Serverless-Plattform von Open Source) eignet, aber ich wollte auch ASP.NET Core-Apps auf meinem Raspberry Pi k8s-Cluster haben. Er hat dies als "Herausforderung" in seinen Blog aufgenommen, also Herausforderung angenommen! Danke für all deine Hilfe und Unterstützung, Alex!

ASP.NET Core auf Docker (auf ARM)

Zuerst erstelle ich eine einfache ASP.NET Core-App. Ich könnte eine Web-API erstellen, aber diesmal mache ich eine MVC-API mit Razor Pages. Um es klar zu sagen, sie sind dasselbe, nur mit unterschiedlichen Ausgangspunkten. Ich kann später jederzeit Seiten oder JSON hinzufügen.

Ich beginne mit "dotnet new mvc" (oder dotnet new razor usw.). Ich werde dies in Docker ausführen, das von Kuberenetes verwaltet wird, und obwohl ich den WebHost in Program.cs jederzeit ändern kann, um zu ändern, wie der Kestrel-Webserver wie folgt gestartet wird:

WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")

Für Docker-Anwendungsfälle ist es einfacher, die Listening-URL mit einer Umgebungsvariablen zu ändern. Sicher, es könnten 80 sein, aber ich mag 5000. Ich setze die Umgebungsvariable ASPNETCORE_URLS auf http://+:5000, wenn ich das Dockerfile erstelle.

Optimierte MultiStage-Dockerdatei für ASP.NET

Es gibt eine Reihe von "richtigen" Möglichkeiten, dies zu tun, also sollten Sie über Ihre Szenarien nachdenken. Sie werden unten sehen, dass ich ARM verwende (weil Raspberry Pi), also wenn Sie Fehler sehen Wenn Sie Ihren Container wie „qemu:Unsupported syscall:345“ ausführen, versuchen Sie, ein ARM-Image auf x86/x64 auszuführen. Ich werde einen ARM-Container von Windows aus erstellen, aber ich kann ihn hier nicht ausführen. Ich muss es in eine Containerregistrierung verschieben und dann meinem Raspberry Pi-Cluster sagen, dass es es herunterziehen soll, und DANN wird es dort drüben ausgeführt.

Hier ist, was ich bisher habe. HINWEIS:Einige Dinge sind auskommentiert, seien Sie also bewusst. Dies ist/war eine Lernübung für mich. Kopieren/Einfügen Sie nicht, es sei denn, Sie wissen, was los ist! Und wenn es einen Fehler gibt, hier ist ein GitHub-Gist meiner Docker-Datei, den Sie ändern und verbessern können.

Es ist wichtig zu verstehen, dass .NET Core ein SDK mit Build-Tools und Entwicklungskits und Compilern und so weiter hat, und dann hat es eine Laufzeit. Die Runtime hat nicht das Zeug „eine App erstellen“, sondern nur das Zeug „eine App ausführen“. Derzeit gibt es kein SDK für ARM, das ist also eine Einschränkung, die wir (etwas elegant) mit der mehrstufigen Build-Datei umgehen. Aber selbst wenn es ein SDK für ARM gäbe, würden wir dennoch eine Docker-Datei wie diese verwenden wollen, da sie den Speicherplatz effizienter einsetzt und ein kleineres Image erstellt.

Lassen Sie uns das aufschlüsseln. Es gibt zwei Stufen. Das erste FROM ist das SDK-Image, das den Code erstellt. Wir führen den Build innerhalb von Docker durch – was eine schöne und zuverlässige Art ist, Builds zu erstellen.

PRO-TIPP: Docker ist schlau darin, Zwischenimages zu erstellen und die geringste Arbeit zu leisten , aber es ist nützlich, wenn wir (die Autoren) auch das Richtige tun, um ihm zu helfen.

Sehen Sie zum Beispiel, wo wir die .csproj-Datei KOPIEREN und dann eine "dotnet-Wiederherstellung" durchführen? Oft werden Sie Leute sehen, die eine "KOPIE . ." und dann eine Wiederherstellung durchführen. Dadurch kann Docker nicht erkennen, was sich geändert hat, und Sie zahlen am Ende für die Wiederherstellung bei JEDEM BAU.

Wenn Sie diese beiden Schritte ausführen – Projekt kopieren, wiederherstellen, Code kopieren, bedeutet dies, dass Ihr Zwischenschritt „dotnet restore“ von Docker zwischengespeichert wird und die Dinge VIEL schneller gehen.

Nach dem Erstellen führen Sie eine Veröffentlichung durch. Wenn Sie das Ziel wie ich kennen (Linux-Arm), können Sie eine RID (Laufzeit-ID) veröffentlichen, die mit -r linux-Arm (oder Debian oder was auch immer) in sich geschlossen ist, und Sie erhalten eine vollständige Selbst- enthaltene Version Ihrer App.

Andernfalls können Sie einfach den Code Ihrer App veröffentlichen und ein .NET Core-Laufzeitimage verwenden, um es auszuführen. Da ich für dieses Image einen vollständig eigenständigen Build verwende, wäre es übertrieben, AUCH die .NET-Laufzeitumgebung einzuschließen. Wenn Sie sich den Docker-Hub für Microsoft/dotnet ansehen, sehen Sie Bilder mit dem Namen „deps“ für „Abhängigkeiten“. Das sind Images, die auf Debian sitzen und die Dinge beinhalten, die .NET zum Laufen braucht – aber nicht .NET selbst.

Der Bilderstapel sieht im Allgemeinen so aus (zum Beispiel)

  • FROM debian:stretch
  • FROM microsoft/dotnet:2.0-runtime-deps
  • FROM microsoft/dotnet:2.0-runtime

Sie haben also Ihr Basisimage, Ihre Abhängigkeiten und Ihre .NET-Laufzeitumgebung. Das SDK-Image würde noch mehr Zeug enthalten, da es Code erstellen muss. Aus diesem Grund verwenden wir das wiederum für das "as builder"-Image und kopieren dann die Ergebnisse der Kompilierung heraus und lege sie in ein anderes Runtime-Image. Sie bekommen das Beste aus allen Welten.

FROM microsoft/dotnet:2.0-sdk as builder  

RUN mkdir -p /root/src/app/aspnetcoreapp
WORKDIR /root/src/app/aspnetcoreapp

#copy just the project file over
# this prevents additional extraneous restores
# and allows us to re-use the intermediate layer
# This only happens again if we change the csproj.
# This means WAY faster builds!
COPY aspnetcoreapp.csproj .
#Because we have a custom nuget.config, copy it in
COPY nuget.config .
RUN dotnet restore ./aspnetcoreapp.csproj

COPY . .
RUN dotnet publish -c release -o published -r linux-arm

#Smaller - Best for apps with self-contained .NETs, as it doesn't include the runtime
# It has the *dependencies* to run .NET Apps. The .NET runtime image sits on this
FROM microsoft/dotnet:2.0.0-runtime-deps-stretch-arm32v7

#Bigger - Best for apps .NETs that aren't self-contained.
#FROM microsoft/dotnet:2.0.0-runtime-stretch-arm32v7

# These are the non-ARM images.
#FROM microsoft/dotnet:2.0.0-runtime-deps
#FROM microsoft/dotnet:2.0.0-runtime

WORKDIR /root/
COPY --from=builder /root/src/app/aspnetcoreapp/published .
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000/tcp
# This runs your app with the dotnet exe included with the runtime or SDK
#CMD ["dotnet", "./aspnetcoreapp.dll"]
# This runs your self-contained .NET Core app. You built with -r to get this
CMD ["./aspnetcoreapp"]

Beachten Sie auch, dass ich eine benutzerdefinierte nuget.config habe. Wenn Sie dies auch tun, müssen Sie sicherstellen, dass diese zur Build-Zeit verfügbar ist, damit dotnet restore alle Pakete abholen kann.

Ich habe eine Reihe von FROMs in der zweiten Phase auskommentiert. Ich verwende nur die ARM-Version, aber ich wollte, dass Sie die anderen sehen.

Sobald wir den von uns erstellten Code in unser Runtime-Image kopiert haben, setzen wir unsere Umgebungsvariable so, dass alle intern auf Port 5000 lauschen (erinnern Sie sich daran von oben?) Dann führen wir unsere App aus. Beachten Sie, dass Sie es mit „dotnet foo.dll“ ausführen können, wenn Sie die Laufzeit haben, aber wenn Sie wie ich sind und einen eigenständigen Build verwenden, führen Sie einfach „foo.“

aus

Zusammenfassend:

  • Mit FROM microsoft/dotnet:2.0-sdk als Builder erstellen
  • Kopieren Sie die Ergebnisse in eine Laufzeitumgebung
  • Verwenden Sie die richtige Laufzeit FROM für Sie
    • Richtige CPU-Architektur?
    • Verwendung der .NET-Laufzeit (typisch) oder Verwendung eines eigenständigen Builds (weniger)
  • Auf dem richtigen Port lauschen (bei einer Web-App)?
  • Führen Sie Ihre App erfolgreich und korrekt aus?
  • Haben Sie eine .dockerignore-Datei? Sehr wichtig für .NET-Builds, da Sie nicht /obj, /bin usw. kopieren möchten, aber Sie möchten /published.
    obj/
    bin/
    !published /

Ein bisschen mehr optimieren

Es gibt einige vorab veröffentlichte „Tree Trimming“-Tools, die Ihre App untersuchen und Code und Binärdateien entfernen können, die Sie nicht aufrufen. Ich habe auch Microsoft.Packaging.Tools.Trimming eingefügt, um es auszuprobieren und noch mehr ungenutzten Code aus meinem endgültigen Image herauszuholen, indem ich einfach ein Paket zu meinem Projekt hinzufüge.

Step 8/14 : RUN dotnet publish -c release -o published -r linux-arm /p:LinkDuringPublish=true
---> Running in 39404479945f
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Trimmed 152 out of 347 files for a savings of 20.54 MB
Final app size is 33.56 MB
aspnetcoreapp -> /root/src/app/aspnetcoreapp/bin/release/netcoreapp2.0/linux-arm/aspnetcoreapp.dll
Trimmed 152 out of 347 files for a savings of 20.54 MB
Final app size is 33.56 MB

Wenn Sie den Docker-Verlauf für Ihr endgültiges Image ausführen, können Sie genau sehen, woher die Größe stammt. Falls/wenn Microsoft von einem Debian-Basis-Image zu einem Alpine-Image wechselt, sollte dieses sogar noch kleiner werden.

C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker history c60
IMAGE CREATED CREATED BY SIZE COMMENT
c6094ca46c3b 3 minutes ago /bin/sh -c #(nop) CMD ["dotnet" "./aspnet... 0B
b7dfcf137587 3 minutes ago /bin/sh -c #(nop) EXPOSE 5000/tcp 0B
a5ba51b91d9d 3 minutes ago /bin/sh -c #(nop) ENV ASPNETCORE_URLS=htt... 0B
8742269735bc 3 minutes ago /bin/sh -c #(nop) COPY dir:cc64bd3b9bacaeb... 56.5MB
28c008e38973 3 minutes ago /bin/sh -c #(nop) WORKDIR /root/ 0B
4bafd6e2811a 4 hours ago /bin/sh -c apt-get update && apt-get i... 45.4MB
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:8b7cf813a113aa2... 85.7MB

Hier ist die Entwicklung meines Dockerfiles, als ich Änderungen vorgenommen habe und das Endergebnis immer kleiner wurde. Sieht aus wie 45 MB, die mit ein wenig Arbeit getrimmt wurden, oder etwa 20 % kleiner.

C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker images | find /i "aspnetcoreapp"
shanselman/aspnetcoreapp 0.5 c6094ca46c3b About a minute ago 188MB
shanselman/aspnetcoreapp 0.4 083bfbdc4e01 12 minutes ago 196MB
shanselman/aspnetcoreapp 0.3 fa053b4ee2b4 About an hour ago 199MB
shanselman/aspnetcoreapp 0.2 ba73f14e29aa 4 hours ago 207MB
shanselman/aspnetcoreapp 0.1 cac2f0e3826c 3 hours ago 233MB

Später schreibe ich einen Blogbeitrag, in dem ich diese standardmäßige ASP.NET Core-Webanwendung mithilfe dieser YAML-Beschreibung in Kubernetes einfüge und sie auf dem Raspberry Pi hochskaliere. Ich lerne viel! Danke an Alex Ellis und Glenn Condron und Jessie Frazelle für ihre Zeit!

Sponsor: Erstellen Sie mit DocuVieware HTML5 Viewer und Document Management Kit leistungsstarke Webanwendungen, um jeden Schritt im Lebenszyklus eines Dokuments zu verwalten. Sehen Sie sich unsere Demos an, um mehr als 100 Formate zu erwerben, zu scannen, zu bearbeiten, zu kommentieren und Ihre Benutzeroberfläche anzupassen!


Docker
  1. Datei aus Docker-Image extrahieren?

  2. So verwenden Sie ein Dockerfile zum Erstellen eines Docker-Images

  3. So ändern Sie Docker-Images

  4. So übertragen Sie Änderungen an einem Docker-Image

  5. Ausführen einer eigenständigen ASP.NET Core-Anwendung auf Ubuntu

.NET und Docker

ZEIT jetzt Bereitstellungen von Open-Source-ASP.NET-Core-Web-Apps mit Docker

Erkunden von ASP.NET Core mit Docker in Linux- und Windows-Containern

Veröffentlichen einer ASP.NET 5-App in Docker unter Linux mit Visual Studio

Verschieben eines ASP.NET Core von Azure App Service unter Windows zu Linux, indem zuerst in WSL und Docker getestet wird

Veröffentlichen einer ASP.NET Core-Website auf einem günstigen Linux-VM-Host