GNU/Linux >> Tutoriels Linux >  >> Panels >> Docker

Optimisation des tailles d'image Docker ASP.NET Core

Il y a un excellent article de Steve Laster en 2016 sur l'optimisation de la taille des images Docker ASP.NET. Depuis lors, Docker a ajouté des fichiers de construction en plusieurs étapes afin que vous puissiez en faire plus dans un Dockerfile... ce qui ressemble à une étape même si ce n'est pas le cas. Les conteneurs sont synonymes de déploiement facile et fiable, mais aussi de densité. Vous voulez utiliser le moins de mémoire possible, bien sûr, mais il est également agréable de les rendre aussi petits que possible afin de ne pas perdre de temps à les déplacer sur le réseau. La taille du fichier image peut également affecter le temps de démarrage du conteneur. En plus, c'est juste rangé.

J'ai construit un petit cluster Kubenetes Raspberry Pi (ARM) à 6 nœuds sur mon bureau - comme vous le faites - cette semaine, et j'ai remarqué que la taille de mes images était un peu plus grande que je ne le souhaiterais. C'est un problème plus important car il s'agit d'un système relativement peu puissant, mais encore une fois, pourquoi transporter x mégaoctets inutiles si vous n'avez pas à le faire ?

Alex Ellis a un excellent blog sur la création d'applications .NET Core pour Raspberry Pi ainsi qu'une vidéo YouTube. Dans sa vidéo et son blog, il construit une application de console "Console.WriteLine()", ce qui est idéal pour OpenFaas (plate-forme open source sans serveur), mais je voulais également avoir des applications ASP.NET Core sur mon cluster Raspberry Pi k8s. Il a inclus cela comme un "défi" dans son blog, alors défi accepté ! Merci pour votre aide et votre soutien, Alex !

ASP.NET Core sur Docker (sur ARM)

Je crée d'abord une application ASP.NET Core de base. Je pourrais faire une API Web, mais cette fois je vais en faire une MVC avec Razor Pages. Pour être clair, ils sont la même chose avec des points de départ différents. Je peux toujours ajouter des pages ou ajouter JSON à l'un ou l'autre, plus tard.

Je commence par "dotnet new mvc" (ou dotnet new razor, etc.). Je vais exécuter ceci dans Docker, géré par Kuberenetes, et bien que je puisse toujours changer le WebHost dans Program.cs pour changer la façon dont le serveur Web Kestrel démarre comme ceci :

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

Pour les cas d'utilisation de Docker, il est plus facile de modifier l'URL d'écoute avec une variable d'environnement. Bien sûr, cela pourrait être 80, mais j'aime 5000. Je définirai la variable d'environnement ASPNETCORE_URLS sur http://+:5000 lorsque je créerai le Dockerfile.

Fichier Docker multi-étapes optimisé pour ASP.NET

Il existe un certain nombre de "bonnes" façons de procéder, vous devrez donc réfléchir à vos scénarios. Vous verrez ci-dessous que j'utilise ARM (parce que Raspberry Pi) donc si vous voyez des erreurs exécutant votre conteneur comme "qemu :appel système non pris en charge :345", vous essayez d'exécuter une image ARM sur x86/x64. Je vais construire un conteneur ARM à partir de Windows mais je ne peux pas l'exécuter ici. Je dois le pousser vers un registre de conteneurs, puis dire à mon cluster Raspberry Pi de le retirer et ALORS il fonctionnera, là-bas.

Voici ce que j'ai jusqu'à présent. REMARQUE il y a certaines choses commentées, alors soyez conscient. C'est/était un exercice d'apprentissage pour moi. Ne faites pas de copier/coller à moins que vous ne sachiez ce qui se passe ! Et s'il y a une erreur, voici un GitHub Gist de mon Dockerfile pour que vous puissiez le modifier et l'améliorer.

Il est important de comprendre que .NET Core a un SDK avec des outils de construction et des kits de développement et des compilateurs et d'autres choses, puis il a un runtime. Le runtime n'a pas le truc "créer une application", il n'a que le truc "exécuter une application". Il n'y a pas actuellement de SDK pour ARM, c'est donc une limitation que nous contournons (avec élégance) avec le fichier de construction en plusieurs étapes. Mais, même s'il y avait un SDK pour ARM, nous voudrions toujours utiliser un Dockerfile comme celui-ci car il est plus efficace avec de l'espace et crée une image plus petite.

Décomposons cela. Il y a deux étapes. Le premier FROM est l'image SDK qui construit le code. Nous réalisons la compilation dans Docker, ce qui est un moyen agréable et fiable de créer des compilations.

CONSEIL DE PRO : Docker est intelligent pour créer des images intermédiaires et faire le moins de travail , mais c'est utile si nous (les auteurs) faisons aussi ce qu'il faut pour l'aider.

Par exemple, voyez où nous COPIONS le .csproj et effectuons ensuite une "restauration dotnet" ? Souvent, vous verrez des gens faire une "COPIE . ." puis faire une restauration. Cela ne permet pas à Docker de détecter ce qui a changé et vous finirez par payer pour la restauration sur CHAQUE CONSTRUCTION.

En faisant ces deux étapes - copier le projet, restaurer, copier le code, cela signifie que votre étape intermédiaire "dotnet restore" sera mise en cache par Docker et que les choses iront BEAUCOUP plus vite.

Après avoir construit, vous ferez une publication. Si vous connaissez la destination comme moi (linux-arm), vous pouvez faire une publication RID (runtime id) qui est autonome avec -r linux-arm (ou debian, ou autre) et vous obtiendrez une auto- version contenue de votre application.

Sinon, vous pouvez simplement publier le code de votre application et utiliser une image d'exécution .NET Core pour l'exécuter. Étant donné que j'utilise une version autonome complète pour cette image, il serait exagéré d'inclure AUSSI le runtime .NET. Si vous regardez le hub Docker pour Microsoft/dotnet, vous verrez des images appelées "deps" pour les "dépendances". Ce sont des images qui se trouvent au-dessus de Debian et qui incluent les choses dont .NET a besoin pour fonctionner - mais pas .NET lui-même.

La pile d'images ressemble généralement à ceci (par exemple)

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

Vous avez donc votre image de base, vos dépendances et votre environnement d'exécution .NET. L'image SDK inclurait encore plus de choses puisqu'elle a besoin de construire du code. Encore une fois, c'est pourquoi nous l'utilisons pour l'image "as builder" puis copiez les résultats de la compilation et placez-les dans une autre image d'exécution. Vous obtenez le meilleur de tous les mondes.

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"]

Notez également que j'ai un nuget.config personnalisé, donc si vous le faites également, vous devrez vous assurer qu'il est disponible au moment de la construction pour que la restauration dotnet récupère tous les packages.

J'ai inclus en commentant un tas de FROM dans la deuxième étape. Je n'utilise que celui d'ARM, mais je voulais que vous voyiez les autres.

Une fois que nous avons copié le code que nous avons créé dans notre image d'exécution, nous définissons notre variable d'environnement afin que tous écoutent sur le port 5000 en interne (vous vous souvenez de cela ci-dessus ?) Ensuite, nous exécutons notre application. Notez que vous pouvez l'exécuter avec "dotnet foo.dll" si vous avez le runtime, mais si vous êtes comme moi et que vous utilisez une version autonome, vous n'aurez qu'à exécuter "foo".

Pour résumer :

  • Construire avec FROM microsoft/dotnet:2.0-sdk comme constructeur
  • Copier les résultats dans un environnement d'exécution
  • Utilisez le bon environnement d'exécution FROM pour vous
    • Bonne architecture de processeur ?
    • Utiliser le runtime .NET (typique) ou utiliser une version autonome (moins)
  • Vous écoutez sur le bon port (s'il s'agit d'une application Web) ?
  • Exécutez votre application avec succès et correctement ?
  • Avez-vous un .dockerignore ? Super important pour les builds .NET, car vous ne voulez pas copier /obj, /bin, etc., mais vous voulez /published.
    obj/
    bin/
    !published /

Optimiser un peu plus

Il existe quelques outils "Tree Trimming" en version préliminaire qui peuvent examiner votre application et supprimer le code et les fichiers binaires que vous n'appelez pas. J'ai également inclus Microsoft.Packaging.Tools.Trimming pour l'essayer et extraire encore plus de code inutilisé de mon image finale en ajoutant simplement un package à mon projet.

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

Si vous exécutez l'historique Docker sur votre image finale, vous pouvez voir exactement d'où vient la taille. Si/quand Microsoft passe d'une image de base Debian à une image Alpine, cela devrait devenir encore plus petit.

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

Voici l'évolution de mon Dockerfile au fur et à mesure que j'apportais des modifications et que le résultat final devenait de plus en plus petit. Ressemble à 45 Mo coupés avec un peu de travail ou environ 20 % plus petit.

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

Plus tard, je publierai un article de blog dans lequel je placerai cette application Web ASP.NET Core standard dans Kubernetes en utilisant cette description YAML et je la mettrai à l'échelle sur le Raspberry Pi. J'apprends beaucoup ! Merci à Alex Ellis, Glenn Condron et Jessie Frazelle pour leur temps !

Parrain : Créez de puissantes applications Web pour gérer chaque étape du cycle de vie d'un document avec DocuVieware HTML5 Viewer et Document Management Kit. Consultez nos démos pour acquérir, numériser, modifier, annoter plus de 100 formats et personnaliser votre interface !


Docker
  1. Extraire le fichier de l'image Docker ?

  2. Comment utiliser un Dockerfile pour créer une image Docker

  3. Comment modifier les images Docker

  4. Comment valider les modifications apportées à une image Docker

  5. Exécution d'une application ASP.NET Core autonome sur Ubuntu

.NET et Docker

ZEIT déploie désormais des applications Web open source ASP.NET Core avec Docker

Explorer ASP.NET Core avec Docker dans les conteneurs Linux et Windows

Publication d'une application ASP.NET 5 sur Docker sous Linux avec Visual Studio

Déplacement d'un ASP.NET Core d'Azure App Service sur Windows vers Linux en testant d'abord dans WSL et Docker

Publication d'un site Web ASP.NET Core sur un hôte de machine virtuelle Linux bon marché