Dieser Beitrag wurde ursprünglich als Artikel im SDN Magazine am 28. Februar 2017 veröffentlicht . Eines der heißesten Themen innerhalb der Microsoft-Entwicklergemeinschaft ist derzeit zweifellos das Thema "Container". Nach dem Erfolg von Docker und Containern auf Linux hat Microsoft eine Windows-Container-Implementierung auf Windows Server 2016 und Windows 10 entwickelt. Nach zweieinhalb Jahren Entwicklungszeit und einem Jahr, in dem diese Containertechnologie in der Vorschau für Insider lief (Windows Server 2016 TP3 - TP5), kündigte Microsoft im September 2016 schließlich an, Windows Server 2016 für die Öffentlichkeit freizugeben. Während die Containertechnologie und Containerized Delivery von Unternehmen, die nicht auf Microsoft fokussiert sind, bereits seit ein paar Jahren eingesetzt werden (Linux hat seine Containertechnologie seit August 2008), steht die Microsoft-Community erst am Anfang dieser neuen Reise. Dies ist also der perfekte Zeitpunkt, um sich zu fragen, ob wir uns für Windows-Container interessieren sollten und ob wir uns mit der Struktur dieser neuen Technologie befassen sollten. Bevor wir jedoch einen genaueren Blick auf die Windows-Container-Technologie werfen, lassen Sie uns einen Blick auf die Art und Weise werfen, in der wir unsere Anwendungen in den letzten 10 Jahren bereitgestellt haben.
Eine kurze Geschichte über Einsatzreife
Wenn wir uns die Art und Weise ansehen, in der wir unsere Software in den letzten 10 Jahren bereitgestellt haben, können wir eine Verlagerung von der manuellen Installation unserer Anwendungen hin zu einer automatisierten Art und Weise, die Anwendungen in Produktion zu bringen, feststellen. Aus der Überlegung heraus, dass die Zuverlässigkeit, Geschwindigkeit und Effizienz der Bereitstellung unserer Anwendungen für unsere Kunden verbessert werden könnte, wurden im Laufe der Zeit verschiedene Tools und Frameworks eingeführt. Sie alle unterstützen die automatisierte Methode der Installation von Anwendungen auf einem beliebigen Server, z.B. WiX, PowerShell, PowerShell DSC, Chocolatey, Chef und Puppet.
Zuvor hatten wir mit WiX ein MSI erstellt. Anschließend wurden unsere Anwendungen von unserem Kollegen aus dem Betrieb installiert, der an den Wochenenden verfügbar war, um die Installation unserer Anwendungen in einer Produktionsumgebung abzuschließen. Damit er seine Arbeit machen konnte, stellte ihm das Entwicklungsteam ein dokumentiertes Runbook mit den erforderlichen Aktionen zur Verfügung. Nach einiger Zeit stellten wir jedoch fest, dass die manuelle Installation unserer Anwendungen einen unerwünschten Nebeneffekt hatte. Sie war fehleranfällig und es kostete uns viel Zeit, unbeabsichtigte Fehler zu finden und zu beheben, die unser Kollege aus dem Betrieb bei der manuellen Installation unserer Anwendungen gemacht hatte. Deshalb haben wir - das Entwicklungsteam - beschlossen, dieses Risiko so weit wie möglich zu minimieren, indem wir zusätzlich zur MSI ein PowerShell-Skript bereitstellten. Der Mitarbeiter musste dann nur noch unser PowerShell-Hauptskript ausführen und die streng geheimen Produktionsvariablen beim Aufruf als Argumente angeben. Der Rest der Installation wurde in PowerShell geskriptet oder war Teil unseres Windows-Installationsprogramms. Obwohl diese skriptgesteuerte Art der Installation unserer Anwendungen uns mehr Zuverlässigkeit und Geschwindigkeit brachte, stießen wir immer noch auf Probleme. Die Ausgangssituation unserer Entwicklungs-, Test-, Abnahme- und Produktionsumgebungen war nicht dieselbe, und so schlugen einige Implementierungen aufgrund fehlender oder unterschiedlicher Bibliotheken, Konfigurationen und Toolversionen fehl. Dies war vor allem in der Produktionsumgebung der Fall, einer Umgebung, die von unserem Betriebsteam verwaltet wird und in der immer die neuesten Produktversionen installiert sein sollten, um sie sicherer zu machen. Probleme traten auch in der Entwicklungsumgebung auf, wo die Anwendung "auf meinem Rechner" funktioniert und nicht auf dem Rechner meines Kollegen. Natürlich stellte sich die Frage, wie man diese Probleme lösen kann. Wir versuchten, unsere PowerShell-Installationsskripte widerstandsfähiger zu machen, aber das hat uns viel Zeit gekostet und wir waren immer noch mit denselben Umgebungsproblemen konfrontiert, wenn auch in geringerem Ausmaß. Als Team beschlossen wir, eine Lösung zu finden, die die Unterschiede in den Basisinstallationen in unseren verschiedenen Umgebungen aufzeigt und die garantiert, dass alle unsere Umgebungen nach der Ausführung unserer Installationsskripte mit denselben Tool- und Assembly-Versionen enden würden. Wir fanden eine Reihe von Tools, die uns dabei helfen konnten, z.B. PowerShell DSC, Chocolatey, Chef und Puppet. Inzwischen sind wir ein DevOps-Team und haben gemeinsam mit unseren Mitarbeitern aus dem operativen Geschäft den gewünschten Zustand unserer Umgebungen für unsere Skripte definiert. Wir haben die verschiedenen umgebungsspezifischen Einstellungen in einem einzigen Konfigurationsdatenspeicher gespeichert, und die Skripte erhalten die richtigen Konfigurationseinstellungen aus diesem Speicher, so dass wir sie nicht explizit in unseren Skripten angeben müssen. Infolgedessen konnten wir unsere Anwendungen mit den gewünschten Statuslösungen und mit einem höheren Maß an Zuverlässigkeit bereitstellen. Außerdem haben wir bei der Entwicklung von Deployment-Skripten viel Zeit gespart, wenn wir unsere normalen PowerShell-Skripte gegen verschiedene Baselines unserer Umgebungen resilient machen. Allerdings sind wir immer noch auf eine Reihe von Problemen gestoßen... Wir haben zwar eine automatisierte, schnelle und zuverlässige Möglichkeit, unsere Anwendungen in der Produktion bereitzustellen, müssen aber immer noch unsere Produktionsrechner nach der Deinstallation neu starten, um sicherzustellen, dass alle Registrierungsschlüssel, Caches und Dateisystemdateien wirklich geleert werden. Außerdem haben wir immer noch eine Menge Wartezeiten und Ausfallzeiten, die durch diese Installationen und Deinstallationen verursacht werden. Selbst bei einer Scale-Out-Lösung müssen wir immer noch warten, bis alle unsere Installationsskripte auf jedem Rechner ausgeführt wurden, um das Upgrade unserer gesamten Umgebung abzuschließen. Kurzum, es ist höchste Zeit für eine neue Art der Bereitstellung unserer Anwendungen. Werfen wir also einen Blick auf die Welt der containerisierten Bereitstellung.
Warum sollte uns das interessieren?
Es wurde bereits viel über die Vorteile geschrieben, die uns Container bringen werden. Die meisten dieser Artikel und Blogs enden mit den gleichen Vorteilen: Mit Containern können wir unsere Anwendungen schneller, kostengünstiger und zuverlässiger bereitstellen. Und es stimmt, dass diese Vorteile genau die Gründe sind, warum Sie den Einsatz von Containern für die Bereitstellung Ihrer Anwendungen in Betracht ziehen sollten. Doch lassen Sie uns diese Vorteile beweisen, indem wir uns die zugrunde liegende Implementierung von Containern genauer ansehen.
Schneller
Container sind die Artefakte einer neuen Ebene der Virtualisierung. Während Dinge wie virtueller Speicher und virtuelle Maschinen ein Ergebnis der Hardware-Virtualisierung sind, sind Container ein Ergebnis der sogenannten Virtualisierung auf Betriebssystemebene. Das bedeutet, dass Container das Betriebssystem gemeinsam nutzen, während VMs alle ihr eigenes Betriebssystem benötigen. Das Interessante dabei ist, dass diese Virtualisierungsebene eine ganz neue Ebene der Anwendungsbereitstellung ermöglicht. Bei anderen Arten der Anwendungsbereitstellung mussten wir unsere Umgebung in einen bestimmten Zustand versetzen. Bei der Bereitstellung in Containern müssen wir unseren Container nur auf einen anderen Container-Host "verschieben", und schon ist er einsatzbereit. Der Container selbst enthält die installierte Anwendung, was zu sofortigen Startzeiten bei der Bereitstellung führt. Anstatt Ihre Anwendung in dem Moment zu installieren, in dem sie bereitgestellt wird, wird die Anwendung bei der containerisierten Bereitstellung während des Erstellungsprozesses des Containers (Docker build) im Container installiert.
Besser
Ein weiterer Aspekt des Docker-Erstellungsprozesses ist, dass während der Erstellung die Zwischenzustände des Containers als sogenannte "Image-Layer" und der endgültige Endzustand als sogenanntes "Container-Image" extrahiert werden. Dieses Abbild und diese Schichten sind eine Blaupause der zu diesem Zeitpunkt im Container laufenden Anwendung. Darüber hinaus enthalten sie aber auch eine Momentaufnahme des Zustands des Dateisystems, der Registrierung und der laufenden Prozesse. Da der Container selbst die installierte Anwendung einschließlich des erforderlichen Umgebungskontextes (Registrierungsschlüssel, Prozesse, zugewiesene Ressourcen und Dateien) enthält, können wir sicher sein, dass unsere Anwendung in verschiedenen Umgebungen in genau demselben Kontext ausgeführt wird, selbst wenn diese Umgebungen (Entwicklung, Test, Produktion) unterschiedliche Speicherressourcen verwenden können und eine unterschiedliche Anzahl anderer Container ausgeführt wird.
Billiger
Der kostensparende Teil der Arbeit mit Containern gilt insbesondere für die Szenarien, in denen Sie eine Virtuelle Maschine bereitstellen, um die Isolierung von Registry, Prozessen und Dateisystemen zwischen den Anwendungen, die Sie Ihren Kunden zur Verfügung stellen, zu gewährleisten. Die erweiterten Möglichkeiten der Containertechnologie ermöglichen es uns, für diese Szenarien Container anstelle von Virtuellen Maschinen zu verwenden. Da Container das Betriebssystem (Lizenz) gemeinsam nutzen und einen viel geringeren Platzbedarf (Speicherplatz) haben als Virtuelle Maschinen, sparen Sie viel Geld, wenn Sie eine Isolierung zwischen Anwendungen benötigen oder wenn Sie mehrere Versionen/Instanzen derselben Anwendung auf einem einzigen Rechner ausführen müssen.
Grundlegende Implementierung
Um zu erklären, wie Container intern im Windows-Betriebssystem implementiert sind, müssen Sie zwei wichtige Konzepte kennen: Benutzermodus und Kernelmodus. Vor der Einführung von Windows Server 2016 bestand jedes von uns verwendete Windows-Betriebssystem aus einem einzigen "Benutzermodus" und einem "Kernelmodus". Beides sind verschiedene Modi, zwischen denen ein Prozessor ständig wechselt, je nachdem, welche Art von Code er ausführen muss.
Kernel-Modus
Der Kernel-Modus eines Betriebssystems wurde für Treiber implementiert, die uneingeschränkten Zugriff auf die zugrunde liegende Hardware haben müssen. Normale Programme (Benutzermodus) müssen die APIs des Betriebssystems nutzen, um auf Hardware oder Speicher zuzugreifen. Code, der im Kernel-Modus ausgeführt wird, hat direkten Zugriff auf diese Ressourcen und nutzt dieselben Speicherplätze/virtuellen Adressräume wie das Betriebssystem und andere Kernel-Treiber. Die Ausführung von Code in diesem Kernel-Modus ist daher sehr riskant, da Daten, die dem Betriebssystem oder einem anderen Treiber gehören, gefährdet werden könnten, wenn Ihr Kernel-Modus-Code versehentlich Daten an eine falsche virtuelle Adresse schreibt. Wenn ein Kernel-Modus-Treiber abstürzt, stürzt das gesamte Betriebssystem ab. Die Ausführung von Code innerhalb des Kernelbereichs sollte daher so wenig wie möglich erfolgen. Das ist genau der Grund, warum der Benutzermodus eingeführt wurde.
Benutzer-Modus
Im Benutzermodus läuft der Code immer in einem separaten Prozess (Benutzerbereich), der über eine eigene Gruppe von Speicherplätzen verfügt (privater virtueller Adressraum). Da der virtuelle Adressraum jeder Anwendung privat ist, kann eine Anwendung keine Daten verändern, die einer anderen Anwendung gehören. Jede Anwendung läuft isoliert, und wenn eine Anwendung abstürzt, ist der Absturz auf diese eine Anwendung beschränkt. Der virtuelle Adressraum einer Anwendung, die im Benutzermodus läuft, ist nicht nur privat, sondern auch begrenzt. Ein Prozessor, der im Benutzermodus läuft, kann nicht auf virtuelle Adressen zugreifen, die für das Betriebssystem reserviert sind. Durch die Begrenzung des virtuellen Adressraums einer Anwendung im Benutzermodus wird verhindert, dass die Anwendung wichtige Daten des Betriebssystems verändert und möglicherweise beschädigt.
Technische Implementierung von Windows-Containern
Aber was haben diese Prozessormodi mit Containern zu tun? Jeder Container ist lediglich ein Prozessor-"Benutzermodus" mit ein paar zusätzlichen Funktionen wie Namespace-Isolierung, Ressourcenverwaltung und dem Konzept eines Unionsdateisystems. Das bedeutet, dass Microsoft das Windows-Betriebssystem anpassen musste, damit es mehrere Benutzermodi unterstützen kann. Das war sehr schwierig, wenn man bedenkt, wie stark die beiden Modi in früheren Windows-Versionen miteinander integriert waren. Das folgende Diagramm gibt einen Überblick über diese neue Architektur für mehrere Benutzermodi.
Wenn wir uns die Benutzermodi von Windows Server 2016 ansehen, können wir zwei Arten identifizieren: den Host-Benutzermodus und den Container-Benutzermodus. Der Host-Benutzermodus ist identisch mit dem normalen Benutzermodus, den wir bereits kennen. Das Ziel dieses Benutzermodus ist es, die Ausführung von Anwendungen auf dem Host zu erleichtern. Eine neue Funktion von Windows Server 2016 ist, dass dieser Host-Benutzermodus, sobald Sie die Container-Funktion aktivieren, einige zusätzliche Container-Verwaltungstechnologien enthält, die sicherstellen, dass Container unter Windows funktionieren. Der Kern dieser Container-Technologie ist die Computerdienste-Abstraktion, die die vom Kernel bereitgestellten Low-Level-Container-Funktionen über eine öffentliche API offenlegt. Tatsächlich starten diese Dienste nur Windows-Container, verfolgen sie, während sie laufen, und verwalten die für den Neustart erforderlichen Funktionen. Der Rest der Container-Verwaltungsfunktionen wird von der Docker-Engine übernommen, die die von Microsoft angebotene "Go-Sprachbindungslösung" nutzt, um mit diesen Compute Services-APIs kommunizieren zu können.
Windows- vs. Linux-Container
Die rechte Seite des Diagramms zeigt zwei verschiedene Windows Server Container. Wie bei Linux-Containern enthalten diese Container die Anwendungsprozesse der Anwendungen, die innerhalb des Containers gestartet wurden. Da Microsoft die öffentlichen Schnittstellen seines Kernels seit jeher über DLLs statt über Syscalls (wie bei Linux) kontrolliert und diese DLLs stark miteinander integriert sind, benötigen auch Windows-Container einige zusätzliche Systemprozesse und -dienste, um ausgeführt werden zu können. Hinweis: In jedem Container läuft ein smss-Prozess, der diese verschiedenen Systemdienste startet. Ein weiterer Unterschied zwischen der Linux- und der Windows-Container-Implementierung ist das Konzept der Hyper-V-Container. Da alle Prozesse, die innerhalb eines normalen Windows Server Containers laufen, vom Container-Host aus gesehen werden können, hat Microsoft beschlossen, einen neuen Containertyp einzuführen, um eine sichere Lösung für feindliche Multi-Tenancy-Situationen zu bieten. Dieser neue Hyper-V-Container verfügt über die gleichen Funktionen wie ein normaler Windows Server Container, abgesehen davon, dass er immer innerhalb einer minimalisierten (Dienstprogramm-) Hyper-V VM läuft, um eine zusätzliche Sicherheitsgrenze um den Container herum zu schaffen. Eigentlich handelt es sich um ein hybrides Modell aus einer VM und einem Container. Das bedeutet auch, dass einige Kernelemente des Kernels und ein Guest Compute Service in diese VM kopiert werden. Hyper-V-Container eignen sich zwar hervorragend für Situationen, in denen sich die verschiedenen Container, die Sie auf einer gemeinsamen Gruppe von Hosts ausführen, nicht in derselben Vertrauensstellung befinden, aber die zusätzliche sichere Hyper-V-Virtualisierungsschicht macht diese Container beim Start etwas langsamer. Deshalb ist es ratsam, sie nur zu verwenden, wenn Sie diese zusätzliche Sicherheitsgrenze wirklich benötigen.
Hinweis: Sie können im Argument isolation des Befehls docker run angeben, welchen Containertyp Sie ausführen möchten. Um Hyper-V-Container ausführen zu können, muss die Hyper-V-Funktion aktiviert sein.
Verfasst von
Cornell Knulst
Cornell works for Xpirit, Hilversum, The Netherlands, as a trainer/architect. He is specialized in the domain of Application Lifecycle Management and Continuous Delivery, with a special focus on Microsoft-based technologies.
Unsere Ideen
Weitere Blogs
Contact



