Entwicklungsworkflow für Docker-Apps

Tipp

Diese Inhalte sind ein Auszug aus dem eBook „.NET Microservices Architecture for Containerized .NET Applications“, verfügbar unter .NET Docs oder als kostenlos herunterladbare PDF-Datei, die offline gelesen werden kann.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Der Lebenszyklus der Anwendungsentwicklung beginnt auf Ihrem Computer. Als Entwickler programmieren Sie die Anwendung mit Ihrer bevorzugten Programmiersprache und testen sie lokal. Mit diesem Workflow entwickeln und testen Sie Docker-Container stets lokal, unabhängig davon, welche Sprache, welches Framework und welche Plattform Sie verwenden.

Jeder Container (eine Instanz eines Docker-Images) umfasst die folgenden Komponenten:

  • Eine Betriebssystemauswahl, z. B. eine Linux-Distribution, Windows Nano Server oder Windows Server Core

  • Dateien, die während der Entwicklung hinzugefügt wurden, z. B. Quellcode- und Anwendungsbinärdateien

  • Konfigurationsinformationen, z. B. Umgebungseinstellungen und Abhängigkeiten

Workflow für die Entwicklung von Docker-Container-basierten Anwendungen

In diesem Abschnitt wird der Entwicklungsworkflow für Docker-Container-basierte Anwendungen beschrieben. Der innere Entwicklungsworkflow berücksichtigt nicht den breiteren DevOps-Workflow, der bis zur Produktionsbereitstellung reichen kann. Stattdessen liegt der Fokus nur auf der Entwicklungsarbeit, die auf dem Computer des Entwicklers vorgenommen wird. Die ersten Schritte zum Einrichten der Umgebung wurden nicht berücksichtigt, da diese nur einmal durchgeführt werden.

Eine Anwendung besteht aus Ihren eigenen Diensten sowie zusätzlichen Bibliotheken (Abhängigkeiten). Im Folgenden sind die grundlegenden Schritte dargestellt, die Sie normalerweise beim Erstellen einer Docker-Anwendung ausführen, wie in Abbildung 5-1 dargestellt.

Diagram showing the seven steps it takes to create a containerized app.

Der Entwicklungsprozess für Docker-Apps: 1. Programmieren der App, 2. Schreiben der Dockerfile(s), 3. Erstellen der in der Dockerfile definierten Images, 4. Erstellen von Diensten in der docker-compose.yml-Datei (optional), 5. Ausführen des Containers oder der docker-compose-App, 6. Testen der App oder des Microservices, 7. Mithilfe von Push an das Repository übertragen und Prozedur wiederholen

Abbildung 5-1. Workflowschritte für die Entwicklung von Containeranwendungen für Docker

In diesem Abschnitt wird der gesamte Prozess ausführlich beschrieben, und jeder wichtige Schritt wird unter besonderer Berücksichtigung einer Visual Studio-Umgebung erläutert.

Bei Verwendung eines Editor-/CLI-Entwicklungsansatzes (z. B. Visual Studio Code plus Docker-CLI auf macOS oder Windows) müssen Sie die einzelnen Schritte in der Regel detaillierter kennen als bei Verwendung von Visual Studio. Weitere Informationen zum Arbeiten in einer CLI-Umgebung finden Sie im E-Book Containerized Docker Application lifecycle with Microsoft Platforms and Tools (Lebenszyklus von Docker-Containeranwendungen mit Microsoft-Plattformen und Tools).

Wenn Sie Visual Studio 2022 verwenden, werden viele dieser Schritte für Sie ausgeführt, wodurch sich Ihre Produktivität entscheidend erhöht. Dies gilt insbesondere, wenn Sie Visual Studio 2022 verwenden und Anwendungen mit mehreren Containern das Ziel sind. Visual Studio fügt beispielsweise mit nur einem Mausklick die Dateien Dockerfile und docker-compose.yml mit der Konfiguration für Ihre Anwendung Ihren Projekten hinzu. Wenn Sie die Anwendung in Visual Studio ausführen, wird das Docker-Image erstellt und die Anwendung mit mehreren Containern wird direkt in Docker ausgeführt. Sie haben sogar die Möglichkeit, mehrere Container auf einmal zu debuggen. Diese Features erhöhen Ihre Entwicklungsgeschwindigkeit.

Allerdings bedeutet die Tatsache, dass Visual Studio die Schritte automatisch ausführt, nicht, dass Sie nicht wissen müssen, was im Hintergrund mit Docker geschieht. Aus diesem Grund werden im folgenden Leitfaden alle Schritte ausführlich erläutert.

Image for Step 1.

Schritt 1. Beginnen Sie mit dem Codieren, und erstellen Sie eine erste Anwendungs- oder Dienstbaseline

Das Entwickeln einer Docker-Anwendung ist mit der Art und Weise vergleichbar, wie Anwendungen ohne Docker entwickelt werden. Der Unterschied besteht darin, dass Sie beim Entwickeln für Docker Ihre Anwendung oder Dienste bereitstellen und testen, die in Docker-Containern in Ihrer lokalen Umgebung ausgeführt werden (entweder in einem Setup für eine Linux-VM von Docker oder direkt unter Windows, wenn Sie Windows-Container verwenden).

Einrichten der lokalen Umgebung mit Visual Studio

Stellen Sie zuerst sicher, dass Docker Desktop for Windows für Windows wie nachfolgend erläutert installiert wurde:

Erste Schritte mit Docker Desktop für Windows

Außerdem benötigen Sie Visual Studio 2022, Version 17.0, mit installierter Workload .ASP.NET und Webentwicklung, wie in Abbildung 5-2 gezeigt.

Screenshot of the .NET Core cross-platform development selection.

Abbildung 5-2. Auswahl der Workload ASP.NET und Webentwicklung während des Setups von Visual Studio 2022

Sie können mit dem Programmieren Ihrer Anwendung in einer einfachen .NET-Umgebung beginnen (normalerweise in .NET Core oder höher, wenn Sie Container verwenden möchten), noch bevor Sie Docker in Ihrer Anwendung aktivieren und in Docker Bereitstellungen und Tests durchführen. Allerdings wird empfohlen, dass Sie so bald wie möglich mit Docker arbeiten, d.h. in der realen Umgebung, sodass etwaige Probleme schnellstmöglich erkannt werden können. Dies wird empfohlen, da Visual Studio die Arbeit mit Docker so erleichtert, dass sie fast transparent erscheint – insbesondere beim Debuggen von Anwendungen mit mehreren Containern über Visual Studio.

Zusätzliche Ressourcen

Image for Step 2.

Für jedes benutzerdefinierte Image, das Sie erstellen möchten, benötigen Sie eine Dockerfile-Datei. Außerdem benötigen Sie eine Dockerfile-Datei für jeden bereitzustellenden Container unabhängig davon, ob Sie automatisch über Visual Studio oder manuell mithilfe der Docker-CLI bereitstellen (Befehle „docker run“ und „docker-compose“). Wenn Ihre Anwendung einen benutzerdefinierten Dienst enthält, benötigen Sie eine einzelne Dockerfile-Datei. Wenn Ihre Anwendung mehrere Dienste enthält (wie in einer Microservicearchitektur), benötigen Sie pro Dienst eine Dockerfile-Datei.

Die Dockerfile-Datei befindet sich im Stammordner der Anwendung oder des Diensts. Sie enthält die Befehle, die Docker mitteilen, wie die Anwendung oder der Dienst in einem Container eingerichtet und ausgeführt werden sollen. Sie können eine Dockerfile-Datei manuell im Code erstellen und mit Ihren .NET-Abhängigkeiten Ihrem Projekt hinzufügen.

Mit Visual Studio und den Tools für Docker erledigen Sie diese Aufgabe mit einigen wenigen Mausklicks. Beim Erstellen eines neuen Projekts in Visual Studio 2022 ist eine Option Docker-Unterstützung aktivieren verfügbar, wie in Abbildung 5-3 dargestellt.

Screenshot showing Enable Docker Support check box.

Abbildung 5-3. Aktivieren von Docker-Unterstützung beim Erstellen eines neuen ASP.NET Core-Projekts in Visual Studio 2022

Sie können die Docker-Unterstützung auch für ein vorhandenes ASP.NET Core-Web-App-Projekt aktivieren, indem Sie mit der rechten Maustaste auf das Projekt im Projektmappen-Explorer klicken und Hinzufügen>Docker-Unterstützung... auswählen, wie in Abbildung 5.4 dargestellt.

Screenshot showing the Docker Support option in the Add menu.

Abbildung 5-4. Aktivieren der Unterstützung für Docker in einem vorhandenen Visual Studio 2022-Projekt

Durch diesen Vorgang wird dem Projekt eine Dockerfile mit der erforderlichen Konfiguration hinzugefügt, die nur für ASP.NET Core-Projekte verfügbar ist.

Visual Studio kann auf ganz ähnliche Weise eine docker-compose.yml-Datei für die gesamte Projektmappe hinzufügen. Dazu wird die Option Hinzufügen > Containerorchestratorunterstützung... verwendet. In Schritt 4 wird diese Option genauer erläutert.

Verwenden eines vorhandenen offiziellen .NET Docker-Images

Sie erstellen ein benutzerdefiniertes Image für den Container in der Regel auf einem Basisimage, das Sie von einem offiziellen Repository in der Docker-Hub-Registrierung erhalten. Das ist genau das, was im Hintergrund geschieht, wenn Sie die Docker-Unterstützung in Visual Studio aktivieren. Die Dockerfile verwendet ein vorhandenes dotnet/core/aspnet-Image.

Es wurde bereits erläutert, welche Docker-Images und Repositorys Sie abhängig vom Framework und Betriebssystem, das Sie ausgewählt haben, verwenden können. Wenn Sie beispielsweise ASP.NET Core (Linux oder Windows) verwenden möchten, muss das Image mcr.microsoft.com/dotnet/aspnet:8.0 verwendet werden. Aus diesem Grund müssen Sie nur angeben, welche Docker-Basisimages Sie für den Container verwenden werden. Fügen Sie hierzu FROM mcr.microsoft.com/dotnet/aspnet:8.0 Ihrer Dockerfile hinzu. Dies wird automatisch von Visual Studio ausgeführt, aber wenn Sie die Version aktualisieren müssen, aktualisieren Sie diesen Wert.

Durch Verwendung eines offiziellen .NET Image-Repositorys von Docker-Hub mit einer Versionsnummer wird sichergestellt, dass die gleichen Sprachfunktionen auf allen Computern (Entwicklung, Test und Produktion) verfügbar sind.

Das folgende Beispiel veranschaulicht eine Dockerfile-Beispieldatei für einen ASP.NET Core-Container.

FROM mcr.microsoft.com/dotnet/aspnet:8.0
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", " MySingleContainerWebApp.dll "]

In diesem Fall basiert das Image auf Version 8.0 des offiziellen ASP.NET Core-Docker-Images (mehrere Architekturen für Linux und Windows). Dies ist die Einstellung FROM mcr.microsoft.com/dotnet/aspnet:8.0. (Weitere Informationen zu diesem Basisimage finden Sie unter ASP.NET Core-Docker-Image.) In der Dockerfile-Datei müssen Sie auch angeben, dass Docker an dem TCP-Port lauscht, den Sie zur Runtime verwenden (in diesem Fall Port 80 gemäß Konfiguration mit der EXPOSE-Einstellung).

Je nach Sprache und Framework, die Sie verwenden, können Sie zusätzliche Konfigurationseinstellungen in der Dockerfile angeben. Beispielsweise weist die ENTRYPOINT-Zeile mit ["dotnet", "MySingleContainerWebApp.dll"] Docker an, eine .NET-Anwendung auszuführen. Wenn Sie das SDK und die .NET-CLI (Dotnet-CLI) verwenden, um die .NET-Anwendung zu entwickeln und auszuführen, wird eine andere Einstellung verwendet. Entscheidend ist, dass die ENTRYPOINT-Zeile und andere Einstellungen je nach Sprache und Plattform variieren können, die Sie für Ihre Anwendung auswählen.

Zusätzliche Ressourcen

Verwenden von Repositorys für Images für mehrere Architekturen

Ein Repository kann Plattformvarianten enthalten, wie z.B. ein Linux-Image und ein Windows-Image. Dieses Feature ermöglicht Anbietern wie Microsoft (Basisimageentwickler), ein Repository für mehrere Plattformen (Linux und Windows) zu entwickeln. Das in der Docker-Hub-Registrierung verfügbare NET-Repository bietet beispielsweise Unterstützung für Linux und Windows Nano Server, indem der gleiche Repositoryname verwendet wird.

Wenn Sie ein explizites Tag für eine bestimmte Plattform angeben, wie in den folgenden Fällen:

  • mcr.microsoft.com/dotnet/aspnet:8.0-bullseye-slim
    Ziele: Nur .NET 8-Runtime unter Linux

  • mcr.microsoft.com/dotnet/aspnet:8.0-nanoserver-ltsc2022
    Ziele: Nur .NET 8-Runtime unter Windows Nano Server

Die Images für mehrere Architekturen (z. B. das aspnet-Image) verwenden bei Angabe des gleichen Imagenamens, auch mit dem gleichen Tag, je nach dem von Ihnen bereitgestellten Docker-Host-Betriebssystem die Linux- oder die Windows-Version, was im folgenden Beispiel dargestellt wird:

  • mcr.microsoft.com/dotnet/aspnet:8.0
    Mehrfacharchitektur: Nur .NET 8-Runtime unter Linux und Windows Nano Server abhängig vom Betriebssystem des Docker-Hosts

Wenn Sie also ein Image von einem Windows-Host pullen, wird die Windows-Variante gepullt. Wenn der gleiche Imagename von einem Linux-Host gepullt wird, wird die Linux-Variante gepullt.

Mehrstufige Builds in einer Dockerfile

Die Dockerfile ähnelt einem Batchskript. Die Vorgehensweise ist in etwa so wie beim Einrichten des Computers über die Befehlszeile.

Sie beginnen mit einem Basisimage, das den Anfangskontext einrichtet, was dem Startdateisystem ähnelt, das sich auf dem Hostbetriebssystem befindet. Dabei handelt es sich jedoch nicht um ein Betriebssystem, Sie können es sich eher als „das“ Betriebssystem innerhalb des Containers vorstellen.

Durch die Ausführung jeder Befehlszeile wird eine neue Ebene im Dateisystem erstellt, wobei jede Ebene die Änderungen der vorherigen enthält. Das gesamte Konstrukt ergibt zusammen das Dateisystem.

Da jede Ebene auf der vorherigen „aufliegt“, und die daraus resultierende Imagegröße mit jedem Befehl zunimmt, können Images sehr groß werden, wenn darin beispielsweise das für die Erstellung und Veröffentlichung einer Anwendung benötigte SDK enthalten sein muss.

Zu diesem Zeitpunkt kommen mehrstufige Builds (ab Docker 17.05 und höher) ins Spiel.

Die Kernidee dahinter ist, dass Sie den Ausführungsprozess für die Dockerfile in Stufen aufteilen können, wobei jede Stufe ein anfängliches Image darstellt, auf das mindestens ein Befehl folgt. Die letzte Stufe bestimmt die endgültige Imagegröße.

Zusammengefasst bedeutet das also, dass für mehrstufige Builds die Erstellung in unterschiedlichen „Phasen“ ablaufen kann und das endgültige Image dann ausschließlich mit den relevanten Verzeichnissen aus den weiterführenden Stufen erstellt wird. Die allgemeine Strategie zur Verwendung dieses Features ist die folgende:

  1. Verwenden Sie ein SDK-Basisimage (dabei ist die Größe nicht wichtig) mit allen Elementen, die zum Erstellen und Veröffentlichen der Anwendung in einem Ordner erforderlich sind.

  2. Verwenden Sie anschließend ein kleines Image, das nur als Runtime dient, und kopieren Sie den Veröffentlichungsordner aus der vorherigen Stufe, um ein kleineres endgültiges Image zu erstellen.

Sie können den mehrstufigen Prozess am besten verstehen, wenn Sie eine Dockerfile ausführlich durchlaufen, und zwar Zeile für Zeile. Beginnen wir also mit der ursprünglichen Dockerfile, die von Visual Studio erstellt wird, wenn zu einem Projekt Docker-Unterstützung hinzugefügt wird. Später erhalten Sie noch mehr Informationen zu einigen Optimierungen.

Die ursprüngliche Dockerfile kann wie folgt aussehen:

 1  FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
 2  WORKDIR /app
 3  EXPOSE 80
 4
 5  FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
 6  WORKDIR /src
 7  COPY src/Services/Catalog/Catalog.API/Catalog.API.csproj …
 8  COPY src/BuildingBlocks/HealthChecks/src/Microsoft.AspNetCore.HealthChecks …
 9  COPY src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions.HealthChecks …
10  COPY src/BuildingBlocks/EventBus/IntegrationEventLogEF/ …
11  COPY src/BuildingBlocks/EventBus/EventBus/EventBus.csproj …
12  COPY src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj …
13  COPY src/BuildingBlocks/EventBus/EventBusServiceBus/EventBusServiceBus.csproj …
14  COPY src/BuildingBlocks/WebHostCustomization/WebHost.Customization …
15  COPY src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions …
16  COPY src/BuildingBlocks/HealthChecks/src/Microsoft.Extensions …
17  RUN dotnet restore src/Services/Catalog/Catalog.API/Catalog.API.csproj
18  COPY . .
19  WORKDIR /src/src/Services/Catalog/Catalog.API
20  RUN dotnet build Catalog.API.csproj -c Release -o /app
21
22  FROM build AS publish
23  RUN dotnet publish Catalog.API.csproj -c Release -o /app
24
25  FROM base AS final
26  WORKDIR /app
27  COPY --from=publish /app .
28  ENTRYPOINT ["dotnet", "Catalog.API.dll"]

Hier sind die Details zeilenweise dargestellt:

  • Linie 1: Beginnen Sie eine Stufe mit einem „kleinen“ Basisimage, das nur als Runtime dient. Nennen Sie es zu Referenzzwecken base.

  • Zeile 2: Erstellen Sie das Verzeichnis /app im Image.

  • Zeilen 3: Machen Sie den Port 80 verfügbar.

  • Zeilen 5: Beginnen Sie eine neue Stufe mit einem „großen“ Image zur Erstellung/Veröffentlichung. Nennen Sie es zu Referenzzwecken build.

  • Zeilen 6: Erstellen Sie das /src-Verzeichnis im Image.

  • Zeilen 7: Kopieren Sie bis zur Zeile 16 die CSPROJ-Projektdateien, auf die verwiesen wird, damit Sie Pakete zu einem späteren Zeitpunkt wiederherstellen können.

  • Zeile 17: Stellen Sie die Pakete für das Catalog.API-Projekt und die Projekte, auf die verwiesen wird, wieder her.

  • Zeilen 18: Kopieren Sie alle Verzeichnisstrukturen für die Projektmappe (mit Ausnahme der Dateien und Verzeichnisse in der DOCKERIGNORE-Datei) in das Verzeichnis /src im Image.

  • Zeilen 19: Ändern Sie den aktuellen Ordner in das Catalog.API-Projekt.

  • Zeilen 20: Erstellen Sie das Projekt (und andere Projektabhängigkeiten) sowie die Ausgabe im Verzeichnis /app im Image.

  • Zeile 22: Beginnen Sie eine weitere neue Stufe aus dem Build. Nennen Sie sie zu Verweiszwecken publish.

  • Zeile 23: Veröffentlichen Sie das Projekt (und die Abhängigkeiten) sowie die Ausgabe im Verzeichnis /app im Image.

  • Zeile 25: Beginnen Sie eine neue Stufe aus dem Image base, und nennen Sie sie final.

  • Zeile 26: Ändern Sie das aktuelle Verzeichnis in /app.

  • Zeile 27: Kopieren Sie das Verzeichnis /app aus der Stufe publish in das aktuelle Verzeichnis.

  • Zeile 28: Definieren Sie den Befehl, der ausgeführt werden soll, sobald der Container gestartet wird.

Lernen Sie nun einige Optimierungen kennen, die Ihnen dabei helfen, die gesamte Prozessleistung zu verbessern. Im Falle von eShopOnContainers bedeutet dies, dass es 22 Minuten oder länger dauert, um die gesamte Projektmappe in Linux-Containern zu erstellen.

Sie nutzen die Cachefunktion für die Ebenen in Docker. Das ist ganz einfach: Wenn sich das Basisimage und die Befehle nicht von den zuvor ausgeführten unterscheiden, kann einfach die sich daraus resultierende Ebene verwendet werden, ohne dass die Befehle ausgeführt werden müssen, wodurch Sie Zeit sparen.

Konzentrieren wir uns daher auf die build-Stufe: Die Zeilen 5–6 unterscheiden sich kaum, jedoch unterscheiden sich die Zeilen 7–17 für jeden eShopOnContainers-Dienst, weshalb sie jedes Mal ausgeführt werden müssen. Ganz anders sieht es aus, wenn Sie die Zeilen 7–16 wie folgt ändern:

COPY . .

Die Vorgehensweise wäre dann für jeden Dienst die gleiche: Die gesamte Projektmappe wird kopiert, und es wird eine größere Ebene erstellt. Dazu ist jedoch Folgendes zu beachten:

  1. Der Kopiervorgang würde nur zum ersten Mal ausgeführt (und bei einer Neuerstellung, wenn eine Datei geändert wird) und würde den Cache für alle anderen Dienste verwenden.

  2. Da das größere Image in einem Zwischenstadium auftritt, wirkt es sich nicht auf die endgültige Imagegröße aus.

Die nächste wichtige Optimierung umfasst den restore-Befehl, der in Zeile 17 ausgeführt wird, der sich jedoch auch für jeden Dienst von eShopOnContainers unterscheidet. Wenn Sie also diese Zeile nur wie folgt ändern...

RUN dotnet restore

...dann werden die Pakete für die gesamte Projektmappe wiederhergestellt. Dies geschieht jedoch nur ein einziges Mal und nicht 15 Mal wie bei der aktuellen Strategie.

Jedoch wird dotnet restore nur ausgeführt, wenn sich ein einzelnes Projekt oder eine einzelne Projektmappendatei im Ordner befindet. Der Weg dahin ist etwas komplizierter, und der Lösungsweg (ohne dass Sie sich in zu vielen Details verlieren) sieht wie folgt aus:

  1. Fügen Sie der .dockerignore-Datei die folgenden Zeilen hinzu:

    • *.sln, um alle Projektmappendateien in der Hauptordnerstruktur zu ignorieren

    • !eShopOnContainers-ServicesAndWebApps.sln, um nur diese Projektmappendatei einzufügen

  2. Schließen Sie das /ignoreprojectextensions:.dcproj-Argument in dotnet restore ein, damit auch das docker-compose-Projekt ignoriert und nur das Paket für die Projektmappe „eShopOnContainers-ServicesAndWebApps“ wiederhergestellt wird.

Für die letzte Optimierung kann es der Fall sein, dass Zeile 20 redundant wird, da Zeile 23 ebenso die Anwendung erstellt und im Grunde genommen gleich auf Zeile 20 folgt. So sparen Sie sich daher einen weiteren zeitaufwändigen Befehl.

Die Datei, die sich daraus ergibt, sieht wie folgt aus:

 1  FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
 2  WORKDIR /app
 3  EXPOSE 80
 4
 5  FROM mcr.microsoft.com/dotnet/sdk:8.0 AS publish
 6  WORKDIR /src
 7  COPY . .
 8  RUN dotnet restore /ignoreprojectextensions:.dcproj
 9  WORKDIR /src/src/Services/Catalog/Catalog.API
10  RUN dotnet publish Catalog.API.csproj -c Release -o /app
11
12  FROM base AS final
13  WORKDIR /app
14  COPY --from=publish /app .
15  ENTRYPOINT ["dotnet", "Catalog.API.dll"]

Neuerstellung des Basisimages

Sie können ein eigenes Docker-Basisimage von Grund auf neu erstellen. Dieses Szenario wird Docker-Einsteigern nicht empfohlen, aber wenn Sie die bestimmten Bits Ihres eigenen Basisimages festlegen möchten, können Sie dies tun.

Zusätzliche Ressourcen

Image for Step 3.

Schritt 3. Erstellen Sie Ihre benutzerdefinierten Docker-Images, und betten Sie Ihre Anwendung oder Ihren Dienst in diese ein

Sie müssen für jeden Dienst in Ihrer Anwendung ein zugehöriges Image erstellen. Wenn Ihre Anwendung aus einem einzelnen Dienst oder einer einzelnen Webanwendung besteht, benötigen Sie nur ein Image.

Beachten Sie, dass die Docker-Images in Visual Studio automatisch erstellt werden. Die folgenden Schritte sind nur für den Editor-/CLI-Workflow und zur Erläuterung der Vorgänge, die im Hintergrund ablaufen, erforderlich.

Als Entwickler entwickeln und testen Sie so lange lokal, bis Sie ein abgeschlossenes Feature pushen oder zu Ihrem Quellkontrollsystem wechseln (z.B. zu GitHub). Dies bedeutet, dass Sie die Docker-Images erstellen und Container auf einem lokalen Docker-Host (Windows- oder Linux-VM) bereitstellen und für die lokalen Container ausführen, testen und debuggen müssen.

Mithilfe des Befehls „docker build“ können Sie, wie in Abbildung 5-5 gezeigt, unter Verwendung der Docker-CLI und Ihrer Dockerfile-Datei ein benutzerdefiniertes Image in ihrer lokalen Umgebung erstellen.

Screenshot showing the console output of the docker build command.

Abbildung 5-5. Erstellen eines benutzerdefinierten Docker-Images

Optional können Sie, anstatt „docker build“ über den Projektordner direkt auszuführen, zuerst einen bereitstellbaren Ordner mit den erforderlichen .NET-Bibliotheken und Binärdateien generieren, indem Sie erst dotnet publish ausführen und dann den Befehl docker build verwenden.

Dadurch wird ein Docker-Image mit dem Namen cesardl/netcore-webapi-microservice-docker:first erstellt. In diesem Fall ist :first ein Tag, das eine bestimmte Version darstellt. Sie können diesen Schritt für jedes benutzerdefinierte Image wiederholen, das Sie für Ihre zusammengesetzte Docker-Anwendung erstellen müssen.

Wenn eine Anwendung aus mehreren Containern besteht, können Sie auch den Befehl docker-compose up --build verwenden, um alle zugehörigen Images mit einem Befehl unter Verwendung der in den zugehörigen docker-compose.yml-Dateien verfügbar gemachten Metadaten zu erstellen.

Sie können die vorhandenen Images in Ihrem lokalen Repository suchen, indem Sie den „docker images“-Befehl, wie in Abbildung 5-6 dargestellt, verwenden.

Console output from command docker images, showing existing images.

Abbildung 5-6. Anzeigen vorhandener Images mithilfe des Befehls „docker images“

Erstellen von Docker-Images mit Visual Studio

Wenn Sie Visual Studio zum Erstellen eines Projekts mit Docker-Unterstützung verwenden, erstellen Sie nicht explizit ein Image. Stattdessen wird das Image für Sie erstellt, wenn Sie F5 (oder STRG+F5) drücken und die dockerisierte Anwendung oder den dockerisierten Dienst ausführen. Dieser Schritt wird in Visual Studio automatisch und für Sie nicht erkennbar ausgeführt, aber es ist wichtig, dass Sie wissen, was im Hintergrund passiert.

Image for the optional Step 4.

Schritt 4. Definieren Sie Ihre Dienste in der Datei docker-compose.yml beim Erstellen einer Docker-Anwendung mit mehreren Containern

Mithilfe der Datei docker compose.yml können Sie mehrere verwandte Dienste definieren, die als zusammengesetzte Anwendung mit Bereitstellungsbefehlen bereitgestellt werden sollen. Dadurch werden ebenso die Abhängigkeitsbeziehungen und die Laufzeitkonfiguration konfiguriert.

Wenn Sie eine docker-compose.yml-Datei verwenden möchten, müssen Sie die Datei in Ihrem Haupt- oder Stammlösungsordner mit Inhalt erstellen, der mit dem in dem folgenden Beispiel verwendeten vergleichbar ist:

version: '3.4'

services:

  webmvc:
    image: eshop/web
    environment:
      - CatalogUrl=http://catalog-api
      - OrderingUrl=http://ordering-api
    ports:
      - "80:80"
    depends_on:
      - catalog-api
      - ordering-api

  catalog-api:
    image: eshop/catalog-api
    environment:
      - ConnectionString=Server=sqldata;Port=1433;Database=CatalogDB;…
    ports:
      - "81:80"
    depends_on:
      - sqldata

  ordering-api:
    image: eshop/ordering-api
    environment:
      - ConnectionString=Server=sqldata;Database=OrderingDb;…
    ports:
      - "82:80"
    extra_hosts:
      - "CESARDLBOOKVHD:10.0.75.1"
    depends_on:
      - sqldata

  sqldata:
    image: mcr.microsoft.com/mssql/server:latest
    environment:
      - SA_PASSWORD=Pass@word
      - ACCEPT_EULA=Y
    ports:
      - "5433:1433"

Diese „docker-compose.yml“-Datei ist eine vereinfachte und zusammengeführte Version. Sie enthält die statischen Konfigurationsdaten für jeden Container (z. B. den Namen des benutzerdefinierten Images), das erforderlich ist, sowie Konfigurationsinformationen, die sich nach der Bereitstellungsumgebung richten können, z. B. die Verbindungszeichenfolge. In späteren Abschnitten erfahren Sie, wie Sie die Konfiguration der docker-compose.yml-Datei in mehrere docker-compose-Dateien aufteilen und Werte je nach Umgebung und Ausführungstyp (Debug oder Release) außer Kraft setzen können.

Im Beispiel der Datei „docker-compose.yml“ werden vier Dienste definiert: der webmvc-Dienst (eine Webanwendung), zwei Microservices (ordering-api und basket-api) und ein Datenquellcontainer, sqldata, basierend auf von als Container ausgeführtem SQL Server für Linux. Da jeder Dienst als ein Container bereitgestellt wird, ist für jeden ein Docker-Image erforderlich.

Die docker-compose.yml-Datei gibt nicht nur an, welche Container verwendet werden, sondern auch, wie diese einzeln konfiguriert werden. Die webmvc-Containerdefinition in der .yml-Datei:

  • Verwendet ein vorab erstelltes eshop/web:latest-Image. Sie können jedoch das als Teil der docker-compose-Ausführung zu erstellende Image mit einer zusätzlichen, auf einem build:-Abschnitt in der docker-compose-Datei basierenden Konfiguration konfigurieren.

  • Initialisiert die beiden Umgebungsvariablen (CatalogUrl und OrderingUrl).

  • Leitet den verfügbar gemachten Port 80 für den Container an den externen Port 80 auf dem Hostcomputer weiter.

  • Verknüpft die Web-App mit dem Katalog- und Bestelldienst mit der depends_on-Einstellung. Dies bewirkt, dass der Dienst wartet, bis diese Dienste gestartet werden.

Wir gehen in einem der folgenden Abschnitte erneut auf die Datei docker-compose.yml ein, wenn wir uns damit beschäftigen, wie Microservices und Apps mit mehreren Containern implementiert werden.

Arbeiten mit „docker-compose.yml“ in Visual Studio 2022

Visual Studio 2017 (ab Version 15.8) kann, wie zuvor erwähnt, einem Projekt ein Dockerfile und außerdem einer Projektmappe noch Orchestratorunterstützung für Docker Compose hinzufügen.

Wenn Sie zum ersten Mal Containerorchestratorunterstützung hinzufügen, wie in Abbildung 5.7 gezeigt, erstellt Visual Studio die Dockerfile für das Projekt sowie ein neues Projekt (Dienstbereich) in Ihrer Projektmappe mit mehreren globalen docker-compose*.yml-Dateien. Anschließend wird das Projekt diesen Dateien hinzugefügt. Sie können dann die docker-compose.yml-Dateien öffnen und mit zusätzlichen Features aktualisieren.

Wiederholen Sie diesen Vorgang für alle Projekte, die Sie in die Datei „docker-compose.yml“ einbinden möchten.

Zum Zeitpunkt der Erstellung dieses Dokuments unterstützt Visual Studio Docker Compose-Orchestratoren.

Screenshot showing the Container Orchestrator Support option in the project context menu.

Abbildung 5-7. Hinzufügen von Docker-Unterstützung in Visual Studio 2022 durch Klicken mit der rechten Maustaste auf ein ASP.NET Core-Projekt

Nachdem Sie der Projektmappe in Visual Studio Orchestratorunterstützung hinzugefügt haben, sehen Sie auch einen neuen Knoten (in der docker-compose.dcprojProjektdatei) im Projektmappen-Explorer mit den hinzugefügten docker-compose.yml-Dateien, wie in Abbildung 5.8 dargestellt.

Screenshot of docker-compose node in Solution Explorer.

Abbildung 5-8. Der im Projektmappen-Explorer in Visual Studio 2022 hinzugefügte Strukturknoten docker-compose

Eine Anwendung mit mehreren Containern kann unter Verwendung des Befehls docker-compose up mit einer einzelnen „docker-compose.yml“-Datei bereitgestellt werden. Da Visual Studio jedoch eine Gruppe von Dateien hinzufügt, können Sie abhängig von der Umgebung (Entwicklung oder Produktion) und vom Ausführungstyp (Release oder Debug) Werte außer Kraft setzen. Diese Funktion wird in späteren Abschnitten erläutert.

Image for the Step 5.

Schritt 5. Erstellen Sie Ihre Docker-Anwendung, und führen Sie sie aus

Wenn die Anwendung nur über einen einzelnen Container verfügt, können Sie sie ausführen, indem Sie sie auf dem Docker-Host (VM oder physischer Server) bereitstellen. Enthält Ihre Anwendung dagegen mehrere Dienste, können Sie sie als zusammengesetzte Anwendung bereitstellen, indem Sie entweder einen einzelnen CLI-Befehl (docker-compose up)) oder Visual Studio verwenden, wobei der Befehl dann im Hintergrund ausgeführt wird. Betrachten wir die unterschiedlichen Optionen.

Option A: Ausführen einer Anwendung mit einem einzelnen Container

Verwendung von Docker-CLI

Sie können einen Docker-Container mit dem Befehl docker run, wie in Abbildung 5.9 dargestellt, ausführen:

docker run -t -d -p 80:5000 cesardl/netcore-webapi-microservice-docker:first

Der obige Befehl erstellt bei jeder Ausführung eine neue Containerinstanz aus dem angegebenen Image. Sie können den --name-Parameter verwenden, um dem Container einen Namen zu geben, und anschließend docker start {name} (alternativ die Container-ID oder den automatischen Namen) einsetzen, um eine vorhandene Containerinstanz auszuführen.

Screenshot running a Docker container using the docker run command.

Abbildung 5-9. Ausführen eines Docker-Containers mithilfe des Befehls „docker run“

In diesem Fall bindet der Befehl den internen Port 5000 des Containers an Port 80 des Hostcomputers. Dies bedeutet, dass der Host an Port 80 lauscht und an Port 5000 des Containers weiterleitet.

Der dargestellte Hash ist die Container-ID. Zudem wird ihm ein zufälliger lesbarer Name zugewiesen, wenn die --name-Option nicht verwendet wird.

Verwenden von Visual Studio

Wenn Sie die Containerorchestratorunterstützung nicht hinzugefügt haben, können Sie auch eine einzelne Container-App in Visual Studio ausführen, indem Sie STRG+F5 drücken. Sie können auch F5 drücken, um die Anwendung innerhalb des Containers zu debuggen. Der Container wird lokal mit „docker run“ ausgeführt.

Option B: Ausführen einer Anwendung mit mehreren Containern

In den meisten Unternehmensszenarios setzt sich eine Docker-Anwendung aus mehreren Diensten zusammen. Dies bedeutet, dass Sie eine Anwendung mit mehreren Containern, wie in Abbildung 5-10 dargestellt, ausführen müssen.

VM with several Docker containers

Abbildung 5-10. VM mit bereitgestellten Docker-Containern

Verwendung von Docker-CLI

Verwenden Sie den docker-compose up-Befehl, um eine Anwendung mit mehreren Containern mithilfe der Docker-CLI auszuführen. Dieser Befehl stellt mithilfe der docker-compose.yml-Datei, die auf Projektmappenebene verfügbar ist, eine Anwendung mit mehreren Containern bereit. Abbildung 5.11 zeigt die Ergebnisse an, wenn der Befehl aus Ihrem Hauptprojektmappenverzeichnis ausgeführt wird, das die docker-compose.yml-Datei enthält.

Screen view when running the docker-compose up command

Abbildung 5-11. Beispielergebnisse bei der Ausführung des Befehls „docker-compose up“

Nachdem der Befehl „docker-compose up“ ausgeführt wurde, werden wie in Abbildung 5.10 die Anwendung und ihre zugehörigen Container in Ihrem Docker-Host bereitgestellt.

Verwenden von Visual Studio

Das Ausführen einer Anwendung mit mehreren Containern mit Visual Studio 2019 ist denkbar einfach. Drücken Sie einfach STRG+F5 oder nur F5, um den Debugvorgang zu starten und wie üblich dabei das docker-compose-Projekt als Startprojekt festlegen. Visual Studio kümmert sich um die gesamte erforderliche Einrichtung, sodass Sie wie gewohnt Breakpoints erstellen und Ihren Code debuggen können. So entstehen unabhängige Prozesse, die auf Remoteservern ausgeführt werden, wobei der Debugger bereits angefügt ist.

Wie bereits erwähnt, wird jedes Mal, wenn Sie Unterstützung für die Docker-Projektmappe einem Projekt in einer Projektmappe hinzufügen, das Projekt in der globalen docker-compose.yml-Datei (Projektmappenebene) konfiguriert wird, wodurch Sie die gesamte Projektmappe auf einmal ausführen oder debuggen können. Visual Studio startet einen Container für jede Projektmappe, für die die Docker-Projektmappenunterstützung aktiviert ist, und führt alle internen Schritte aus (dotnet publish, docker build usw.).

Wenn Sie einen Blick auf Ihre bisherige Arbeit werfen möchten, sehen Sie sich diese Datei an:

{root solution folder}\obj\Docker\docker-compose.vs.debug.g.yml

Wichtig dabei ist, dass Visual Studio 2019, wie in Abbildung 5-12 dargestellt, über einen zusätzlichen Docker-Befehl für die Aktion der F5-Taste verfügt. Diese Option ermöglicht Ihnen, eine Anwendung mit mehreren Containern auszuführen oder zu debuggen, indem Sie alle in den docker-compose.yml-Dateien auf Projektmappenebene definierten Container ausführen. Die Möglichkeit, Lösungen mit mehreren Containern zu debuggen, bedeutet, das Sie mehrere Haltepunkte festlegen und jeden Haltepunkt in einem anderen Projekt (Container) festlegen können. Während des Debuggings in Visual Studio halten Sie an Haltepunkten an, die in verschiedenen Projekten definiert sind und in verschiedenen Containern ausgeführt werden.

Screenshot of the debug toolbar running a docker-compose project.

Abbildung 5-12. Ausführen von Apps mit mehreren Containern in Visual Studio 2022

Zusätzliche Ressourcen

Ein Hinweis zum Testen und Bereitstellen mit Orchestratoren

Die Befehle „docker-compose up“ und „docker run“ (oder Ausführen und Debuggen der Container in Visual Studio) sind zum Testen von Containern in der Entwicklungsumgebung geeignet. Verwenden Sie diesen Ansatz jedoch nicht für Produktionsbereitstellungen. Für diese sollten Sie Orchestratoren wie Kubernetes oder Service Fabric anzielen. Wenn Sie Kubernetes verwenden, müssen Sie Pods zum Organisieren von Containern einsetzen sowie Dienste, um diese zu vernetzen. Sie können ebenso Bereitstellungen verwenden, um die Erstellung und Änderung von Pods zu organisieren.

Image for the Step 6.

Schritt 6. Testen Sie die Docker-Anwendung mithilfe des lokalen Docker-Hosts

Dieser Schritt hängt davon ab, welche Vorgänge die Anwendung ausführt. In einer einfachen .NET-Webanwendung, die als einzelner Container oder Dienst bereitgestellt wird, können Sie auf den Dienst zugreifen, indem Sie einen Browser auf dem Docker-Host öffnen und, wie in Abbildung 5-13 gezeigt, zu dieser Site navigieren. (Wenn die Konfiguration in der Docker-Datei den Container einem anderen Port als Port 80 auf dem Host zuordnet, berücksichtigen Sie den Host-Port in der URL.)

Screenshot of the response from localhost/API/values.

Abbildung 5-13. Beispiel für das lokale Testen der Docker-Anwendung mithilfe von „localhost“

Wenn „localhost“ nicht auf die Docker-Host-IP verweist (was bei Verwendung von Docker CE jedoch standardmäßig der Fall sein sollte), verwenden Sie die IP-Adresse der Netzwerkkarte Ihres Computers, um zum Dienst zu navigieren.

Diese URL verwendet im Browser Port 80 für das besprochene Containerbeispiel. Allerdings werden intern die Anforderungen zu Port 5000 umgeleitet, da die Bereitstellung, wie in einem vorherigen Schritt beschrieben, mit dem Befehl „docker run“ erfolgte.

Sie können die Anwendung auch mithilfe von „curl“ über das Terminal testen, was in Abbildung 5-14 dargestellt wird. Bei einer Docker-Installation unter Windows lautet die standardmäßige Docker-Host-IP zusätzlich zur eigentlichen IP-Adresse Ihres Computers stets 10.0.75.1.

Console output from getting the http://10.0.75.1/API/values with curl.

Abbildung 5-14. Beispiel für das Testen der Docker-Anwendung mithilfe von „curl“

Testen und Debuggen von Containern mit Visual Studio 2022

Beim Ausführen und Debuggen des Containers mit Visual Studio 2022 können Sie die .NET-Anwendung ähnlich wie bei einer Ausführung ohne Container debuggen.

Testen und Debuggen ohne Visual Studio

Wenn Sie mit dem Editor-/CLI-Ansatz entwickeln, ist das Debuggen von Containern schwieriger. Sie möchten wahrscheinlich debuggen, indem Ablaufverfolgungen generiert werden.

Zusätzliche Ressourcen

Vereinfachter Workflow bei der Entwicklung von Containern mit Visual Studio

Der Workflow bei Verwendung von Visual Studio ist wesentlich einfacher als bei Verwendung des Editor/CLI-Ansatzes. Die meisten der von Docker vorausgesetzten Schritte in Zusammenhang mit der Dockerfile-Datei und den docker-compose.yml-Dateien werden, wie in Abbildung 5-15 gezeigt, von Visual Studio ausgeblendet oder vereinfacht.

Diagram showing the five simplified steps it takes to create an app.

Der Entwicklungsprozess für Docker-Apps: 1. Programmieren der App, 2. Schreiben der Dockerfile(s), 3. Erstellen der in der Dockerfile definierten Images, 4. Erstellen von Diensten in der docker-compose.yml-Datei (optional), 5. Ausführen des Containers oder der docker-compose-App, 6. Testen der App oder des Microservices, 7. Mithilfe von Push an das Repository übertragen und Prozedur wiederholen

Abbildung 5-15. Vereinfachter Workflow bei der Entwicklung mit Visual Studio

Darüber hinaus müssen Sie Schritt 2 (Hinzufügen von Docker-Unterstützung in Ihren Projekten) nur einmal ausführen. Aus diesem Grund ähnelt der Workflow den üblichen Entwicklungsaufgaben bei Verwendung von .NET für andere Entwicklungsarbeiten. Sie müssen wissen, was im Hintergrund passiert (Imageerstellungsprozess, verwendete Basisimages, Bereitstellung von Containern usw.), und in einigen Fällen müssen Sie auch die Dockerfile oder die docker-compose.yml-Datei bearbeiten. Aber der Großteil der Arbeit wird durch Verwendung von Visual Studio, stark vereinfacht, und Ihre Produktivität wird entscheidend erhöht.

Verwenden von PowerShell-Befehlen in einer Dockerfile-Datei zum Einrichten von Windows-Containern

Windows-Container ermöglichen es Ihnen, Ihre vorhandenen Windows-Anwendungen in Docker-Images zu konvertieren und diese mit den gleichen Tools wie den Rest des Docker-Ökosystems bereitzustellen. Um Windows-Container zu verwenden, führen Sie PowerShell-Befehle in der Dockerfile-Datei aus, was im folgenden Beispiel gezeigt wird:

FROM mcr.microsoft.com/windows/servercore
LABEL Description="IIS" Vendor="Microsoft" Version="10"
RUN powershell -Command Add-WindowsFeature Web-Server
CMD [ "ping", "localhost", "-t" ]

In diesem Fall wird ein Windows Server Core-Basisimage (FROM-Einstellung) verwendet und IIS wird mit einem PowerShell-Befehl ausgeführt (RUN-Einstellung). Auf ähnliche Weise können Sie auch mithilfe von PowerShell-Befehlen zusätzliche Komponenten wie ASP.NET 4.x, .NET Framework 4.6 oder eine beliebige andere Windows-Software einrichten. Der folgende Befehl in einer Dockerfile-Datei richtet z.B. ASP.NET 4.5 ein:

RUN powershell add-windowsfeature web-asp-net45

Zusätzliche Ressourcen