„Cloud-native“ oder einfach „Cloud“-Anwendungen werden speziell für den Betrieb in Cloud-Infrastrukturen entwickelt. Sie bestehen in der Regel aus einer Reihe schwach gekoppelter Mikrodienste, die in Containern verpackt sind, die wiederum von einer Cloud-Plattform verwaltet werden. Diese Anwendungen sind standardmäßig fehlertolerant, was bedeutet, dass sie auch bei erheblichen Infrastrukturfehlern zuverlässig funktionieren und skalierbar sind. Die Kehrseite der Medaille sind die Einschränkungen (Verträge), die die Cloud-Plattform auf Containeranwendungen auferlegt, um sie automatisiert verwalten zu können.

Da sich viele Organisationen der Notwendigkeit und Bedeutung des Übergangs zu Cloud-Anwendungen bewusst sind, wissen viele jedoch immer noch nicht, wo sie anfangen sollen. In diesem Beitrag werden wir eine Reihe von Prinzipien untersuchen, deren Einhaltung bei der Entwicklung von Containeranwendungen das Potenzial von Cloud-Plattformen ausschöpfen und eine zuverlässige Funktion und Skalierung der Anwendungen auch bei erheblichen IT-Infrastrukturfehlern gewährleisten kann. Das übergreifende Ziel der hier dargestellten Prinzipien ist es, zu lernen, wie man Anwendungen erstellt, die von Cloud-Plattformen wie Kubernetes automatisch verwaltet werden können.
Designprinzipien für Software
Im Bereich der Programmierung werden unter Prinzipien allgemeine Regeln verstanden, die bei der Softwareentwicklung eingehalten werden müssen. Sie können für jede Programmiersprache angewandt werden. Jedes Prinzip hat seine eigenen Ziele, deren Erreichung in der Regel durch Entwurfsmuster und Praktiken erfolgt. Es gibt auch eine Reihe grundlegender Prinzipien, die für die Erstellung qualitativ hochwertiger Software entscheidend sind und aus denen sich alle anderen ableiten. Hier sind einige Beispiele grundlegender Prinzipien:
- (Keep it simple, stupid) – nicht komplizieren;
- (Don’t repeat yourself) – nicht wiederholen;
- (You aren’t gonna need it) – nichts schaffen, was nicht unmittelbar benötigt wird;
- (Separation of concerns) – Verantwortlichkeiten trennen.
Wie ersichtlich, stellen diese Prinzipien keine spezifischen Regeln dar, sondern gehören zu den sogenannten gesundem Menschenverstand, die viele Entwickler teilen und auf die sie sich regelmäßig beziehen.
Darüber hinaus gibt es – eine Sammlung der ersten fünf Prinzipien der objektorientierten Programmierung und des Designs, die von Robert Martin formuliert wurden. SOLID umfasst allgemeine und interpretierbare komplementäre Prinzipien, die – wenn sie zusammen angewendet werden – helfen, qualitativ hochwertigere Softwaresysteme zu erstellen und diese langfristig besser zu warten.
Die SOLID-Prinzipien beziehen sich auf den Bereich der OOP und werden in Begriffen und Konzepten wie Klassen, Schnittstellen und Vererbung formuliert. Analog können auch Prinzipien für Cloud-Anwendungen formuliert werden, wobei das grundlegende Element hier nicht die Klasse, sondern der Container ist. Indem man diesen Prinzipien folgt, ist es möglich, containerbasierte Anwendungen zu erstellen, die besser auf die Ziele und Anforderungen von Cloud-Plattformen wie Kubernetes abgestimmt sind.
Cloud-orientierte Container: Red Hat-Ansatz
Heute kann man praktisch jede Anwendung relativ einfach in Container verpacken. Um jedoch Anwendungen effizient zu automatisieren und zu orchestrieren, die auf einer Cloud-Plattform wie Kubernetes laufen, sind zusätzliche Anstrengungen erforderlich.
Die Grundlage für die nachstehenden Ideen bildete die Methodologie und zahlreiche andere Arbeiten zu verschiedenen Aspekten der Erstellung von Webanwendungen, von der Verwaltung des Quellcodes bis zu Skalierungsmodellen. Die beschriebenen Prinzipien beziehen sich nur auf die Entwicklung von containerbasierten Anwendungen, die auf Microservices basieren und für Cloud-Plattformen wie Kubernetes bestimmt sind. Das grundlegende Element in unseren Überlegungen ist das Container-Image, und die Zielumgebung für die Ausführung von Containern versteht sich als Plattform zur Orchestrierung von Containern. Das Ziel der vorgeschlagenen Prinzipien ist es, Container zu erstellen, für die in den meisten Orchestrierungsplattformen Aufgaben wie Scheduling (Auswahl des Hosts für die Ausführung einer Containerinstanz), Skalierung und Überwachung automatisiert werden können. Die Prinzipien werden in beliebiger Reihenfolge dargelegt.
Prinzip der einzigen Aufgabe (Single Concern Principle, SCP)
Dieses Prinzip ähnelt stark dem Prinzip der einzigen Verantwortung (Single Responsibility Principle, ), der Teil des SOLID-Satzes ist und besagt, dass jedes Objekt eine einzige Verantwortung haben sollte, und diese Verantwortung sollte vollständig in der Klasse gekapselt sein. Das Wesen des SRP besteht darin, dass jede Verantwortung einen Grund für Änderungen darstellt, und die Klasse sollte genau einen einzigen Grund für Änderungen haben.
Im SCP verwenden wir anstelle des Wortes „Verantwortung“ (responsibility) das Wort „Aufgabe“ (concern), um auf ein höheres Abstraktionsniveau hinzuweisen und auf die breitere Funktionalität des Containers im Vergleich zur OOP-Klasse. Wenn das Ziel des SRP darin besteht, nur einen Grund für Änderungen zu haben, dann steht hinter dem SCP der Wunsch, die Möglichkeiten zur Wiederverwendbarkeit und zum Austausch von Containern zu erweitern. Wenn Sie dem SRP folgen und einen Container erstellen, der eine einzige Aufgabe erfüllt und dies funktional abgeschlossen tut, erhöhen Sie die Chancen, dass dieses Containerbild in verschiedenen Anwendungskontexten wiederverwendet wird.
Das SCP-Prinzip besagt, dass jeder Container eine einzige Aufgabe gut erfüllen sollte. Außerdem wird das SCP in der Welt der Container einfacher erreicht als das SRP in der OOP-Welt, da Container in der Regel einen einzigen Prozess ausführen, und dieser Prozess löst in der Regel eine einzige Aufgabe.
Wenn ein containerisierter Mikroservice mehrere Aufgaben gleichzeitig erfüllen muss, kann er in einaufgaben Container zerlegt und im Rahmen eines Pods (eine Einheit der Bereitstellung der Containerplattform) mit Hilfe von Sidecar- und Init-Containern kombiniert werden. Darüber hinaus erleichtert das SCP den Austausch eines alten Containers (z. B. Webservers oder Nachrichtenbrokers) durch einen neuen, der dieselbe Aufgabe erfüllt, aber erweiterte Funktionalitäten besitzt oder besser skalierbar ist.

Das Prinzip der hohen Beobachtbarkeit (High Observability Principle, HOP)
Bei der Verwendung von Containern als standardisierte Methode zur Verpackung und Ausführung von Anwendungen werden die Anwendungen selbst als „Black Box“ betrachtet. Wenn es sich jedoch um Cloud-Container handelt, müssen sie spezielle APIs zur Verfügung stellen, damit die Laufzeitumgebung die Integrität der Container überwachen und gegebenenfalls geeignete Maßnahmen ergreifen kann. Ohne dies können die Automatisierung des Container-Updates und das Management ihres Lebenszyklus nicht standardisiert werden, was wiederum die Stabilität und Benutzerfreundlichkeit des Softwaresystems beeinträchtigt.
In der Praxis sollte die Containeranwendung mindestens eine API für verschiedene Arten von Integritätstests haben: Liveness-Tests und Readiness-Tests. Wenn die Anwendung mehr beansprucht, sollte sie auch andere Mittel zur Überwachung ihres Zustands bereitstellen. Beispielsweise das Protokollieren wichtiger Ereignisse über STDERR und STDOUT zur Aggregierung von Protokollen mit Werkzeugen wie Fluentd, Logstash und ähnlichen. Außerdem sollte eine Integration mit Bibliotheken für Tracing und Metriksammlung wie OpenTracing, Prometheus usw. erfolgen.
Im Allgemeinen kann die Anwendung nach wie vor als „Black Box“ betrachtet werden, muss jedoch mit allen APIs ausgestattet sein, die die Plattform benötigt, um sie optimal zu überwachen und zu verwalten.
Prinzip der Lebenszykluskonformität (Life-cycle Conformance Principle, LCP)
LCP ist ein Gegenstück zu HOP. Während HOP besagt, dass der Container der Plattform APIs zur Verfügung stellen muss, um Informationen zu lesen, verlangt LCP von der Anwendung die Fähigkeit, Informationen von der Plattform zu empfangen. Der Container sollte nicht nur Ereignisse empfangen, sondern sich auch anpassen, d.h. darauf reagieren. Daher der Name des Prinzips, das als Anforderung betrachtet werden kann, der Plattform APIs zur Verfügung zu stellen, um Daten zu schreiben.

Plattformen haben verschiedene Arten von Ereignissen, die helfen, den Lebenszyklus des Containers zu verwalten. Es obliegt jedoch der Anwendung selbst zu entscheiden, welche davon sie wahrnehmen und wie sie darauf reagieren möchte.
Es ist verständlich, dass einige Ereignisse wichtiger sind als andere. Zum Beispiel, wenn eine Anwendung schlecht mit einem unerwarteten Abschluss umgeht, muss sie die signale signal: terminate (SIGTERM) empfangen und so schnell wie möglich ihre Abschlussprozedur einleiten, um vor dem Eintreffen des signal: kill (SIGKILL) zu reagieren, das nach SIGTERM kommt.
Darüber hinaus können für den Lebenszyklus einer Anwendung Ereignisse wie PostStart und PreStop von Bedeutung sein. Beispielsweise benötigt eine Anwendung nach dem Start möglicherweise eine bestimmte Zeit zum „Aufwärmen“, bevor sie auf Anfragen reagieren kann. Oder die Anwendung muss beim Beenden auf eine besondere Weise Ressourcen freigeben.
Prinzip der Unveränderlichkeit von Container-Images (Image Immutability Principle, IIP)
Es ist allgemein anerkannt, dass Container-Anwendungen nach dem Kompilieren unverändert bleiben sollten, selbst wenn sie in verschiedenen Umgebungen ausgeführt werden. Daher ist es notwendig, die Speicherung von Daten zur Laufzeit zu externalisieren (mit anderen Worten, externe Mittel dafür zu verwenden) sowie sich auf externe, auf die jeweilige Runtime-Umgebung angepasste Konfigurationen zu verlassen, anstatt einzigartige Container für jede Umgebung zu modifizieren oder zu erstellen. Nach Änderungen an der Anwendung muss das Container-Image neu erstellt und in allen verwendeten Umgebungen bereitgestellt werden. Übrigens wird bei der Verwaltung von IT-Systemen ein ähnliches Prinzip angewendet, das als Prinzip der Unveränderlichkeit von Servern und Infrastruktur bekannt ist.
Ziel des IIP ist es, die Erstellung separater Container-Images für verschiedene Laufzeitumgebungen zu verhindern und überall dasselbe Image zusammen mit der entsprechenden Konfiguration für die jeweilige Umgebung zu verwenden. Die Befolgung dieses Prinzips ermöglicht die Umsetzung wichtiger Automatisierungspraktiken in Cloud-Systemen, wie Rollbacks und Roll-Forward-Updates der Anwendung.

Prinzip der Einmaligkeit von Prozessen (Process Disposability Principle, PDP)
Eine der wichtigsten Eigenschaften eines Containers ist seine Flüchtigkeit: Ein Container-Exemplar kann problemlos erstellt und leicht zerstört werden, weshalb es jederzeit unkompliziert gegen ein anderes Exemplar ausgetauscht werden kann. Gründe für einen solchen Austausch können vielfältig sein: ein fehlerhafter Test, Skalierung der Anwendung, Migration auf einen anderen Host, Ressourcenerschöpfung der Plattform oder andere Situationen.
Daher müssen Containeranwendungen ihren Zustand mit Hilfe externer Mittel speichern oder interne verteilte Schemata mit Redundanz nutzen. Darüber hinaus sollte die Anwendung schnell gestartet und zügig beendet werden und bereit sein für plötzliche fatale Hardwarefehler.
Eine der Praktiken, die hilft, dieses Prinzip umzusetzen, besteht darin, Container kleiner Größe zu erstellen. Cloud-Umgebungen können automatisch einen Host für die Ausführung einer Containerinstanz auswählen, weshalb ein kleinerer Container schneller gestartet werden kann – er wird einfach schneller über das Netzwerk auf den Zielhost kopiert.
Prinzip der Selbstgenügsamkeit (Self-containment Principle, S-CP)
Nach diesem Prinzip werden in der Build-Phase alle erforderlichen Komponenten in den Container aufgenommen. Der Container sollte so gebaut werden, dass nur ein reines Linux-Kernel-System vorhanden ist, weshalb alle erforderlichen zusätzlichen Bibliotheken im Container selbst untergebracht werden müssen. Ebenso sollten Dinge, wie die Laufzeitumgebung für die entsprechende Programmiersprache, die Anwendungsplattform (falls erforderlich) und andere Abhängigkeiten, die zur Ausführung der Containeranwendung benötigt werden, dort platziert werden.

Ausnahmen gelten nur für Konfigurationen, die von Umgebung zu Umgebung variieren und während der Ausführung bereitgestellt werden müssen, zum Beispiel über Kubernetes ConfigMap.
Die Anwendung kann mehrere containerisierte Komponenten enthalten, wie zum Beispiel einen separaten Datenbankcontainer innerhalb einer containerisierten Webanwendung. Laut dem S-CP sollten diese Container nicht zu einem einzigen zusammengefasst werden, sondern der Datenbankcontainer sollte alles Nötige für den Betrieb der Datenbank beinhalten, während der Webanwendungscontainer alles für den Betrieb der Webanwendung, einschließlich des Webservers, bereitstellt. Dadurch wird der Container der Webanwendung während der Ausführung vom Datenbankcontainer abhängig sein und nach Bedarf auf ihn zugreifen.
Prinzip der Laufzeiteinschränkung (Runtime Confinement Principle, RCP)
Das S-CP-Prinzip bestimmt, wie ein Container aufgebaut sein sollte und was die Binärdatei des Images beinhalten muss. Aber ein Container ist nicht nur ein „schwarzer Kasten“, der lediglich eine Eigenschaft hat – die Dateigröße. Während der Ausführung erhält der Container auch andere Dimensionen: den Speicherverbrauch, die CPU-Zeit und andere Systemressourcen.

Hier kommt das RCP-Prinzip ins Spiel, gemäß dem der Container seine Anforderungen an die Systemressourcen dekapseln und an die Plattform weitergeben muss. Mit den Ressourcenprofilen jedes Containers (wie viel CPU, Speicher, Netzwerk und Speicherplatz er benötigt) kann die Plattform die Zuordnung und die automatische Skalierung optimal durchführen, IT-Ressourcen steuern und SLAs für Container aufrechterhalten.
Neben der Erfüllung der Ressourcenanforderungen des Containers ist es für die Anwendung auch wichtig, die von ihr selbst gesetzten Grenzen nicht zu überschreiten. Andernfalls wird die Plattform bei einem Ressourcenengpass wahrscheinlich die Anwendung in die Liste der Anwendungen aufnehmen, die gestoppt oder migriert werden müssen.
Wenn wir von Cloud-Orientierung sprechen, meinen wir in erster Linie die Art und Weise, wie gearbeitet wird.
Wir haben eine Reihe allgemeiner Prinzipien formuliert, die die methodologische Grundlage für den Aufbau qualitativ hochwertiger Containeranwendungen für Cloud-Umgebungen bilden.
Es ist zu beachten, dass neben diesen allgemeinen Prinzipien auch erweiterte Methoden und Techniken zur Arbeit mit Containern erforderlich sind. Darüber hinaus gibt es einige kurze Empfehlungen, die einen spezifischeren Charakter haben und je nach Situation angewendet (oder nicht angewendet) werden sollten:
- Versuchen Sie, die Größe der Images zu reduzieren: Entfernen Sie temporäre Dateien und installieren Sie keine unnötigen Pakete – je kleiner die Containergröße, desto schneller wird er erstellt und über das Netzwerk auf den Ziel-Host kopiert.
- Orientieren Sie sich an willkürlichen User-IDs: Verwenden Sie nicht den Befehl sudo oder spezielle User-IDs, um Ihre Container zu starten.
- Kennzeichnen Sie wichtige Ports: Die Portnummern können auch zur Laufzeit festgelegt werden, aber es ist besser, sie mit dem Befehl EXPOSE anzugeben – anderen Personen und Programmen wird es leichter fallen, Ihre Images zu verwenden.
- Speichern Sie persistente Daten auf Volumes: Daten, die nach der Zerstörung des Containers bestehen bleiben sollen, sollten auf Volumes geschrieben werden.
- Definieren Sie die Metadaten des Images: Tags, Labels und Anmerkungen erleichtern die Nutzung von Images – andere Entwickler werden Ihnen dankbar sein.
- Synchronisieren Sie Host und Images: Für einige Containeranwendungen ist es erforderlich, den Container mit dem Host basierend auf bestimmten Attributen wie Zeit oder Maschinen-ID zu synchronisieren.
- Abschließend teilen wir Vorlagen und Best Practices, die Ihnen helfen, die oben genannten Prinzipien effektiver umzusetzen:
11. Juni um 11:00 Uhr
Was Sie lernen werden:
- Immutable Red Hat Enterprise Linux CoreOS
- OpenShift Service Mesh
- Operator Framework
- Knative Framework
Quelle: habr.com
