16. Jul 2018
Lesedauer 8 Min.
.NET, Container und Co.
Container in Azure
Moderne Softwarelösungen wie Containertechnologien und -dienste verändern Schritt für Schritt die Art und Weise, wie Software installiert und betrieben wird.

Es ist mittlerweile gar nicht mehr so einfach, in puncto moderne Containertechnologien auf dem Laufenden zu bleiben. In diesem Artikel geht es deshalb darum, einen Überblick über verschiedene Containerdienste-Technologien zu schaffen. Zumindest im Moment spricht man im Microsoft-Universum hauptsächlich über:
- Docker,
- Azure Container Service,
- Azure Container Instances,
- Kubernetes und
- Service Fabric.
Der „Docker“
Bevor wir uns mit dem Containerdienst Docker vertraut machen, müssen zuerst einige Docker-Grundlagen verstanden werden. Docker an sich ist eine Virtualisierungstechnologie. Anders als bei einer virtuellen Maschine, die ein OS (Betriebssystem) virtualisiert, wird mit Docker eine Anwendung virtualisiert. Docker wird bei einigen Betriebssystemen mitgeliefert. Auf einer klassischen Windows-10-Entwicklermaschine ist dies nicht der Fall. Wenn Sie die Beispiele aus diesem Artikel ausprobieren möchten, müssen Sie Docker zuerst installieren. Folgen Sie dabei am besten den Anweisungen unter [1]. Am Ende der Installation können Sie den folgenden Befehl in der Kommandozeile ausführen:docker <span class="hljs-comment">--version </span>
Als Ergebnis werden Sie Folgendes sehen:
<span class="hljs-selector-tag">Docker</span> <span class="hljs-selector-tag">version</span> 17<span class="hljs-selector-class">.12</span><span class="hljs-selector-class">.0-ce</span>, <span class="hljs-selector-tag">build</span> <span class="hljs-selector-tag">c97c6d6</span>
Bevor wir uns mit Containern beschäftigen, müssen noch einige Begrifflichkeiten geklärt werden. Die ausführlichen Beschreibungen zu diesen Termini finden Sie unter [2].
Docker-Image
Ein Docker-Image ist eine Ansammlung von Filesystem-Änderungen in einer Reihenfolge (dockerizing). Dies kann man sich wie folgt vorstellen: Man nimmt eine Basisinstallation eines OS – das könnte ein Windows Nano Server sein –, als Nächstes wird zum Beispiel ein IIS-Webserver installiert und anschließend die Webanwendung. Das resultierende Basis-Image heißt in diesem Fall microsoft/nanoserver:latest.Das Wort latest bezeichnet ein sogenanntes Tag. Es ist eine Art von Label, das die Images voneinander unterscheidet. Häufig wird als Label das Versionsmerkmal verwendet. latest steht hier für die letzte Version.Somit kann eine Reihe von Images entstehen, die aufeinander aufbauen, zum Beispiel:- microsoft/nanoserver:latest
- meinwebserveraufnano:latest
- meineappmitwebservermitnano:v1.0.1
Listing 1: Dockerfile für FDD
<span class="hljs-keyword">FROM</span> microsoft/dotnet <br/>ARG EXE_DIR=. <br/><span class="hljs-keyword">WORKDIR</span><span class="bash"> /app </span><br/><span class="bash">COPY <span class="hljs-variable">$EXE_DIR</span>/ ./ </span>
Die erste Zeile FROM … bezeichnet das Basis-Image für unser zu erstellendes Image. In diesem Fall handelt es sich um ein microsoft/dotnet-Image. Dies ist ein Basisbetriebssystem mit einem vorinstallierten .NET Core Framework. Wichtig ist das, weil es sich um ein FDD handelt. Wenn der Docker-Host für Windows betrieben wird, wird das Basis-Image ein Windows-Image sein. Ähnlich verhält es sich, wenn der Docker-Host für Linux betrieben wird: Das Basis-Image wird dann ein Linux-Image sein, siehe Bild 3.
Erstellt wird das Image mit folgender Kommandozeilenanweisung:
<span class="hljs-symbol">docker</span> <span class="hljs-keyword">build </span>--<span class="hljs-keyword">build-arg </span>EXE_DIR=./<span class="hljs-keyword">bin/Debug/</span>
<span class="hljs-keyword">netcoreapp2.0/publish </span>-t cosmosperftests:latest .
Achten Sie hier darauf, den Punkt am Ende nicht zu vergessen. Die Ausführung dieses Befehls dauert einige Sekunden oder sogar Minuten.Der Befehl build wird für die Erstellung von Images verwendet. Mit --build-arg EXE_DIR= wird ein Argument namens EXE_DIR gesetzt. Dieses Argument ist in Listing 1 in der Docker-Datei als Argumentname spezifiziert. Somit können verschiedenste Argumente über die Kommandozeile eingegeben werden. In unserem konkreten Fall definiert EXE_DIR, wo die fertige Anwendung inklusive aller Binaries liegt. Der Build-Prozess wird diese Dateien in das zu erstellende Image unter WORKDIR (in unserem Beispiel /app) kopieren. Das Image bekommt den Namen cosmosperftests:latest.Um alle Images auf Ihrer Maschine zu sehen, führen Sie folgenden Befehl aus:
>docker images
Das Ergebnis dieses Befehls zeigt Bild 4. Dort enthält die erste Zeile unser neu erstelltes Image.
Alle anderen Images waren schon vorher erstellt oder „gepullt“. Mit „pull“ bezeichnet man den Prozess zum Herunterladen anderer Images, die als Basis-Image verwendet werden. Um etwa unser Image cosmosperftests zu erstellen, benötigt man laut dockerfile (siehe Listing 1) ein Basis-Image namens microsoft/dotnet.Beim Erstellen vom Images wird zuerst überprüft, ob bereits ein Basis-Image vorhanden ist. Wenn nicht, wird zunächst ein Image heruntergeladen – in diesem Fall aus dem Microsoft-Repository. Je nach Größe kann dies einige Minuten dauern. Dieses Image sehen Sie in Bild 4 weiter unten.
Container
Um eine Anwendung zu starten, muss mit dem Kommando>Docker <span class="hljs-keyword">run</span><span class="bash"> 54b6c6a9359e </span>
ein Image gestartet werden. Daraufhin wird ein Container erstellt, in dem die Anwendung aus dem Image heraus isoliert ausgeführt wird. Die Zahl 54b6c6a9359e ist die ID des Images, die in Bild 4 zu sehen ist.Wenn der Befehl run ausgeführt wird, sehen Sie in der Regel die Konsolenausgabe der Anwendung. Beim Starten des Containers wird die Anwendung im Verzeichnis WORKDIR mit dem Befehl gestartet, der mit folgendem Eintrag im dockerfile definiert ist:
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"dotnet"</span>, <span class="hljs-string">"cosmosperftests.dll"</span>] </span>
Innerhalb des Containers wird dann Folgendes ausgeführt:
dotnet<span class="hljs-selector-class">.exe</span> cosmosperftests<span class="hljs-selector-class">.dll</span>
In der Welt von .NET Core bedeutet dies, die Anwendung in der cosmosperftests.dll auszuführen. Das Beispiel ist in diesem Artikel nicht beschrieben, weil es im Docker-Kontext nicht unbedingt wichtig ist.Wichtig zu wissen ist dagegen, welche Argumente benötigt werden, wie das Ergebnis der Anwendung aussieht und welche physikalische Anforderung die Anwendung im Container an die Infrastruktur stellt.Falls Sie dennoch am Sourcecode interessiert sind, finden Sie ihn unter [4]. Argumente sind auf der Kommandozeile zu übergeben und Ergebnisse als Konsolenausgabe zu lesen. Dieser Vorgang ist nicht typisiert, siehe auch Listing 1.Im realen Leben werden Container nicht manuell, sondern von einem sogenannten Orchestrator (zum Beispiel Kubernetes) automatisiert gestartet. Um alle aktuell laufenden Container zu sehen, verwendet man folgenden Befehl:
>docker <span class="hljs-keyword">ps</span>.
Das Ergebnis zeigt Bild 5. Dort sieht man neben der Image-ID auch eine Container-ID. Wenn die Anwendung ein Argument erwartet, wird
docker <span class="hljs-keyword">run</span><span class="bash"> 54b6c6a9359e docdb </span>
als Befehlssyntax verwendet. Die Argumente werden wie gewohnt nacheinander eingegeben. In diesem Fall bedeutet das Argument docdb, dass nur die DocumentDB-Performance-Tests ausgeführt werden sollen. Wenn der Container gestartet ist, werden Sie entsprechend feststellen, dass dieses Mal keine MongoDB-Tests ausgeführt werden.
Mehr über . NET-Core-Images
Das Erstellen von Images mit .NET-Core-Anwendungen kann ebenfalls automatisiert werden. In Listing 1 haben wir gesehen, wie ein Image aus einer fertig erstellten .NET-Anwendung erstellt werden kann.Zuerst muss die Anwendung erstellt und publiziert werden und anschließend das Image. Damit dies funktioniert, muss das .NET SDK installiert werden. Auf einer Entwicklermaschine ist das kein Problem.Manchmal ist es aber hilfreich, die Images ohne Visual Studio und SDKs erstellen zu können. Um dies zu erreichen, wird zuerst ein Image erstellt, das als Basis das .NET Core SDK enthält. In dieses Image wird der Sourcecode kopiert, anschließend wird die gesamte Anwendung in diesem Image erstellt. Listing 2 zeigt, wie das geht.Listing 2: Dockerfile für FDD
FROM microsoft/dotnet <span class="hljs-keyword">AS</span> build-sdk-env <br/>ARG EXE_DIR=. <br/>WORKDIR /<span class="hljs-keyword">app</span> <br/><span class="hljs-keyword">RUN</span> echo '**' build-sdk-env <br/># <span class="hljs-keyword">copy</span> csproj and <span class="hljs-keyword">restore</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">distinct</span> layers <br/><span class="hljs-keyword">COPY</span> *.csproj ./ <br/><span class="hljs-keyword">RUN</span> dotnet <span class="hljs-keyword">restore</span> <br/><br/># <span class="hljs-keyword">copy</span> everything <span class="hljs-keyword">else</span> and build <br/><span class="hljs-keyword">COPY</span> . ./ <br/><br/><span class="hljs-keyword">RUN</span> dotnet publish -c Release -o <span class="hljs-keyword">out</span> <br/><br/># build runtime image <span class="hljs-keyword">by</span> using output <span class="hljs-keyword">in</span> previously <br/># built image. <br/>FROM microsoft/dotnet:2.0-runtime <br/>WORKDIR /<span class="hljs-keyword">app</span> <br/><span class="hljs-keyword">COPY</span> --from=build-sdk-env /<span class="hljs-keyword">app</span>/<span class="hljs-keyword">out</span> ./ <br/><br/>ENTRYPOINT [<span class="hljs-string">"dotnet"</span>, <span class="hljs-string">"./CosmosPerfTests.dll"</span>]
Als Erstes wird das Build-Image auf der Basis von microsoft/dotnet erstellt. Dieses erhält das .NET Core SDK. Im nächsten Schritt wird die .csproj-Datei aus dem Source-Verzeichnis in das Image-Arbeitsverzeichnis kopiert und dotnet restore aufgerufen, damit die nötigen Referenzen heruntergeladen werden. Achtung an alle mit nichttoleranter IT: Für diesen Schritt ist eine Internetverbindung notwendig.Mit COPY . ./ wird die gesamte Source kopiert, mit RUN dotnet publish -c Release -o out gebaut und anschließend in das Verzeichnis out publiziert.Die verbleibenden Zeilen erstellen das Image mit der Anwendung auf Basis der microsoft/dotnet:2.0-runtime. Dies ist ein Image mit der .NET Core Runtime ohne SDK. Wie weiter oben bereits gezeigt, wird das Image wie folgt erzeugt:
<span class="hljs-symbol">docker</span> <span class="hljs-keyword">build </span>-t cosmosperftests:latest .
Im Unterschied zu vorher werden dieses Mal zwei Images erstellt (siehe Bild 6). Das erste Image ist wie gehabt cosmosperftests mit unserer Anwendung.
Daneben gibt es nun jedoch noch ein weiteres Image namens <none>. Dies ist ein Image mit dem SDK, in dem die Anwendung kompiliert und publiziert wird. Um dies zu beweisen, starten Sie dieses Image mit:
docker <span class="hljs-built_in">run</span> -<span class="hljs-keyword">it</span> <span class="hljs-number">2358</span>ffda0aba
Das Argument -it bezeichnet einen Start in der interaktiven Session. Mit diesem Befehl startet man das Image und landet in der Konsole. In unserem Fall handelt es sich um einen Linux-Container. Bild 7 zeigt den Inhalt des Verzeichnisses /app, in dem sich Sourcecode befindet. Sie erinnern sich: Der Sourcecode wurde mit COPY . ./ (siehe Listing 2) kopiert.
Mit cd out gelangt man weiter ins /out-Verzeichnis. Dort befinden sich alle fertig erstellten Dateien, die mit dem letzten Schritt in Listing 2 in das eigentliche Image kopiert werden. Wenn Sie misstrauisch sind, können Sie gern die Anwendung direkt im /out-Verzeichnis des Build-Image wie folgt ausführen:
dotnet CosmosPerfTests<span class="hljs-selector-class">.dll</span>
Für alle IoT-begeisterten Maker zeigt Listing 3, wie ein Image für den Raspberry Pi erstellt werden kann.
Listing 3: Image für Raspberry Pi
FROM microsoft/dotnet:2.0-sdk <span class="hljs-keyword">AS</span> build-env <br/>WORKDIR /<span class="hljs-keyword">app</span> <br/><br/><span class="hljs-keyword">COPY</span> *.csproj ./ <br/><span class="hljs-keyword">RUN</span> dotnet <span class="hljs-keyword">restore</span> <br/><br/><span class="hljs-keyword">COPY</span> . ./ <br/><span class="hljs-keyword">RUN</span> dotnet publish -c Release -o <span class="hljs-keyword">out</span> <br/><br/>FROM microsoft/dotnet:2.0.0-runtime-stretch-arm32v7 <br/>WORKDIR /<span class="hljs-keyword">app</span> <br/><span class="hljs-keyword">COPY</span> --from=build-env /<span class="hljs-keyword">app</span>/<span class="hljs-keyword">out</span> ./ <br/>ENTRYPOINT [<span class="hljs-string">"dotnet"</span>, <span class="hljs-string">"./CosmosPerfTests.dll"</span>]
Das einzig Besondere in diesem Listing ist das Basis-Image, das die ARM-Runtime von .NET Core verwendet:
microsoft/dotne<span class="hljs-variable">t:2</span>.<span class="hljs-number">0.0</span>-<span class="hljs-keyword">runtime</span>-stretch-arm32v7
Unter [5] finden Sie einige Beispiele, die unter anderem auch Images mit Automated Testing erzeugen.
.NET API
Manchmal ist es notwendig, die Orchestrierung von Containern selbst zu implementieren. Dafür kann es ganz unterschiedliche Gründe geben: Sie implementieren zum Beispiel eine Anwendung, die einen Code in einem Container ausführen soll.Das ist nicht unbedingt die schönste Vorgehensweise, aber manchmal haben Sie einfach keine Wahl, weil die Anwendung im Container sehr alt ist. Was immer der Grund sein mag, es gibt für diesen Zweck ein Docker .NET API, siehe [6].Listing 4 zeigt, wie die Images (im API Containers genannt) gelistet werden und wie ein einzelner Container gestartet wird. Beachten Sie hier, dass beim Starten des Containers im Aufruf von StartContainerAsync() die ID des Containers und nicht die des Images einzugeben ist. Sie erhalten diese ID mit dem folgenden Befehl:Listing 4: Beispiel für das Docker .NET API
DockerClient client = <br/> <span class="hljs-keyword">new</span> DockerClientConfiguration(<br/> <span class="hljs-keyword">new</span> Uri(<span class="hljs-string">"npipe://localhost/pipe/docker_engine"</span>))<br/> .CreateClient(); <br/><br/>IList&lt;ContainerListResponse&gt; containers = <br/> client.Containers.ListContainersAsync( <br/> <span class="hljs-keyword">new</span> ContainersListParameters() <br/> { <br/> Limit = <span class="hljs-number">100</span>, <br/>}).Result; <br/><br/><span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> cont <span class="hljs-keyword">in</span> containers) <br/>{ <br/> Console.WriteLine(<span class="hljs-string">$"<span class="hljs-subst">{cont.Image}</span>-<span class="hljs-subst">{cont.ImageID}</span>-</span><br/><span class="hljs-string"> <span class="hljs-subst">{cont.ID}</span>"</span>); <br/>} <br/><br/><span class="hljs-keyword">var</span> res = client.Containers.StartContainerAsync(<br/> <span class="hljs-string">"0af2e1fda1257d2e035031c898a89d37c1e94b0bbb9848</span><br/><span class="hljs-string"> ef59b7fd0f21da78f2"</span>, <span class="hljs-keyword">new</span> ContainerStartParameters() <br/>{ <br/>}).Result;
docker container ls --all
Oder direkt aus dem Code in Listing 2:
cont.ID
Das API kann aus beliebigen .NET-Framework-Versionen heraus verwendet werden.
Fazit
In diesem Artikel haben wie die Grundlagen von Docker vorgestellt und gesehen, wie .NET-Anwendungen in Docker-Containern verpackt und betrieben werden können.Mit der Azure Docker Registry befasst sich der nachfolgende Artikel ab Seite 18 in dieser Heftausgabe [7].Fussnoten
- Docker-Setup, http://www.dotnetpro.de/SL1808Container1
- Docker Glossary, https://docs.docker.com/glossary
- Damir Dobric, .NET Core 2.0, Rein ins (neue) Vergnügen, dotnetpro 3/2018, Seite 66 ff., http://www.dotnetpro.de/A1803NETCoreDeep
- CosmosDB-Performancetest-Beispiel im Docker-Container, http://www.dotnetpro.de/SL1808Container2
- Docker .NET Core Samples,, http://www.dotnetpro.de/SL1808Container3
- Docker .NET API, http://www.dotnetpro.de/SL1808Container4
- Thorsten Hans, Container im Griff, Docker-Images mit der ACR verwalten, dotnetpro 8/2018, Seite 18 ff., http://www.dotnetpro.de/A1808AzureContainer