GNU/Linux >> Linux Esercitazione >  >> Panels >> Docker

Ottimizzazione delle dimensioni dell'immagine Docker ASP.NET Core

C'è un ottimo post di Steve Laster nel 2016 sull'ottimizzazione delle dimensioni delle immagini Docker ASP.NET. Da allora Docker ha aggiunto file di build multifase in modo da poter fare di più in un Dockerfile... che sembra un passaggio anche se non lo è. I container riguardano un'implementazione facile e affidabile e riguardano anche la densità. Vuoi usare meno memoria possibile, certo, ma è anche bello renderli il più piccoli possibile in modo da non perdere tempo a spostarli nella rete. La dimensione del file immagine può anche influire sul tempo di avvio del contenitore. Inoltre è semplicemente ordinato.

Ho costruito un piccolo cluster Kubenetes Raspberry Pi (ARM) a 6 nodi sulla mia scrivania - come fai tu - questa settimana e ho notato che le dimensioni delle mie immagini erano un po' più grandi di quanto avrei voluto. Questo è un problema più grande perché è un sistema relativamente a bassa potenza, ma ancora una volta, perché portare con sé x megabyte non necessari se non è necessario?

Alex Ellis ha un ottimo blog sulla creazione di app .NET Core per Raspberry Pi insieme a un video di YouTube. Nel suo video e blog crea un'app console "Console.WriteLine()", che è ottima per OpenFaas (piattaforma serverless open source) ma volevo anche avere app ASP.NET Core sul mio cluster Raspberry Pi k8s. Ha incluso questo come una "sfida" nel suo blog, quindi sfida accettata! Grazie per tutto il tuo aiuto e supporto, Alex!

ASP.NET Core su Docker (su ARM)

Per prima cosa creo un'app ASP.NET Core di base. Potrei fare un'API Web, ma questa volta ne farò una MVC con Razor Pages. Per essere chiari, sono la stessa cosa solo con punti di partenza diversi. Posso sempre aggiungere pagine o aggiungere JSON in un secondo momento.

Comincio con "dotnet new mvc" (o dotnet new razor, ecc.). Lo eseguirò in Docker, gestito da Kuberenetes, e mentre posso sempre cambiare WebHost in Program.cs per cambiare il modo in cui il server Web Kestrel si avvia in questo modo:

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

Per i casi d'uso Docker è più semplice modificare l'URL di ascolto con una variabile di ambiente. Certo, potrebbe essere 80, ma mi piace 5000. Imposterò la variabile di ambiente ASPNETCORE_URLS su http://+:5000 quando creo il Dockerfile.

File Docker MultiStage ottimizzato per ASP.NET

Esistono diversi modi "giusti" per farlo, quindi ti consigliamo di pensare ai tuoi scenari. Di seguito vedrai che sto usando ARM (perché Raspberry Pi), quindi se vedi errori eseguendo il tuo contenitore come "qemu:Unsupported syscall:345", quindi stai tentando di eseguire un'immagine ARM su x86/x64. Costruirò un contenitore ARM da Windows ma non posso eseguirlo qui. Devo inserirlo in un registro contenitori e quindi dire al mio cluster Raspberry Pi di tirarlo giù e POI verrà eseguito, laggiù.

Ecco cosa ho finora. NOTA ci sono alcune cose commentate, quindi sii consapevole. Questo è/era un esercizio di apprendimento per me. Non copiare/incollare a meno che tu non sappia cosa succede! E se c'è un errore, ecco un GitHub Gist del mio Dockerfile da modificare e migliorare.

È importante capire che .NET Core ha un SDK con strumenti di compilazione, kit di sviluppo, compilatori e altro, e quindi ha un runtime. Il runtime non ha le cose "crea un'app", ha solo le cose "esegui un'app". Al momento non esiste un SDK per ARM, quindi questa è una limitazione che stiamo (in qualche modo elegante) aggirando con il file di build multistadio. Ma, anche se esistesse un SDK per ARM, vorremmo comunque utilizzare un Dockerfile come questo perché è più efficiente con lo spazio e crea un'immagine più piccola.

Analizziamolo. Ci sono due fasi. Il primo FROM è l'immagine dell'SDK che crea il codice. Stiamo eseguendo la build all'interno di Docker, che è un modo fantastico e affidabile per creare build.

SUGGERIMENTO PRO: Docker è intelligente nel creare immagini intermedie e nel fare il minimo lavoro , ma è utile se anche noi (gli autori) facciamo la cosa giusta per aiutarlo.

Ad esempio, vedi dove COPIIAMO il .csproj e quindi eseguiamo un "ripristino dotnet"? Spesso vedrai la gente fare una "COPIA . ." e quindi eseguire un ripristino. Ciò non consente a Docker di rilevare cosa è cambiato e finirai per pagare il ripristino su OGNI BUILD.

Eseguendo questi due passaggi:copia il progetto, ripristina, copia il codice, ciò significa che il tuo passaggio intermedio di "ripristino dotnet" verrà memorizzato nella cache da Docker e le cose saranno MOLTO più veloci.

Dopo aver creato, farai una pubblicazione. Se conosci la destinazione come me (linux-arm) puoi fare una pubblicazione RID (id di runtime) che sia autonoma con -r linux-arm (o debian, o altro) e otterrai un completo self- versione contenuta della tua app.

In caso contrario, puoi semplicemente pubblicare il codice dell'app e usare un'immagine di runtime .NET Core per eseguirla. Dal momento che sto usando una build autonoma completa per questa immagine, sarebbe eccessivo includere ANCHE il runtime .NET. Se guardi l'hub Docker per Microsoft/dotnet vedrai immagini chiamate "deps" per "dipendenze". Quelle sono immagini che si trovano sopra Debian che includono le cose di cui .NET ha bisogno per funzionare, ma non .NET stesso.

La pila di immagini è generalmente così (ad esempio)

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

Quindi hai la tua immagine di base, le tue dipendenze e il tuo runtime .NET. L'immagine dell'SDK includerebbe ancora più elementi poiché deve creare codice. Ancora una volta, ecco perché lo usiamo per l'immagine "as builder" e quindi copiamo i risultati della compilazione e inserirli in un'altra immagine di runtime. Ottieni il meglio di tutti i mondi.

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

Nota anche che ho un nuget.config personalizzato, quindi se lo fai anche tu dovrai assicurarti che sia disponibile in fase di compilazione per il ripristino di dotnet per raccogliere tutti i pacchetti.

Ho incluso commentando un gruppo di FROM nella seconda fase. Sto usando solo quello ARM, ma volevo che tu vedessi gli altri.

Dopo aver copiato il codice che abbiamo creato nella nostra immagine di runtime, impostiamo la nostra variabile di ambiente in modo che tutto sia in ascolto sulla porta 5000 internamente (ricordate quello dall'alto?) Quindi eseguiamo la nostra app. Nota che puoi eseguirlo con "dotnet foo.dll" se hai il runtime, ma se sei come me e usi una build indipendente, eseguirai semplicemente "foo".

Per riassumere:

  • Crea con FROM microsoft/dotnet:2.0-sdk come builder
  • Copia i risultati in un runtime
  • Utilizza il runtime FROM adatto a te
    • Architettura CPU corretta?
    • Utilizzo di .NET Runtime (tipico) o di una build indipendente (meno)
  • Ascolto sulla porta giusta (se si tratta di un'app Web)?
  • Eseguire l'app correttamente e correttamente?
  • Hai un .dockerignore? Molto importante per le build .NET, poiché non vuoi copiare su /obj, /bin, ecc., ma vuoi /pubblicato.
    obj/
    bin/
    !pubblicato /

Ottimizzazione un po' di più

Esistono alcuni strumenti "Tree Trimming" pre-release che possono esaminare la tua app e rimuovere codice e binari che non stai chiamando. Ho incluso anche Microsoft.Packaging.Tools.Trimming per provarlo e ottenere ancora più codice inutilizzato dalla mia immagine finale semplicemente aggiungendo un pacchetto al mio progetto.

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

Se esegui la cronologia della finestra mobile sull'immagine finale, puoi vedere esattamente da dove provengono le dimensioni. Se/quando Microsoft passa da un'immagine di base Debian a un'immagine Alpine, questo dovrebbe diventare ancora più piccolo.

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

Ecco l'evoluzione del mio Dockerfile quando ho apportato modifiche e il risultato finale è diventato sempre più piccolo. Sembra 45 mega tagliati con un piccolo lavoro o circa il 20% in meno.

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

Successivamente farò un post sul blog in cui inserirò questa app Web ASP.NET Core standard in Kubernetes usando questa descrizione YAML e la ridimensionerò su Raspberry Pi. Sto imparando molto! Grazie ad Alex Ellis e Glenn Condron e Jessie Frazelle per il loro tempo!

Sponsor: Crea potenti applicazioni Web per gestire ogni fase del ciclo di vita di un documento con DocuVieware HTML5 Viewer e Document Management Kit. Consulta le nostre demo per acquisire, scansionare, modificare, annotare oltre 100 formati e personalizzare la tua interfaccia utente!


Docker
  1. Estrarre il file dall'immagine Docker?

  2. Come utilizzare un Dockerfile per creare un'immagine Docker

  3. Come modificare le immagini Docker

  4. Come eseguire il commit delle modifiche a un'immagine Docker

  5. Esecuzione di un'applicazione ASP.NET Core autonoma su Ubuntu

.NET e Docker

ZEIT ora distribuisce le app Web ASP.NET Core open source con Docker

Esplorazione di ASP.NET Core con Docker in entrambi i contenitori Linux e Windows

Pubblicazione di un'app ASP.NET 5 su Docker in Linux con Visual Studio

Spostamento di un ASP.NET Core dal servizio app di Azure in Windows a Linux eseguendo prima il test in WSL e Docker

Pubblicazione di un sito Web ASP.NET Core su un host di macchine virtuali Linux economico