„Cloud-native“ Anwendungen werden speziell für den Betrieb in Cloud-Infrastrukturen entwickelt. Sie bestehen in der Regel aus einer Sammlung von lose gekoppelten Mikrodiensten, die in Containern verpackt sind, und diese Container werden von der Cloud-Plattform verwaltet. Solche Anwendungen sind standardmäßig fehlertolerant und funktionieren zuverlässig sowie skalierbar, selbst bei schwerwiegenden Infrastrukturausfällen. Auf der anderen Seite gibt es eine Reihe von Einschränkungen (Verträgen), die die Cloud-Plattform den Containeranwendungen auferlegt, um eine automatische Verwaltung zu ermöglichen.

Obwohl viele Organisationen die Notwendigkeit und Wichtigkeit des Wechsels zu Cloud-Anwendungen erkennen, wissen sie oft nicht, wo sie anfangen sollen. In diesem Beitrag betrachten wir eine Reihe von Prinzipien, deren Einhaltung bei der Entwicklung von Containeranwendungen es ermöglicht, das Potenzial von Cloud-Plattformen auszuschöpfen und eine zuverlässige Leistung sowie Skalierbarkeit der Anwendungen selbst bei schwerwiegenden Ausfällen der IT-Infrastruktur zu gewährleisten. Das übergeordnete Ziel der hier dargestellten Prinzipien ist es, zu lernen, wie man Anwendungen erstellt, die automatisch von Cloud-Plattformen wie Kubernetes verwaltet werden können.
Entwurfsprinzipien für Software
Im Bereich der Programmierung beziehen sich Prinzipien auf allgemeine Regeln, die bei der Softwareentwicklung eingehalten werden müssen. Sie können auf jede Programmiersprache angewendet werden. Jedes Prinzip hat seine eigenen Ziele, die meist durch Muster und Praktiken erreicht werden. Es gibt auch eine Reihe grundlegender Prinzipien zur Erstellung qualitativ hochwertiger Software, aus denen alle anderen abgeleitet sind. Hier sind einige Beispiele für grundlegende Prinzipien:
- (Keep it simple, stupid) – Überkomplizierung vermeiden;
- (Don’t repeat yourself) – Wiederholen Sie sich nicht;
- (You aren’t gonna need it) – Erstellen Sie nichts, was nicht unmittelbar benötigt wird;
- (Separation of concerns) – Trennen Sie Verantwortlichkeiten.
Wie zu sehen ist, legen diese Prinzipien keine spezifischen Regeln fest, sondern gehören zur Kategorie der sogenannten gesund verstandenen Überlegungen, die auf praktischen Erfahrungen basieren, und auf die viele Entwickler regelmäßig verweisen.
Darüber hinaus gibt es – eine Sammlung der ersten fünf Prinzipien der objektorientierten Programmierung und des Designs, formuliert von Robert C. Martin. SOLID umfasst generalisierte und interpretationsfähige, sich ergänzende Prinzipien, die – wenn sie gemeinsam angewendet werden – helfen, qualitativ hochwertige Softwaresysteme zu erstellen und deren langfristige Wartung zu verbessern.
Die SOLID-Prinzipien beziehen sich auf das Gebiet der objektorientierten Programmierung (OOP) und werden in Begriffen wie Klassen, Schnittstellen und Vererbung formuliert. Analog lässt sich auch für Cloud-Anwendungen eine Entwicklungsprinzipien aufstellen, wobei hier nicht die Klasse, sondern der Container das grundlegende Element ist. Durch die Einhaltung dieser Prinzipien können containerbasierte Anwendungen erstellt werden, die den Zielen und Anforderungen von Cloud-Plattformen wie Kubernetes besser gerecht werden.
Containerorientierte Clouds: Ansatz von Red Hat
Heute können nahezu alle Anwendungen relativ einfach in Container verpackt werden. Um jedoch sicherzustellen, dass diese Anwendungen in einer Cloud-Plattform wie Kubernetes effizient automatisiert und orchestriert werden, sind zusätzliche Anstrengungen erforderlich.
Die Grundlage der nachfolgenden Ideen bildet die Methodologie und viele andere Arbeiten zu verschiedenen Aspekten der Entwicklung von Webanwendungen, von der Verwaltung des Quellcodes bis hin zu Skalierungsmodellen. Die beschriebenen Prinzipien beziehen sich ausschließlich auf die Entwicklung von containerisierten Anwendungen, die auf Microservices basieren und für Cloud-Plattformen wie Kubernetes vorgesehen sind. Die grundlegende Einheit in unseren Überlegungen ist das Container-Image, während die Zielumgebung für die Ausführung von Containern die Plattform zur Orchestrierung von Containern bezeichnet. Das Ziel der vorgeschlagenen Prinzipien besteht darin, Container zu erstellen, für die auf den meisten Orchestrierungsplattformen die Aufgaben der Bereitstellung (Scheduling – Auswahl des Hosts für die Ausführung einer Containerinstanz), Skalierung und Überwachung automatisiert werden können. Die Prinzipien werden in willkürlicher Reihenfolge erläutert.
Prinzip der einzelnen Aufgabe (Single Concern Principle, SCP)
Dieses Prinzip ähnelt stark dem Prinzip der einzelnen Verantwortung (Single Responsibility Principle, ), der Teil der SOLID-Prinzipien ist und besagt, dass jedes Objekt eine einzige Verantwortung haben sollte, die vollständig in der Klasse gekapselt sein muss. Der Kern des SRP ist, dass jede Verantwortung einen Grund für Veränderungen darstellt, und die Klasse sollte nur einen einzigen Grund für Änderungen haben.
Im SCP verwenden wir anstelle des Begriffs „Verantwortung“ (responsibility) das Wort „Aufgabe“ (concern), um auf ein höheres Abstraktionsniveau und einen breiteren Zweck des Containers im Vergleich zu einer OOP-Klasse hinzuweisen. Wenn das Ziel des SRP darin besteht, nur einen Grund für Veränderungen zu haben, so steht hinter dem SCP das Bestreben, die Wiederverwendbarkeit und Ersetzbarkeit von Containern zu erweitern. Indem Sie dem SRP folgen und einen Container erstellen, der eine einzige Aufgabe auf funktional abgeschlossene Weise löst, erhöhen Sie die Chancen, dass dieses Container-Image in verschiedenen Anwendungskontexten wiederverwendet wird.
Das SCP-Prinzip besagt, dass jeder Container eine einzige Aufgabe erfüllen und dies gut tun sollte. Dabei wird SCP in der Welt der Container einfacher erreicht als das SRP in der Welt der objektorientierten Programmierung, da Container in der Regel einen einzigen Prozess ausführen, der die meiste Zeit eine einzige Aufgabe löst.
Wenn ein containerbasierter Mikrodienst mehrere Aufgaben erfüllen muss, kann er in einzelne, auf eine Aufgabe fokussierte Container aufgeteilt und innerhalb eines Pods (Einheit der Containerbereitstellungsplattform) mithilfe von Sidecar- und Init-Container-Templates zusammengelegt werden. Darüber hinaus erleichtert SCP den Austausch eines alten Containers (zum Beispiel eines Webservers oder eines Message Brokers) durch einen neuen, der dieselbe Aufgabe erfüllt, aber erweiterte Funktionen bietet oder besser skalierbar ist.

Das Prinzip der hohen Beobachtbarkeit (High Observability Principle, HOP)
Bei der Verwendung von Containern als einheitliche Methode zum Verpacken und Starten von Anwendungen werden die Anwendungen selbst als "Schwarze Box" betrachtet. Bei Cloud-Containern müssen jedoch spezielle APIs bereitgestellt werden, um die Integrität der Container zu überwachen und gegebenenfalls angemessene Maßnahmen zu ergreifen. Ohne dies ist es nicht möglich, die Automatisierung von Container-Updates und deren Lebenszyklus zu standardisieren, was wiederum die Stabilität und Benutzerfreundlichkeit des Softwaresystems beeinträchtigt.
In der Praxis sollte eine containerisierte Anwendung mindestens eine API für verschiedene Arten von Gesundheitsprüfungen bereitstellen: Aktivitätsprüfungen (liveness) und Bereitschaftsprüfungen (readiness). Wenn die Anwendung mehr beansprucht, sollte sie auch andere Mittel zur Überwachung ihres Zustands bieten. Zum Beispiel die Protokollierung wichtiger Ereignisse über STDERR und STDOUT zur Aggregation von Logs mit Hilfe von Fluentd, Logstash und ähnlichen Tools. Außerdem sollte eine Integration mit Bibliotheken für das Tracing und das Sammeln von Metriken erfolgen, wie OpenTracing, Prometheus usw.
Im Grunde kann die Anwendung weiterhin als „Black Box“ betrachtet werden, allerdings muss sie mit allen APIs ausgestattet werden, die die Plattform benötigt, um sie optimal zu überwachen und zu verwalten.
Prinzip der Anpassung an den Lebenszyklus (Life-cycle Conformance Principle, LCP)
LCP ist das Gegenstück zu HOP. Während HOP besagt, dass ein Container der Plattform APIs zur Verfügung stellen muss, um Informationen abzurufen, verlangt LCP von der Anwendung die Fähigkeit, Informationen von der Plattform zu empfangen. Der Container muss nicht nur Ereignisse empfangen, sondern sich auch anpassen, das heißt, darauf reagieren. Daher der Name des Prinzips, das als Anforderung verstanden werden kann, der Plattform APIs zum Schreiben bereitzustellen.

Die Plattform verfügt über verschiedene Arten von Ereignissen, die helfen, den Lebenszyklus des Containers zu steuern. Es sollte jedoch die Anwendung selbst entscheiden, welche davon wahrgenommen werden und wie darauf reagiert werden soll.
Es ist klar, dass einige Ereignisse bedeutender sind als andere. Wenn eine Anwendung beispielsweise schlecht mit einem unvorhergesehenen Herunterfahren umgeht, muss sie Signale wie signal: terminate (SIGTERM) empfangen und so schnell wie möglich ihren Beendigungsprozess einleiten, um rechtzeitig vor dem Empfang von signal: kill (SIGKILL) zu reagieren, das auf SIGTERM folgt.
Darüber hinaus können Ereignisse wie PostStart und PreStop für den Lebenszyklus einer Anwendung von Bedeutung sein. Beispielsweise benötigt die Anwendung nach dem Start möglicherweise eine gewisse Zeit zum 'Aufwärmen', bevor sie auf Anfragen reagieren kann. Oder die Anwendung muss beim Herunterfahren auf spezielle Weise Ressourcen freigeben.
Prinzip der Unveränderlichkeit des Container-Images (Image Immutability Principle, IIP)
Es ist allgemein anerkannt, dass Containeranwendungen nach dem Build unverändert bleiben sollten, auch wenn sie in unterschiedlichen Umgebungen ausgeführt werden. Daraus ergibt sich die Notwendigkeit, die Datenspeicherung zur Laufzeit zu externalisieren (mit anderen Worten, externe Mittel dafür zu nutzen) und auf externe, umgebungsspezifische Konfigurationen zurückzugreifen, anstatt einzigartige Container für jede Umgebung zu modifizieren oder zu erstellen. Nach Änderungen an der Anwendung muss das Container-Image neu gebaut 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.
Das Ziel von IIP besteht darin, die Erstellung separater Container-Images für verschiedene Laufzeitumgebungen zu verhindern und überall dasselbe Image zusammen mit der entsprechenden Konfiguration für die spezifische Umgebung zu verwenden. Diesem Prinzip zu folgen, ermöglicht die Umsetzung wichtiger Praktiken zur Automatisierung von Cloud-Systemen, wie z.B. das Rollback und das Rollforward von Anwendungsupdates.

Das Prinzip der Prozess-Einfältigkeit (Process Disposability Principle, PDP)
Eine der wesentlichsten Eigenschaften eines Containers ist seine Ephemerität: Eine Instanz eines Containers kann leicht erstellt und ebenfalls leicht zerstört werden, sodass sie jederzeit problemlos durch eine andere Instanz ersetzt werden kann. Es gibt viele Gründe für einen solchen Austausch: ein Fehlschlagen des Funktionstests, das Skalieren einer Anwendung, der Umzug auf einen anderen Host, das Erschöpfen der Plattformressourcen oder andere Situationen.
Als Folge müssen containerisierte Anwendungen ihren Zustand durch externe Mittel bewahren oder interne verteilte Schemen mit Redundanz verwenden. Darüber hinaus sollte die Anwendung schnell starten und schnell herunterfahren, sowie auf plötzliche fatale Hardwarefehler vorbereitet sein.
Eine der Praktiken, die hilft, dieses Prinzip zu realisieren, besteht darin, Container kleiner Größe zu erstellen. Cloud-Umgebungen können automatisch einen Host zum Starten einer Containerinstanz auswählen, daher desto kleiner der Container, desto schneller wird er gestartet – er wird einfach schneller über das Netzwerk auf den Zielhost kopiert.
Prinzip der Selbstgenügsamkeit (Self-containment Principle, S-CP)
Nach diesem Prinzip werden beim Zusammenstellen des Containers alle erforderlichen Komponenten integriert. Der Container sollte so gestaltet sein, dass er auf ein reines Linux-Kernel-System ausgelegt ist; daher müssen alle erforderlichen zusätzlichen Bibliotheken direkt im Container untergebracht werden. Dort sollten auch Dinge wie die Laufzeitumgebung für die entsprechende Programmiersprache, die Anwendungsplattform (sofern erforderlich) sowie andere Abhängigkeiten, die während des Betriebs der Containeranwendung benötigt werden, zu finden sein.

Ausnahmen gelten lediglich für Konfigurationen, die von Umgebung zu Umgebung variieren und zur Laufzeit bereitgestellt werden müssen, zum Beispiel über Kubernetes ConfigMap.
Eine Anwendung kann mehrere containerisierte Komponenten umfassen, wie zum Beispiel einen separaten Datenbank-Container innerhalb einer containerisierten Webanwendung. Gemäß dem S-CP-Prinzip sollten diese Container nicht zu einem einzigen vereint werden, sondern so strukturiert sein, dass der Datenbank-Container alles Notwendige für den Betrieb der Datenbank enthält, während der Webanwendungs-Container alles für den Betrieb der Webanwendung, einschließlich des Webservers, bereitstellt. In der Folge wird der Webanwendungs-Container zur Laufzeit vom Datenbank-Container abhängig sein und bei Bedarf auf ihn zugreifen.
Prinzip der Laufzeit-Einschränkung (Runtime Confinement Principle, RCP)
Das S-CP-Prinzip definiert, wie ein Container aufgebaut sein sollte und was die Binärdatei des Images enthalten muss. Ein Container ist jedoch nicht einfach nur ein "schwarzer Kasten", dessen einzige Eigenschaft die Dateigröße ist. Während der Ausführung erhält der Container weitere Dimensionen: den verbrauchten Speicher, die CPU-Zeit und andere Systemressourcen.

Hier spielt das RCP-Prinzip eine wichtige Rolle, wonach der Container seine Anforderungen an die Systemressourcen de-couplieren und an die Plattform übergeben sollte. Mit den Ressourcenumfängen jedes Containers (wie viel CPU, RAM, Netzwerk und Speicher er benötigt), kann die Plattform das Ressourcenmanagement und das automatische Scaling optimal umsetzen sowie die IT-Kapazitäten verwalten und die SLA-Levels für die Container sicherstellen.
Neben der Erfüllung der Anforderungen an die Containerressourcen ist es auch wichtig, dass die Anwendung die von ihr selbst gesetzten Grenzen nicht überschreitet. Andernfalls wird die Plattform im Falle eines Ressourcenengpasses eher geneigt sein, die Anwendung auf die Liste der Programme zu setzen, die unterbrochen oder migriert werden müssen.
Wenn wir von Cloud-Orientierung sprechen, meinen wir vor allem die Arbeitsweise.
Zuvor haben wir eine Reihe von grundlegenden Prinzipien formuliert, die die methodologische Grundlage für den Aufbau qualitativ hochwertiger Containeranwendungen für Cloud-Umgebungen bilden.
Bitte beachten Sie, dass zusätzlich zu diesen allgemeinen Prinzipien auch fortgeschrittene Methoden und Techniken im Umgang mit Containern erforderlich sind. Zudem haben wir einige kurze Empfehlungen, die spezifischer Natur sind 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 Größe des Containers, desto schneller wird er erstellt und über das Netzwerk auf den Ziel-Host kopiert.
- Verwenden Sie anonymisierte User-IDs: Nutzen Sie nicht den Befehl sudo oder spezielle User-IDs zum Starten Ihrer Container.
- Markieren Sie wichtige Ports: Portnummern können auch zur Laufzeit festgelegt werden, aber es ist besser, sie mit dem Befehl EXPOSE anzugeben – es erleichtert anderen Benutzern und Programmen, Ihre Images zu verwenden.
- Speichern Sie dauerhafte Daten in Volumes: Daten, die nach der Zerstörung des Containers erhalten bleiben sollen, sollten in Volumes geschrieben werden.
- Verwenden Sie Metadaten für das Image: 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, dass der Container mit dem Host über bestimmte Attribute wie Zeit oder Maschinen-ID synchronisiert wird.
- Abschließend teilen wir Vorlagen und bewährte Praktiken, die Ihnen helfen werden, die oben genannten Prinzipien effizienter umzusetzen:
11. Juni um 11:00 Uhr
Was Sie erfahren werden:
- Immutable Red Hat Enterprise Linux CoreOS
- OpenShift-Service-Mesh
- Operator-Framework
- Knative-Framework
Quelle: habr.com
