Der lise Blog:
Einsichten, Ansichten, Aussichten

ASP.NET mit Docker

Docker gibt es jetzt schon einige Jahre und es ist seitdem sehr erfolgreich. Es macht nicht den Anschein, als würde der Trend abnehmen. Besonders die Containerisierung von Node.JS und nginx Anwendungen vorwiegend auf Basis von Linux erfreuen sich in Microservice-Szenarien großer Beliebtheit. Aber es muss nicht immer Linux sein. Heute zeigen wir euch, wie es möglich ist, auch ASP.NET im Container laufen zu lassen.

Dazu stellen wir eine Beispielanwendung vor, die sich aus sowohl einem klassischen ASP.NET Assembly des .NET Frameworks als auch aus einem ASP.NET Core Assembly zusammensetzt. Wir wählten bewusst einen unterschiedlichen Technologie-Stack, um die Entkopplung von Komponenten in einer Anwendung zu veranschaulichen.
 

Beispielanwendung MettHub – Eine ASP.NET Core MVC Anwendung greift auf eine klassische ASP.NET WebApi zu

In der lise GmbH ist es Tradition zu diversen Anlässen (Einstand, Nachwuchs oder einfach nur so) den Kollegen Brötchen und dazu einen Haufen Mett auszugeben. Dem Ausgebenden stellt sich dann immer die Frage, wie schwer der Mettklumpen sein sollte, den er kaufen muss.

Unsere Beispielanwendung MettHub bringt da Abhilfe. Ein kleiner Mett Calculator ermittelt die benötigte Menge im Verhältnis zur Teilnehmerzahl und stellt somit ein wahres Power-User-Feature für unsere Mitarbeiter dar. Als Bonus hat MettHub noch eine Galerie, in der Mettbilder angezeigt werden können.

Der Code für die Beispielanwendung findet ihr hier.

Für das MettHub wurden in Visual Studio zwei Projekte angelegt. Zum einen eine MVC-Anwendung mit ASP.NET Core als Weboberfläche für den Nutzer. Dort findet sie die Navigation für die zwei Bereiche Mett Calculator und Mett Galerie vor. Das zweite Projekt ist eine ASP.NET WebApi, die auf dem klassischen .NET Framework aufsetzt.

Gibt der Nutzer im Mett Calculator die Teilnehmeranzahl ein und bestätigt, wird im Hintergrund von der MVC-Anwendung ein Request an den Endpunkt

 

/api/mettcalculation?people={Teilnehmeranzahl}

 

der API gesendet. Das Ergebnis kommt in Gramm und wird dann in der Oberfläche angezeigt.

Die URL unter der die WebApp, den die API erreichen kann, ist über die Umgebungsvariable API_URL konfigurierbar. Wenn wir MettHub.WebApp lokal aus Visual Studio im Debug-Modus starten, wird der Wert 

 

http://localhost:59215/api aus appsettings.Development.json 

 

genommen, der Projekt-URL des WebApi Projekts. Für den Container-Betrieb werden wir den Wert entsprechend anpassen.

Die Bilder, die in der Mett-Galerie angezeigt werden, werden aus dem Verzeichnis MettPics im

 

ContentRootPath

 

genommen.

Wie bekommt man die Anwendung nun in den Container? Dazu zunächst etwas Hintergrundinformationen zum Betriebssystem.
 

Docker kann auch Windows Container

Traditionell lief die Docker Engine nur auf Linux und somit auch nur für Linux Container, da der Kernel ja geteilt wird. In gemeinsamer Entwicklung von Docker und Microsoft wurde später das Konzept der Windows-Container realisiert, die vom Prinzip her den Linux-Containern ähneln. Die Docker Engine ist inzwischen in allen Versionen von Windows Server 2016 enthalten. Aber auch auf Windows 10 können mit Docker für Windows Container gestartet werden. Bisher war dazu Hyper-V mit einer Moby Linux VM nötig, um Linux-Container laufen zu lassen. Seit Docker für Windows Version 18.03.0-ce-win59 können Linux-Container sogar nativ auf Windows laufen, dank LCOW (Linux Contrainers on Windows).

Einen Windows-Container unter Linux zu starten, ist allgemein leider nicht möglich.
 

Die Basis-Images von Microsoft

Als Vorlage für die oben beschriebene Konstellation dienen die folgenden von Microsoft zur Verfügung gestellten Docker Basis-Images:

  • microsoft/dotnet für ASP.NET Core 2.x für die Weboberfläche
  • microsoft/aspnet für ASP.NET 3.5 – 4.7.1 für die WebApi.

Eine Liste mit allen Images von Microsoft findet ihr auf Docker Hub.

Die dotnet- und aspnetcore-Images sind von einem Windows-Nano-Server-Image abgeleitet. Das ist ein stark abgespeckter Windows Server 2016, der ausschließlich für den Container-Betrieb gedacht ist. Weggelassen werden neben der GUI einige Server-Rollen wie Active Directory und zahlreiche Management-Features.

Das aspnet-Image basiert auf einem IIS-Image, welches wiederum auf Windows Server Core (Windows Server 2016 ohne GUI) basiert.

Windows Nano Server bringt hier für die Containerisierung weniger Ballast mit und ist mit dem ohnehin viel schlankeren ASP.NET Core klar die beste Wahl. Außerdem läuft es auch mit Docker unter Linux.
 

So kommt das klassische ASP.NET in den Container

Der erste Schritt besteht darin, das Dockerfile names „Dockerfile“ für die MettHub.Api mit diesem Inhalt anzulegen:

 

FROM microsoft/aspnet

COPY ./bin/Release/PublishOutput/ /inetpub/wwwroot

 

In der ersten Zeile definieren wir, dass unser neues Image auf Basis von microsoft/aspnet erzeugt werden soll. In der nächsten Zeile soll unser Image die Dateien unseres kompilierten Projekts aus

 

bin/Release/PublishOutput

 

außerhalb des Containers in

 

/inetpub/wwwroot

 

innerhalb des Containers kopieren. Von dort wird es dann vom Microsoft Internet Information Server (IIS), der im Image enthalten ist, im laufenden Container bereitgestellt.

 

Nachdem wir das Projekt in Visual gepublisht haben, können wir

 

docker build -t metthub-api .

 

im Verzeichnis des Dockerfiles aufrufen, um aus unserem Dockerfile ein Image namens metthub-api zu erzeugen. Das kann einen Moment dauern, da das Base-Images zunächst einmalig heruntergeladen werden muss.

 

$ docker build -t metthub-api .

Sending build context to Docker daemon  80.21MB

Step 1/2 : FROM microsoft/aspnet

 ---> 21a4756c6eac

Step 2/2 : COPY ./bin/Release/PublishOutput/ /inetpub/wwwroot

 ---> 02dbd5bc778e

Successfully built 02dbd5bc778e

Successfully tagged metthub-api:latest

 

 

Jetzt können wir mit

 

docker run

 

einen Container mit diesem Image starten.

Damit der Container starten kann, müssen wir Docker auf Windows-Container umgestellt haben:

$ docker run -p 8081:80 metthub-api

 

Port 80 im Container wird auf Port 8081 des Host-Systems umgeleitet.

Wenn alles klappt, sollten wir bei einem GET Request an

 http://localhost:8081/api/mettcalculation?people=10 

eine Response mit Status 200 OK und einer 800 im Body bekommen.
 

... und so das MVC-Projekt mit ASP.NET Core

Für das MVC-Projekt verfahren wir ähnlich wie beim WebApi-Projekt. Auch hier gibt es ein Dockerfile, nur diesmal von microsoft/dotnet abgeleitet.        

 

FROM microsoft/dotnet:2.1-sdk AS build-env

WORKDIR /app

COPY *.csproj ./

RUN dotnet restore

COPY . ./

RUN dotnet publish -c Release -o out

FROM microsoft/dotnet:2.1-aspnetcore-runtime

WORKDIR /app

COPY --from=build-env /app/out .

ENTRYPOINT ["dotnet", "MettHub.WebApp.dll"]

 

Es werden ebenfalls die kompilierten Dateien unseres Projekts eingebunden. Das machen wir aber diesmal nicht, wie im WebApi-Projekt manuell, sondern automatisiert, mit Hilfe eines Intermediate-Containers vom dotnet-sdk-Image, den es nur zum Buildvorgang des Dockerimages gibt. Das finale Image auf Basis von

 

aspnetcore-runtime

 

bekommt den Release-Output reinkopiert. Die letzte Zeile lässt den Container beim Start die dotnet-CLI mit unserem Projekt starten. Gehostet wird diesmal im selfhosted Mode ohne IIS.

Visual Studio bietet uns auch die Möglichkeit, sich ein Dockerfile beim Anlegen eines neuen Projekts aus den Templates in Visual Studio mit erzeugen lassen. Alternativ kann das per Rechtsklick auf unser Projekt -> Add -> Docker Support geschehen. Dabei wird noch ein Debug-Profil „Docker“ in launchSettings.json angelegt.

Mit docker run können wir nun unser Image im Container starten. Dabei müssten wir noch die Umgebungsvariable

 

API_URL

 

im Container setzen und ein Volumen für das Verzeichnis MettPics übergeben. Hier die abhängigen Stellen im Code:

MettGalleryService.cs

            var picPaths = Directory.GetFiles(Path.Combine(_hostingEnvironment.ContentRootPath, "MettPics"));

            var mettPicUrls = picPaths.Select(x => "/MettPics/" + (new FileInfo(x)).Name);

MettCalculationService.cs

            var apiUrl = Environment.GetEnvironmentVariable("API_URL");

            var client = _httpClientFactory.CreateClient();

      client.BaseAddress = new Uri(apiUrl);

            var calculationPath = $"/api/mettcalculation?people={people}";

            var result = await client.GetStringAsync(calculationPath);
 

Docker Compose startet alles zusammen

Bisher haben wir zwei einzelne Images. Mit Docker Compose können wir durch eine Konfigurationsdatei mehrere Container gleichzeitig starten und Konfigurationsparameter festlegen, die wir sonst

 

docker run

 

als Parameter mitgegeben hätten.

Wir legen eine Datei namens docker-compose.yml im übergeordneten Verzeichnis beider Projekte an:

 

version: '3'

services:

  webapp:

    build: "MettHub.WebApp"

    ports:

     - "8080:80"

    volumes:

     - C:\mettpics:C:\app\MettPics

    environment:

     - API_URL=http://api/api

  api:

    build: "MettHub.Api"

 

Um den Container-Verbund zu starten, führen wir in diesem Verzeichnis

 

docker-compose up

 

aus. Vorher brauchen wir noch ein Verzeichnis mit Bildern auf dem Host-System. Hier ist C:\mettpics schon vorkonfiguriert. Wenn ihr keine eigenen Mettbilder habt, findet ihr welche in unserem Repo. Um das Volumen, wie hier, einbinden zu können, müssen wir Laufwerk C in den Docker-Settings als Shared Drive freigeben. Es könnte aber auch ein existierendes Docker-Volume übergeben werden.

Der Webserver der WebApp lauscht auf Port 80. Durch das hier definierte Portmapping kann die App auf Port 8080 auf dem Hostsystem aufgerufen werden. Beide Container starten isoliert in einem Netzwerk und können sich aber gegenseitig über ihren Service-Namen ansprechen. So geben wir der WebApp für die Umgebungsvariable

 

API_URL

 

eine entsprechende URL, mit der dieser Container auf den Api-Container zugreifen kann. Von außen ist diese nicht erreichbar.
 

Das Ergebnis

Der erste Start unserer Konstruktion mit

 

docker-compose up

 

kann eine Weile dauern, wenn ihr die benötigten Docker-Images zum ersten Mal verwendet. Ihr erreicht die Webanwendung nach dem abgeschlossenen Startvorgang unter http ://localhost:8080.

Jetzt stünde der Veröffentlichung in der Cloud nichts mehr im Wege. Azure und Co. bieten zahlreiche Möglichkeiten zum Hosten von Windows-Containern.

Mit

 

docker history <image name>

 

können wir uns jeweils die Schichten und ihre Größe der Images metthub_webapp und metthub_api anschauen.

Während metthub_webapp mit rund 500 MB gut dasteht, benötigt metthub_api knapp 4 GB. Das ist dem Unterschied der Base-Images von jeweils .NET Core und .NET Framework zu verdanken. ASP.NET Core-Anwendungen eignen sich daher verglichen zur klassischen Variante viel besser zur Containerisierung und dem Einsatz von Microservices.

Zudem können sie auch auf Linux gehostet werden. Wenn man gezwungen ist, auf Linux zu hosten, kann man statt unseres WebApi Containers auch schnell einen aus ASP.NET Core oder Node.JS basteln. Änderungen am Image metthub_webapp wären nicht nötig. Nur die docker-compose.yaml muss angepasst werden.

Wir hoffen, es ist uns gelungen, ASP.NET-Entwicklern einen ersten Schritt in Richtung Docker zu zeigen. Aber es muss nicht gleich ein Umstieg auf eine Microservice-Architektur sein. Docker bietet auch für ASP.NET-Entwickler eine Vielzahl an interessanten Möglichkeiten. Auch schon beim Ausführen von automatisierten Tests kann der Einsatz einer Container-Technologie einen großen Mehrwert bieten.

 

Du bist Softwareentwickler:in und suchst ein junges und agiles Team?  Dann schau doch mal bei unserer Jobbörse vorbei!

Ja, ich möchte zur lise Family gehören!

 

Diesen Artikel weiterempfehlen

 Teilen  Teilen  Teilen  Teilen