Dezember 2017

Band 32, Nummer 12

Container: Modernisieren einer .NET-App mit Docker und Windows Server-Containern

Von Sean Iannuzzi

Inzwischen haben Sie wahrscheinlich schon von Docker, Docker-Containern und, mit der Einführung von Windows Server 2016, von integrierten Windows Server-Containern gehört. Ich bin fest davon überzeugt, dass Docker-Container innerhalb weniger Jahre zum Standard für das Ausführen von Websites, Anwendungen und anderen Systemen werden, im Gegensatz zur Ausführung von virtuellen Computern (VMs) zur Unterstützung von Anwendungen. Die Verwendung von Docker ermöglicht Skalierbarkeit, Isolierung und Sicherheit und stellt gleichzeitig sicher, dass Anwendungen und Systeme ordnungsgemäß konfiguriert werden – mit wenig Unterstützung aus Sicht der Bereitstellung. Verglichen mit der Komplexität des Einrichtens eines virtuellen Computers und der Konfiguration aller erforderlichen Features ist die Einfachheit des Docker-Setups sehr vorteilhaft. So wie die Anforderungen an physische Computer allmählich zugunsten von virtuellen Computern abgebaut wurden, ist es wahrscheinlich, dass Docker in den nächsten Jahren die Notwendigkeit von virtuellen Computern ersetzen wird – wenn nicht sogar früher.

In diesem Artikel beschreibe ich, wie ich einen Containeransatz mithilfe von Windows Server 2016, Dateifreigabe und Socketkommunikation mit Windows Server-Containern zur Modernisierung mehrerer .NET-Anwendungen genutzt habe. Ich werde auf die Details eingehen, wie ich mit Windows PowerShell ein Docker-Image erstellt und Dateien und Sockets zwischen einem Windows Server 2016-Hostsystem und einem Windows Server-Container freigegeben habe. Es ist wahrscheinlich, dass viele Ihrer Anwendungen über allgemeine Funktionen wie diese verfügen, die Sie aktivieren müssen, um sicherzustellen, dass Ihre .NET-Anwendung in einen Docker-Container portiert werden kann. Viele der Features, die ich hier bespreche, sind nicht für Windows Server-Container spezifisch und können für alle Anwendungen mit ähnlicher Funktionalität genutzt werden.  

Geschäftliche Herausforderungen: Eine .NET-Anwendung, die CPU, Arbeitsspeicher und Datenträger intensiv nutzt

Meine geschäftliche Herausforderung bestand darin, mehrere vorhandene .NET- und C++-Konsolenanwendungen zu modernisieren, die für die Verarbeitung großer Datenmengen verantwortlich sind. Dafür wurden CPU, Arbeitsspeicher und Datenträger sehr intensiv genutzt. Ich musste diese Konsolenanwendungen in einem traditionelleren Webmodell bereitstellen, in dem das System von einem System für einen einzelnen Benutzer zu einem unterstützten Setup für mehrere Benutzer migriert wurde. Angesichts der Art und Weise, wie die Anwendungen eingerichtet wurden und der Datenmenge, die verarbeitet wurde, wollte ich nicht mehrere Kopien der Daten oder ausführbaren Dateien über virtuelle Computer hinweg verwalten.

Als Teil dieser geschäftlichen Herausforderung musste ich herausfinden, wie ich diese Anwendungen am besten erweitern und die Netzwerklatenz und die Dateiverwaltung im gesamten Netzwerk minimieren konnte. Die Leistung der Anwendungen war ausschlaggebend, und jede Verwendung von Netzwerkfreigaben, Dateifreigaben oder anderer verteilter Verarbeitung würde die Leistung erheblich beeinträchtigen. Um diese geschäftliche Herausforderung als erfolgreich zu betrachten, musste ich daher ein skalierbares Modell zur Verfügung stellen, das auch eine hohe Leistung erbrachte (in Bezug auf CPU, Arbeitsspeicher und Datenträger-E/A), ohne mehrere Kopien meiner Daten verwalten zu müssen. Wie bei den meisten Projekten war der Zeitrahmen für die Bereitstellung einer neu modernisierten und skalierbaren Version dieser Anwendungen sehr begrenzt, sodass eine vollständige Neugestaltung nicht möglich war.

Wichtige Features: Dateifreigabe, Socketverbindungen und .NET in Docker

Für meine speziellen Anwendungen habe ich verschiedene Optionen in Betracht gezogen, bevor ich bei Docker und insbesondere Windows Server-Containern gelandet bin. Im Rahmen meiner Auswertung hatte ich drei sehr spezifische technische Herausforderungen zu bewältigen, um die Anwendungen erfolgreich zu Docker zu migrieren: 

  • Ausführen einer traditionellen .NET-App in Docker.
  • Nutzung der Dateifreigabe zwischen dem Hostsystem und meinem Docker-Container.
  • Ermöglichen der Socketkommunikation zwischen dem Host und dem Docker-Container.

Ich zeige Ihnen im Detail, wie Sie diese technischen Herausforderungen meistern und die Konzepte mit Docker und Windows Server-Containern unter Windows Server 2016 implementieren können. Die Konzepte selbst sind erst der Anfang, wenn man bedenkt, wie viele Ihrer .NET-Anwendungen potenziell zu Docker oder Windows Server-Containern migriert werden können. Die Beispiele, die ich hier vorstelle, können auf verschiedene Anwendungsfeatures angewendet oder erweitert werden, was wiederum eine modernere Bereitstellung Ihrer Anwendungen ermöglicht.

Anwendungsleistung: 8 GB RAM, 10 TB Dateiverarbeitung

Bevor ich zu tief in die Optionen und Konzepte eintauche, die ich in Betracht gezogen habe, möchte ich ein wenig mehr Details zu den Anwendungen und Systemen aufzeigen, die ich in Docker-Container verlagert habe. Die Anwendungen sind in erster Linie einzigartig hinsichtlich der Art der Aufgaben, die sie ausführen, und sie nutzen CPU, Arbeitsspeicher und Datenträger intensiv. Darüber hinaus ist die Geschwindigkeit, mit der die Anwendungen arbeiten, entscheidend für den Erfolg des Systems.

Meine Anwendungen, die hauptsächlich für einen einzelnen Benutzer konzipiert sind, führen sehr komplexe Berechnungen aus. Sie verarbeiten Datendateien und wurden mit einer Kombination aus C++ und .NET Framework erstellt. Um Ihnen eine Vorstellung von den Leistungsherausforderungen meines Systems zu geben: Es benötigt ungefähr 8 GB RAM pro Benutzer, um Berechnungen für Datendateien auszuführen, die größer als 10 TB sind, und es benötigt vorab zugewiesenen Speicher und extrem schnelle Datenträgergeschwindigkeiten, um die großen Datenmengen innerhalb von Sekunden zu verarbeiten. Das System verwendet außerdem Socketverbindungen für den Aufruf und die Benachrichtigung vom Anforderer. Als sich die Anwendungen und Systeme weiterentwickelten, fand ich heraus, dass ich eine schnelle Möglichkeit benötigte, das System zu skalieren und die Verarbeitung durch mehrere Benutzer zu unterstützen. Ich glaube, dass sich viele von Ihnen ähnliche Anwendungen vorstellen können, die davon profitieren würden, wenn sie in einen Container verschoben werden.

Lösungsoptionen: Reengineering, automatische Skalierung, Docker

Die technischen Herausforderungen beinhalteten die Bewertung der verschiedenen Wege, mit denen ich meine Ziele erreichen konnte. Ich habe drei Möglichkeiten in Betracht gezogen.

  1. Reengineering: Eine Möglichkeit bestand darin, die gesamte Anwendungssuite zu überarbeiten. Das würde sicherlich funktionieren, aber angesichts der Größe und Komplexität meines Systems brauchte ich eine Lösung, die weniger Risiko mit sich bringt und nicht so lange dauert. Ein Jahr oder auch nur mehrere Monate zu warten, um das System zu überarbeiten, war nicht akzeptabel. Dennoch war es wichtig, diese Option auszuwerten, falls sie sich als vernünftige Lösung erweisen sollte. 
  2. Automatische Skalierung: Eine weitere Option war die Untersuchung, wie ich virtuelle Computer und automatische Skalierung nutzen könnte. Dies wäre definitiv schneller als das Neuschreiben der Anwendung und würde das Risiko insgesamt verringern. Es würde jedoch viel Mehraufwand verursachen, da die Zuordnung eines virtuellen Computers, insbesondere eines virtuellen Computers mit 10 TB Speicherplatz, sehr viel Zeit in Anspruch nehmen würde. Auch wenn ich dafür Lösungen finden konnte, z.B. die Verwendung von Standbyinstanzen und anschließende Bereitstellung und Aufhebung der Bereitstellung der Server über eine zusätzliche Schicht oder Anwendung, schien es mir immer noch nicht der bestmögliche Ansatz zu sein. Diese Option hat mir jedoch definitiv die richtige Richtung gewiesen, da sie kein Reengineering der gesamten Anwendung erforderte und mehrere ausführbare Dateien pro virtuellem Computer bereitstellen und die virtuellen Computer automatisch skalieren konnte. Ich habe mich entschlossen, meine Suche nach einem einfacheren Implementierungsmodell mit einem moderneren technologischen Ansatz fortzusetzen.
  3. Docker-Container: Die letzte Option, die ich in Betracht zog, war die Verwendung von Docker, mit Interoperabilität zwischen dem Hostsystem und den Docker-Containern. Die Verwendung von Docker-Containern würde es mir ermöglichen, das System nach Bedarf zu skalieren, ohne das gesamte System neu zu entwerfen. Dieser Ansatz würde die Risiken, die mit dem Reengineering der Anwendung verbunden sind, mindern, einen Grad an Isolierung für Sicherheitszwecke bieten und es mir ermöglichen, diese Aktualisierungen schnell zu implementieren, während ich immer noch das Maß an Skalierbarkeit bereitstelle, das ich benötige.

Bereitstellen von .NET-Apps mit Docker

Die Hauptprobleme, die ich mit der Docker-Option hatte, bestanden darin, dass die Anwendung in .NET und C++ geschrieben wurde, und ich hatte Bedenken, dass meine Anwendung nicht direkt in Docker ausgeführt werden könnte. Als ich anfing zu recherchieren, wie ich meine .NET-/C++-Anwendungen zu Docker migrieren kann, erfuhr ich, dass ein Upgrade oder ein Neuentwurf erforderlich sein würde. Da mein Ansatz schnell sein musste, begann ich damit, mich ausführlicher über Windows Server 2016 und die vollständig integrierten Windows Server-Container zu informieren. Durch die Nutzung von Windows Server-Containern hatte ich gehofft, dass ich die Anwendung so belassen könnte, wie sie war, und alle Abhängigkeiten zusammen mit dem anderen erforderlichen Setup in meinem Container bereitstellen könnte. Die anfängliche technische Herausforderung, der ich begegnet bin, bestand darin, dass traditionelle Docker-Container für .NET-Anwendungen .NET Core benötigen, während meine Anwendung mit .NET und C++ geschrieben wurde. Natürlich hätte ich auch ein Upgrade der Anwendung auf .NET Core ausführen können, aber das wäre mit erheblichem Aufwand verbunden gewesen, und ich habe versucht, eine Lösung bereitzustellen, die so schnell wie möglich und mit dem geringsten Risiko verbunden war. Ich habe auch versucht, sicherzustellen, dass ich die Fähigkeit zur Skalierung (zusammen mit einem gewissen Grad an Isolation und Sicherheit für meine Anwendung) einbeziehe.

 Obwohl der Einsatz von Windows Server-Containern anfangs sehr vielversprechend aussah, musste ich dennoch eine Reihe verschiedener Konzepte testen (z.B. Dateifreigaben und Socketverbindungen), die auch für Sie sehr nützlich sein können. Obwohl vieles von dem, was ich beschreibe, einzigartig für mein spezielles Setup ist, gilt dies nicht für die Optionen und Konzepte. Diese können für andere Systeme genutzt werden, die diese Art der Migration oder Skalierung erfordern, ohne dass die Anwendung neu entworfen oder neu geschrieben werden muss. Natürlich ersetzt dieser Ansatz nicht eine Neugestaltung der Anwendung, aber er bietet einem Team Zeit, die Anwendung neu zu entwerfen, wenn dies die gewünschte Richtung ist. Im Rahmen dieser Neugestaltung kann das Team ein Reengineering der Anwendung ausführen, das mit Docker kompatibel oder für Docker aktiviert ist.

In den nächsten Abschnitten werde ich Folgendes beschreiben:

  1. Wie ich den virtuellen Windows Server 2016-Computer so einrichte, dass er Windows Server-Container unterstützt.
  2. Wie ich mein Docker-Image mit PowerShell erstellt habe.
  3. Die auf Windows Server Core basierende Docker-Datei.
  4. Aktivieren der erweiterte Dateifreigabe zwischen dem Host und dem Container.
  5. Aktivieren eines Socketlisteners auf dem Host und im Container.

Windows Server 2016 und Container

Zu Beginn habe ich einen virtuellen Windows Server 2016-Computer installiert sowie die entsprechenden Features wie .NET Framework, IIS und Container aktiviert. Abbildung 1 zeigt dies.

Aktivieren von .NET Framework und Containerdiensten
Abbildung 1: Aktivieren von .NET Framework und Containerdiensten

Beachten Sie, dass zum Erstellen dieser Art von Lösung .NET Framework installiert sein muss.

Nachdem ich alle erforderlichen Features installiert hatte, habe ich sie entsprechend überprüft. Um sicherzustellen, dass Docker ordnungsgemäß ausgeführt wird, habe ich den PowerShell-Befehl „docker -version“ ausgeführt. Ich habe dann bestätigt, dass der Windows Docker Engine-Dienst ebenfalls ausgeführt wird, indem ich den Befehl „(get-service "Docker").Status“ aus PowerShell eingegeben habe. Als letzten Schritt habe ich einen Docker Pull Request für das Windows Server Core Docker-Image von dockr.ly/2i7pDSn ausgeführt. Nachdem der Pull Request abgeschlossen war, habe ich überprüft, ob das Docker-Image erfolgreich erstellt wurde, indem ich den Befehl „docker images“ ausgeführt habe.

Nachdem ich die Windows Container Services installiert und die Umgebung mit meinem Docker-Basisimage eingerichtet hatte, war ich bereit, mit der Arbeit mit meiner .NET-Konsolenanwendung zu beginnen.

.NET-App-Setup

Ich habe mit einer sehr einfachen Konsolenanwendung unter Verwendung von .NET Framework 4.6.1 begonnen. Die Anwendung hat wirklich nicht viel anderes geleistet, als ein Argument anzunehmen und eine Antwort anzuzeigen. Bevor ich mit der vollständigen Migration zu einem Windows-Container zu weit ging, wollte ich sicherstellen, dass die erforderliche Funktionalität wie vorgesehen funktionierte. Es gab jedoch eine Reihe von Schritten, die ich ausführen musste, bevor ich die Anwendung in einem Container unter Windows Server 2016 ausführen konnte.

Der erste Schritt war das Erstellen eines wiederverwendbaren PowerShell-„Buildskripts“, das die Anwendung und ein Docker-Image unter Windows Server 2016 erstellt. Um diese Aufgabe zu lösen, habe ich zwei Funktionen geschrieben: eine Funktion zum Ausführen von MSBuild und eine andere zum Erstellen des eigentlichen Docker-Images, wie in Abbildung 2 gezeigt.

Abbildung 2: PowerShell-Funktionen zum Erstellen der Anwendung und eines Docker-Images

Set-StrictMode -Version Latest
$ErrorActionPreference="Stop"
$ProgressPreference="SilentlyContinue"
s
# Docker image name for the application
$ImageName="myconsoleapplication"
function Invoke-MSBuild ([string]$MSBuildPath, [string]$MSBuildParameters) {
  Invoke-Expression "$MSBuildPath $MSBuildParameters"
}
function Invoke-Docker-Build ([string]$ImageName, [string]$ImagePath,
  [string]$DockerBuildArgs = "") {
  echo "docker build -t $ImageName $ImagePath $DockerBuildArgs"
  Invoke-Expression "docker build -t $ImageName $ImagePath $DockerBuildArgs"
}

Der nächste Schritt im Skript war die Ausführung dieser beiden Funktionen, wobei alle erforderlichen Parameter übergeben wurden:

Invoke-MSBuild -MSBuildPath "MSBuild.exe" -MSBuildParameters
  ".\myconsoleapplication.csproj /p:OutputPath=.\publish /p:Configuration=Release"
Invoke-Docker-Build -ImageName $ImageName -ImagePath "."

Mit dem nun vorhandenen Buildskript war es nur noch erforderlich, meine Docker-Datei zu erstellen und meine Konsolenanwendung für Windows Server-Container unter Windows Server 2016 zu aktivieren. Beachten Sie, dass es aus entwicklungstechnischer Sicht hilfreich sein kann, die Visual Studio-Eingabeaufforderung zu verwenden, die MSBuild in Ihrem Pfad enthält, wenn Sie den Buildvorgang testen. Als Teil der vorbereitenden Installation habe ich ein Docker-Basisimage namens „Windows Server Core“ installiert, das alle Basisfunktionen enthielt, die ich für die Ausführung meiner Anwendung benötigte. Beim Erstellen der Docker-Datei habe ich Docker angewiesen, dieses Image zu verwenden und meine Anwendung mit dem Namen „myconsoleapplication.exe“ als Einstiegspunkt zu veröffentlichen: 

FROM microsoft/windowsservercore
ADD publish/ /
ENTRYPOINT myconsoleapplication.exe

Der Einstiegspunkt ist die Main-Funktion in der Konsolenanwendung.

Endgültiger Build & Deployment unter Windows Server 2016

Sobald ich über eine vollständige .NET Konsolenanwendung verfügte, die für Windows Server-Container aktiviert war, war ich bereit, meine Anwendung bereitzustellen. Eine einfache Möglichkeit, dies zum Testen einzurichten, bestand darin, den Anwendungsordner einfach auf den virtuellen Computer zu kopieren. Nachdem ich die Anwendung auf den Server kopiert hatte, habe ich das PowerShell-Skript ausgeführt, um die Anwendung zu erstellen. Ich bin zum Quellverzeichnis navigiert und habe dann den Befehl „./build“ aus PowerShell ausgeführt.

Die Ausgabe des Buildskripts sollte ähnlich aussehen wie das in Abbildung 3 gezeigte Ergebnis.

Abbildung 3: Ausgabe des Buildskripts

docker build -t myconsoleapplication .
Sending build context to Docker daemon  6.058MB
Step 1/3 : FROM microsoft/windowsservercore
 ---> 2cddde20d95d
Step 2/3 : ADD publish/ /
 ---> 452c4b42caa5
Removing intermediate container cafb387a3634
Step 3/3 : ENTRYPOINT myconsoleapplication.exe
 ---> Running in a128ff044ef3
 ---> 4c7dce888b36
Removing intermediate container a128ff044ef3
Successfully built 4c7dce888b36
Successfully tagged myconsoleapplication:latest

Um zu bestätigen, dass mein Docker-Image erfolgreich erstellt wurde, habe ich den Befehl „docker images“ erneut ausgeführt. Ich konnte das neue Docker-Image sehen, wie in Abbildung 4 gezeigt.

Konsolenanwendung als ein Docker-Image
Abbildung 4: Konsolenanwendung als ein Docker-Image

Testen der Konsolen-App des Windows Server-Containers

Der allerletzte Schritt, den ich unternommen habe, bevor ich mich mit einigen sehr spezifischen Funktionen befasst habe, bestand darin, meine Anwendung zu testen, um sicherzustellen, dass sie tatsächlich in einem Windows Server-Container ausgeführt wird. Zu diesem Zweck habe ich folgenden Befehl ausgeführt:

docker run --rm -it myconsoleapplication
  ".NET Framework App Running in Windows Container"

Wie erwartet, gab die Anwendung das Argument aus, das ihr im Konsolenfenster übergeben wurde.

Damit wurden die Grundlagen für die Bereitstellung, Konfiguration und Einrichtung einer .NET-Anwendung geschaffen, die in einem Windows Server-Container ausgeführt werden kann. An dieser Stelle denken Sie vielleicht an viele vorhandene Anwendungen, die Sie ggf. in einen Windows Server-Container verschieben könnten. Es gibt jedoch immer noch einige wichtige Features, die ich als sehr hilfreich empfunden habe (z.B. Dateifreigabe und Socketkommunikation), die auch für Sie nützlich sein könnten. Im nächsten Abschnitt werde ich mich eingehender mit diesen Features und ihrer Nutzung in Ihren eigenen Anwendungen beschäftigen.

Docker-Container: Aktivieren erweiterter Dateifeatures

Wie bei vielen Konsolenanwendungen kann auch bei Ihrer Anwendung eine beträchtliche Anzahl von Dateien vorhanden sein, die aus verschiedenen Gründen genutzt werden. Ob es sich nun um Protokollierung oder Verarbeitung oder um etwas anderes handelt: Die Verwendung von Dateien kann sehr intensiv sein. Für meine speziellen Anwendungen habe ich sehr große Dateien eingelesen und wollte die Dateien nicht in jeden Container kopieren. Außerdem wollte ich mein Bestes geben, um die Datenträger-E/A zu optimieren, aber die Verwendung eines freigegebenen Ordners (Dateiserver) im Netzwerk führte zu viel Latenz und beeinträchtigte die Leistung beim Versuch, so große Dateien einzulesen. Außerdem wollte ich nicht mehrere Versionen meiner Anwendung mit verschiedenen Konfigurationen, Ports und Verzeichnissen erstellen, da dies ein Verwaltungsalbtraum wäre. Daraufhin begann ich zu untersuchen, wie ich meine Dateien auf meinem Hostsystem, auf dem der Docker-Dienst ausgeführt wird, freigeben und dann von meinem Container aus auf diese Dateien zugreifen konnte. Ich habe herausgefunden, dass dies ausgesprochen einfach ist, und es spielt keine Rolle, ob Sie Windows Server-Container oder einen Docker-Container unter Linux verwenden. Docker bietet vollständige Unterstützung für diese Art von Funktionalität. Tatsächlich war das, was für mein Setup am vorteilhaftesten war, dass ich nicht einmal meine Anwendung ändern musste, solange ich ein Laufwerk im Container bereitstellte, um die Korrelation mit den internen Verzeichnissen zu erzielen. Die einzige Änderung, die ich vorgenommen habe, bestand darin, dass ein Parameter meinen Pfad festlegt, wenn ich den Docker-Container starte, anstatt ihn aus einer Konfigurationsdatei zu lesen.

Ich war in der Lage, alle Dateiverarbeitungen und Pfade intakt zu halten, da sie alle relative Pfade waren und sich in einem Hauptverzeichnis befanden. Das bedeutete, dass ich die Kernlogik meiner Anwendung nicht ändern musste, was wiederum einen Großteil des Risikos verringerte, das ich durch die Änderung meiner .NET-Konsolenanwendung möglicherweise eingegangen wäre.

Um diese Funktionalität zu testen, habe ich meiner Konsolenanwendung einen einfachen Datei-E/A-Prozess hinzugefügt, indem ich den folgenden Code einfügt habe:

using (StreamWriter sw = File.AppendText(@"C:\containertmp\testfile.txt"))
{
  sw.WriteLine(DateTime.Now.ToString() + " - " + args[0]);
}

Anschließend habe ich meine Lösung auf dem virtuellen Windows Server 2016-Computer bereitgestellt. Dies erforderte auch eine Neuerstellung des Images durch Ausführen des PowerShell-Skripts „./build“.

Der letzte Schritt, um diese Funktionalität zu aktivieren, war die Erstellung eines Verzeichnisses auf dem Host, das ich für den Docker-Container bereitstellen musste. In meinem Fall habe ich einfach einen Ordner namens „hostcontainershare“ erstellt. Der Schlüssel dazu war die Art der Bereitstellung dieses Ordners aus dem Windows Server-Hostsystem im Docker-Container. Überraschenderweise ist dies sehr einfach zu bewerkstelligen, indem das folgende Argument an den Befehl „docker run“ übergeben wird:

-v [source directory or path]:[container directory or path]

Dieses Argument wird so eingerichtet, dass es eine Quelle und ein Ziel akzeptiert. Ich habe z.B. zuerst mein lokales Windows Server-Hostverzeichnis übergeben und dann angegeben, wie ich es im Container bereitstellen möchte. Dies ist der gesamte Befehl „docker run“:

docker run --rm -it -v c:\hostcontainershare:c:\containertmp myconsoleapplication
  ".NET Framework App Writing to Host Folder" 1

Es gibt verschiedene Möglichkeiten, diese Funktionalität sowohl in Windows Server-Containern als auch in Docker-Containern zu erreichen, aber für meine .NET-Konsolenanwendung fand ich diese Methode sehr einfach und leicht zu implementieren. Eine Darstellung zur Einrichtung finden Sie in Abbildung 5.

Dateivorgänge des Hosts und des Windows Server-Containers: Übersicht
Abbildung 5: Dateivorgänge des Hosts und des Windows Server-Containers: Übersicht

Das Ergebnis des Befehls „docker run“ war eine Datei, die aus meinem Docker-Container in mein Hostverzeichnis geschrieben wurde, wie in Abbildung 6 gezeigt.

Beispiel für Schreibzugriff aus dem Docker-Container auf den Windows Server 2016-Host
Abbildung 6: Beispiel für Schreibzugriff aus dem Docker-Container auf den Windows Server 2016-Host

Das Aktivieren dieser Funktionalität brachte für meine Anwendung erhebliche Vorteile mit sich, da sie mit sehr großen Dateien arbeitet. Ich fand heraus, dass ich meine Dateien nicht über alle Container hinweg duplizieren musste. Solange ich optimale oder Solid State Drives auf meinem Host verwende, ist die Dateiverarbeitung viel schneller als die Verwendung eines freigegebenen Ordners, eines Netzlaufwerks oder einer anderen nicht lokalen Website. Die Vorteile dieser Technik sind für herkömmliche Konsolenanwendungen zahllos.

Nach erfolgreicher Dateifreigabe hatte ich noch ein letztes Feature (Socketverbindungen) zu berücksichtigen, auf das ich im nächsten Abschnitt eingehen werde.

Docker-Container: Aktivieren erweiterter Socketfeatures

Eines der Hauptfeatures, das ich prüfen musste, war die Möglichkeit der Kommunikation zwischen einer Hostsocketverbindung und einer internen Containersocketverbindung. Auch hier kann ein Großteil dieser Funktionalität sowohl in Windows Server-Containern als auch in Docker-Containern genutzt werden, da das Setup über Befehlszeilenargumente gesteuert wird, die angeben, wie der Docker-Container ausgeführt wird und welche Ports bereitgestellt werden.

Um diese Funktionalität zu unterstützen, habe ich Client- und Serversocketanwendungen erstellt, die eine Verbindung von der Clientanwendung, die unter Windows Server ausgeführt wird, mit einem serverseitigen Anwendungslistener herstellen, der als Windows Server-Container ausgeführt wird. Ich habe meiner Anwendung auch den Code hinzugefügt, der erforderlich ist, um an einem bestimmten Socket zu lauschen und dann in der Konsole mit den empfangenen Daten und Bytes zu antworten.

Ich habe die Socketbeispiele von Microsoft aus dem asynchronen Clientsocketbeispiel unter bit.ly/2gDKYz2 und dem asynchronen Serversocketbeispiel unter bit.ly/2i8VUbK für die Basiscodesegmente genutzt, die ich in meine Anwendung integriert habe.

Ich habe einige Änderungen am serverseitigen Code vorgenommen, um die IP-Adresse des Containers zu ermitteln, sodass ich bei der Verwendung der Clientsocketanwendung die zugewiesene IP-Adresse angeben konnte. Ich war in der Lage, die NAT-Details des Containers abzurufen, indem ich den folgenden Befehl ausgeführt habe:

docker network inspect nat

Ich habe auch verschiedene Nachschlagevorgänge ausgeführt, um die IP-Adresse des Containers abzurufen. Um das Debuggen und die Fehlersuche zu vereinfachen, habe ich aber eine Schleife zum Abrufen aller IP-Adressen hinzugefügt, die dann in das Konsolenfenster geschrieben wurden:

foreach (var info in ipHostInfo.AddressList)
{
  Console.WriteLine("\nIP: " + info);
}

Außerdem habe ich den Port auf den spezifischen Port festgelegt, den ich für meine Socketverbindung getestet habe. Ich habe meine Anwendung erneut auf dem virtuellen Windows Server 2016-Computer installiert und meine Clientanwendung auf den Server kopiert, um die Konnektivität zu testen. Standardmäßig werden keine benutzerdefinierten Ports aus dem Container bereitgestellt, und der Container lässt keine TCP-Socketverbindung zu. Um diese Funktionalität zu aktivieren, musste ich Docker die entsprechenden Ausführungsargumente übergeben, ähnlich wie bei der Freigabe eines Ordners.

In meinem Fall wollte ich eine Verbindung mit Port 50020 von dem Host, der meine Client-Anwendung ausführt, mit der .NET-Konsolenanwendung herstellen, die in meinem Windows Server-Container ausgeführt wird. Abbildung 7 veranschaulicht, wie die Anwendung eingerichtet wird.

Hostsocketkommunikation zwischen dem Client und dem Windows Server-Container
Abbildung 7: Hostsocketkommunikation zwischen dem Client und dem Windows Server-Container

Nachdem alles eingerichtet und konfiguriert war, musste ich dem Windows Server-Container und dem Docker-Container mitteilen, dass ich bestimmte Ports aus meinem Container für den Hostcomputer bereitstellen möchte. Um dieses Verhalten zu aktivieren, habe ich das folgende Argument für den Befehl „docker run“ angegeben:

-p [host port]:[container port]

Sie können mehrere Ports bereitstellen, indem Sie dieses Argument für jeden einzelnen Port wiederholen, z.B. „-p 50020: 50020 –p 50019:50019“ usw. Durch das Ausführen meines Containers und das Bereitstellen der Ports war ich bereit zu testen, ob eine Verbindung zwischen der Windows Server-Container-Konsolenanwendung und meinem Client besteht, der auf dem virtuellen Windows Server 2016-Computer ausgeführt wird.

Der vollständige Befehl, mit dem ich den Windows Server-Container ausgeführt habe, lautete folgendermaßen:

docker run --rm -it -p 50010:50010 -v c:\hostcontainershare:c:\containertmp myconsoleapplication
  ".NET Framework App Listening on Socket" 2

Nachdem ich den Windows Server-Container gestartet hatte, der die Konsolenanwendung ausführt, war ich bereit, meine Clientanwendung zu starten. Die Containerkonsolenanwendung gab die aktuelle IP-Adresse des Containers und die Tatsache an, dass er an dem von mir angegebenen Socket lauschte. Ich musste nur noch meine Clientanwendung starten und die aktuelle IP-Adresse des Containers übergeben, mit dem die Clientanwendung eine Verbindung herstellen sollte. Meine Tests wären dann vollständig. Wie in Abbildung 8 gezeigt, stellte die Clientanwendung eine Verbindung mit der IP-Adresse der auf dem Bildschirm angezeigten Containerkonsolenanwendung her und übermittelte einen kleinen Datensatz über den Socket. Erfolg!

Clientsocketanwendung, die Daten an die Containerkonsolenanwendung sendet
Abbildung 8: Clientsocketanwendung, die Daten an die Containerkonsolenanwendung sendet

Zusammenfassung

Aufgrund der Art der Anwendungen, die ich ausgeführt habe, benötigte ich einige spezifische Funktionen, die für Docker verfügbar sein mussten. Als ich erfuhr, dass ich meine .NET Konsolenanwendung mit Windows Server-Containern ausführen kann, war ich ziemlich optimistisch, dass ich in der Lage sein würde, auf eine Datei vom Host aus zuzugreifen und die Socketkommunikation zwischen dem Hostsystem und meinem Docker-Container zu ermöglichen. Was mich am meisten beeindruckt hat, ist die Möglichkeit, Ordner und Dateien freizugeben und gleichzeitig Sockets und Ports bereitzustellen, die für meine Anwendungen oder andere Anwendungen spezifisch sind. Mit Windows Server 2016 erfolgt die Integration von Windows Server-Containern ausgesprochen reibungslos und erfordert nur sehr wenig Konfiguration oder Orchestrierung für die Bereitstellung von Windows-Containern. Für .NET-Apps, die Sie zu Docker migrieren möchten, empfehle ich auf jeden Fall die Verwendung von Windows Server-Containern und die entsprechende Bereitstellung von Features von Docker, um sicherzustellen, dass Ihre Anwendung wie erwartet ausgeführt wird. Wie bei den meisten Anwendungen und der gemeinsamen Nutzung von Ressourcen muss die Sicherheit immer berücksichtigt und überprüft werden. Sie müssen dennoch große Vorsicht walten lassen, wenn Sie Daten oder Sockets aus einem Hostsystem für einen Container freigeben. Wenn Sie solche Funktionen aktivieren, müssen Sie außerordentlich vorsichtig sein, um ein Sicherheitsrisiko zu vermeiden. Darüber hinaus müssen die gemeinsame Nutzung von Dateien und das Öffnen von Ports zwischen einem Hostsystem und einem Container mit Vorsicht behandelt werden, um Sicherheitsrisiken so weit wie möglich auszuschließen. Ich habe festgestellt, dass ich mit meiner Anwendung ein hohes Maß an Skalierbarkeit bieten und gleichzeitig bestimmte Komponenten der Anwendung insgesamt modernisieren konnte. Die Anwendung kann nun mit Docker Swarm oder anderen Skalierungsmodellen, die die Ausführung der Anwendung ermöglichen, in einer skalierbaren Umgebung implementiert werden, die nur bezüglich der Kosten bzw. der Hardwareebene Einschränkungen unterliegt. Als Bonus gab mir diese Lösung die nötige Zeit, um zu beurteilen, ob eine Neugestaltung erforderlich war oder ob diese Lösung die dauerhafte Lösung sein könnte. Mit vielen der in diesem Artikel gezeigten Features können Sie hoffentlich Ihre eigene Migration und Ihren eigenen Entwurf beginnen, um Ihre .NET-Anwendungen zu modernisieren.


Sean Iannuzzi ist seit mehr als 20 Jahren in der Technologiebranche tätig und hat eine entscheidende Rolle bei der Überbrückung der Kluft zwischen Technologie und Geschäftsvisionen für eine Vielzahl zeitgemäßer Lösungen gespielt: soziales Netzwerken, Big Data, Datenbanklösungen, Cloud Computing, E-Commerce und Finanzanwendungen. Iannuzzi verfügt über Erfahrung mit mehr als 50 einzigartigen Technologieplattformen, hat mehr als ein Dutzend technischer Auszeichnungen/Zertifizierungen erhalten und ist spezialisiert auf die Entwicklung von Technologien und Lösungen zur Erreichung von Geschäftszielen.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Jesse Squire


Diesen Artikel im MSDN Magazine-Forum diskutieren