Bilgin Ibryam und Roland Huß: Kubernetes Patterns

Bilgin Ibryam und Roland Huß: Kubernetes Patterns

Bilgin Ibryam, Roland Huß
Übersetzung: Thomas Demming
Kubernetes Patterns: wiederverwendbare Muster zum Erstellen von Cloud-nativen Anwendungen

1. Auflage
O’Reilly
2020, XX, 237 Seiten, Broschur
ISBN: 978-3-96009-132-5

Informationen zum Buch im Katalog der Deutschen Nationalbibliothek.



Die Autoren beschreiben in ihrem Buch Kubernetes Patterns typische wiederkehrende Aufgaben bei der Bereitstellung, dem Betrieb und der Entwicklung Cloud-nativer Anwendungen und wie diese mit Hilfe von Kubernetes umgesetzt werden können.

Struktur

Das Buch Kubernetes Patterns besteht aus einer Einführung und fünf Teilen. Wie bei vielen IT-Fachbüchern ist dem Ganzen noch eine Einleitung vorangestellt, in der, die im Buch verwendeten Konventionen, der Leserkreis und der zu erwartende Inhalt kurz umrissen werden.

Die Einführung fasst die wichtigsten Features von Kubernetes als Wiederholung zusammen, z.B. Pods, Namespaces oder Services und stellt mit Microservices und Cloud-nativen Anwendungen den Bezug zur heutigen Welt der Softwareentwicklung her.

In den folgenden fünf Teilen werden typische, wiederkehrende Aufgaben aufgegriffen und es wird beschrieben, wie diese mit Hilfe von Kubernetes effektiv gelöst werden. Jeder Teil enthält die einzelnen Patterns als Kapitel, die wiederum in die Unterabschnitte: Problem, Lösung, Diskussion und Weitere Informationen eingeteilt sind. Die Kapitel sind über das ganze Buch fortlaufend nummeriert.

Die ersten beiden Teile heißen Grundlegende Patterns und Verhaltens-Patterns. Sie bilden gemeinsam einen Spannungsbogen von den Grundbausteinen hin zu verschiedenen Kombinationsmöglichkeiten dieser Elemente. Danach folgt im dritten Teil mit den Strukturellen Patterns ein Schwenk in Richtung Softwareentwicklung. Der vierte Teil heißt Konfigurations-Patterns und steigt in die Details der Konfiguration einer Anwendung in Kubernetes ein. Im fünften Teil geht es schließlich um Fortgeschrittene Patterns.

Inhalt

Einführung

In der Einführung wird das zugrunde gelegte Szenario von Cloud-nativen Anwendungen, Microservices und Kubernetes als Self-Service-Plattform vorgestellt. Dabei werden grundlegende Elemente von Kubernetes, wie z.B. Pods, Namespaces oder Services kurz zusammengefasst; dies ist aber keine vollständige Einführung in Kubernetes.

Grundlegende Patterns

Ausgehend von dem in der Einleitung beschriebenen Szenario stellt sich zunächst die Aufgabe einen einzelnen Container sozusagen als Baustein auf einem Node zu platzieren, zu starten und am Laufen zu halten. Für diese Grundaufgaben haben die Autoren fünf Patterns identifiziert.

Das Erste ist Predictable Demands. Dieses Pattern wird als „Grundlage des erfolgreichen Deployens, Managens und gemeinsamen Existierens von Anwendungen in einer gemeinsam benutzten Cloud-Umgebung“ bezeichnet (siehe Seite 15). Um eine gleichmäßige Verwendung der vorhandenen Ressourcen zu gewährleisten, sollten für jeden Pod der tatsächliche Bedarf an RAM und CPU angegeben werden. Überraschend ist dabei, dass die gewünschte Verfügbarkeit eines Pods auch zu diesen Angaben gehört, da ansonsten Kubernetes bei Ressourcenknappheit nicht zögert einzelne Pods zu verschieben oder gar zu entfernen.

Das nächste Pattern Declarative Deployment ruft in Erinnerung, dass Kubernetes grundsätzlich deklarativ arbeitet. Das gilt auch für das Platzieren und Aktualisieren eines Containers: „Diese Abstrahierung kapselt den Upgrade- und Rollback-Prozess einer Gruppe von Containern und macht dessen Ausführung zu einer wiederholbaren und automatisierten Aktion“ fassen die Autoren auf Seite 25 den Kern dieses Vorgehens zusammen. Danach folgt eine Beschreibung der verschiedenen Umsetzungsvarianten, jeweils charakterisiert anhand der Gesamtzahl der Pods, der Zahl der Pods pro Knoten und der Anzahl von Pods mit einer bestimmten Softwareversion.

Wenn der Container erst einmal läuft und auf dem neuesten Stand ist, stellt sich die Aufgabe den darin enthaltenen Service am Laufen zu halten. Dies ist im Pattern Health Probe beschrieben. Man erfährt, dass Kubernetes zwar den im Container laufenden Startprozess überwacht, dies aber für ein komplettes Bild über den Zustand eines Containers nicht ausreicht (siehe Problembeschreibung auf Seite 33). Als zusätzliches Merkmal muss ein Container deshalb zwei von außen aufrufbare Funktionen bereitstellen: die Liveness-Probe und die Readyness-Probe (siehe Seite 34 f.).

Container werden durch Kubernetes instanziiert und wieder verworfen. Ein einzelner Container ist dem völlig ausgeliefert und hat keine Kontrolle darüber. Der im Container laufende Prozess kann sich jedoch über seine bevorstehende Beendigung informieren lassen, um z.B. noch Daten schreiben zu können. Dieses Pattern haben die Autoren Managed Lifecycle genannt. Sie beschreiben dort die Möglichkeiten diese Nachrichten zu erhalten, z.B. als Signal oder als WebHook.

Teil I des Buches wird durch das Pattern Automated Placement abgeschlossen. Per Default entscheidet Kubernetes selbst, auf welchem Knoten ein Container platziert wird, um die zur Verfügung stehenden Ressourcen gleichmäßig auszulasten.  Die Autoren beschreiben, dass es eine ganze Reihe von Möglichkeiten gibt, auf diesen Vorgang Einfluss zu nehmen, z.B. durch Vorgabe eines einzelnen Knoten bzw. einer Gruppe von Knoten, durch Gruppierungsregeln oder durch das Sperren bzw. Entsperren von Knoten.

Verhaltens-Patterns

Im zweiten Teil geht es um das Verhalten eines Containers zur Laufzeit. Es wird dazu betrachtet, wie lange der im Container ausgeführte Prozess läuft, wie viele Container-Instanzen jeweils benötigt werden und wie diese auf den vorhandenen Nodes platziert werden.

Im einfachsten Fall gibt es einen oder mehrere Arbeitsschritte, die irgendwann fertig sind und sich dann beenden. Dies wird von den Autoren als das Pattern Batch Job zusammengefasst. Die Arbeitsschritte können alleine oder in mehreren Job-Instanzen parallel ausgeführt werden. Die Aufgabe muss für das gewünschte Verhalten, z.B. parallele Ausführung, passend in Containern abgelegt sein. Die Autoren machen klar, dass Kubernetes diese Vorarbeit nicht abnimmt. Hat man das geschafft, bietet Kubernetes aber volle Kontrolle über die Ausführung und definiert Kriterien zur Unterscheidung von Erfolg oder Fehlerfall.

Für den Fall, dass ein Job zeitlich wiederholt werden soll, wird von den Autoren das Pattern Periodic Job eingeführt. Hier beschreiben sie, wie man einen Batch Job zeitgesteuert ausführt und verwenden dazu den von Unix-artigen Betriebssystemen bekannten Cron-Job als Analogie.

Nach diesen beiden Eingangskapiteln wenden sich die Autoren den dauerhaft laufenden Services zu. Die Szenarien werden nach der Anzahl der Instanzen und nach deren Verteilung auf die vorhandenen Knoten unterschieden. 

Zunächst wieder eine Analogie aus der Unix/Linux-Welt: das Pattern Daemon Service. Ganz analog zu Daemon-Prozessen ist damit „das Platzieren und Ausführen priorisierter, auf die Infrastruktur fokussierter Pods auf den Zielknoten“ (siehe Seite 71) gemeint. Als Anwendungsbeispiele werden Log-Kollektoren, Metrik-Exporter oder Kube-Proxy-Prozesse genannt. Obwohl Daemon Services meist von Administratoren eingesetzt werden, heben die Autoren hervor, dass auch Anwendungsentwickler dieses Feature unbedingt kennen sollten.

Im umgekehrten Fall, wenn es genau eine ausfallsichere Instanz eines Service geben soll, liegt das Pattern Singleton Service vor. Die Autoren beschreiben die Realisierung mit einem ReplicaSet und auch die Grenzen dieser Lösung. So garantiert Kubernetes nicht die Maximalzahl, sondern die Minimalzahl von Instanzen, so dass in Grenzfällen kurzzeitig mehr als eine Serviceinstanz vorhanden sein kann. Die Autoren kommen zu dem Schluss, dass bei strengen Anforderungen an die Singleton-Eigenschaft, sich diese nur durch einen Auswahlmechanismus (z.B. Lock) in der Anwendung selbst realisieren lässt.

Ein weiteres umfangreiches Kapitel ist das über das Pattern Stateful Service. Als erstes leiten die Autoren mit einem ReplicaSet in das Thema ein und beschreiben warum diese Realisierung nicht sicher ist und z.B. zu inkonsistenten Daten führen kann. So sind mit State oft lokale Daten gemeint, aber auch Reihenfolge und Identität wie z.B. Netzwerknamen. Diese sind immer dann nötig, wenn sich die Instanzen gegenseitig Aufrufen können sollen, z.B. für einen Leader-Election-Mechanismus. Die beiden Autoren beschreiben dann die richtige Lösung mit einem Kubernetes StatefulSet.

Es folgt das Pattern Service Discovery: „Es bietet einen stabilen Endpunkt, über das die Clients eines Service auf dessen Instanzen zugreifen können“, fassen die Autoren dieses Pattern auf Seite 95 zusammen. Es geht um das Grundproblem eines verteilten Systems, dass es mehrere implementierende Serviceinstanzen gibt, der Client diese aber nicht einzeln kennen soll und netzwerkmäßig auch gar nicht direkt erreichen kann. Es wird also eine Vermittlung benötigt. Im Folgenden besprechen die Autoren eine ganze Reihe von Szenarien (siehe Tabelle 12-1 auf Seite 109) z.B. für interne Container oder für den Aufruf externer Services.

Zum Abschluss von Teil II gibt es noch das Pattern Self Awareness, in dem ein Container bzw. ein Pod Informationen aus der eigenen Deklaration auslesen kann. Dies ist z.B. nötig, um sich an die vereinbarte RAM-Grenze zu halten. Wir erfahren, dass dies mit der sog. Downward API von Kubernetes realisiert wird, über die sich Daten aus der Deklaration des Pods oder Laufzeitdaten, wie z.B. die POD-ID als Umgebungsvariable oder als Datei zur Laufzeit einfügen lassen.

Strukturelle Patterns

Teil III legt den Schwerpunkt auf die Entwicklung von Pods und beschreibt, wie Container zusammengefügt werden können, um mit dieser Struktur Standardaufgaben effizient und wiederverwendbar zu lösen.

Für das Pattern Init Container ziehen die Autoren wieder die Analogie zwischen Klassen in der OOP und Containern heran. So entspricht der Init Container dem Konstruktor einer Klasse. Die komplette Initialisierung soll als separierter Arbeitsschritt in einen eigenen Container ausgelagert werden. „Init Container ermöglichen eine Separation of Concerns und sie erlauben es, Container für einen einzigen Zweck zu bauen“ schreiben die Autoren zur Begründung auf Seite 119.

Das Grundprinzip der Separation von Zuständigkeiten liegt auch dem Pattern Sidecar zu Grunde. „Das Sidecar Pattern beschreibt diese Art der Zusammenarbeit, bei der ein Container die Funktionalität eines anderen, bestehenden Containers erweitert“, heißt es dazu im Text (Seite 125 – eigene Hervorhebung). Zur Verdeutlichung vergleichen die Autoren das Sidecar Pattern mit aspektorientierter Programmierung, bei der orthogonale Fähigkeiten in ein bestehendes Programm injiziert werden (siehe Seite 130).

Teil III wird abgeschlossen durch zwei spezielle Sidecar-Anwendungen. Dies ist zum einen das Adapter Pattern, das den Zugriff auf einen bestimmten Teilaspekt eines Service bereitstellt, z.B. um Metriken auszulesen, oder den Zugriff auf einen Pod als einheitliche Schnittstelle organisiert, falls der Pod diese nicht selbst anbietet.

Zum anderen gibt es das Pattern Ambassador, mit dem als Proxy der Zugriff auf eine externe Schnittstelle ermöglicht wird. Auch hier gilt wieder Separation als Grundprinzip, d.h. der Zugriff auf die externe Schnittstelle wird in einen eigenen Container ausgelagert und belastet bzw. verändert damit nicht den ursprünglichen Container.

Konfigurations-Patterns

Teil IV beschäftigt sich relativ kompakt mit verschiedenen Szenarien bei der Konfiguration von Anwendungen in Kubernetes.

Im Pattern EnvVar Configuration beschreiben die Autoren wie man Konfigurationsparameter mit Hilfe von Umgebungsvariablen beim Start des Pods übergibt und welche Quellen dafür in Frage kommen, z.B. eine Kubernetes ConfigMap. Sie weisen darauf hin, dass diese Methode zwar sehr einfach ist, die Werte aber nicht mehr geändert werden können, wenn der Pod erst einmal läuft.

Man kann sich auch den Umweg über die Umgebungsvariablen sparen und die Konfigurationsdaten direkt lesen. Dieses Vorgehen wird im Pattern Configuration Ressource zusammengefasst. Kubernetes stellt dazu ConfigMaps und Secrets als Elemente zur Verfügung, die dann zur Laufzeit als Files im Container gelesen werden können.

Um die Beschränkung der Datenmenge von ConfigMaps und Secrets zu umgehen, schlagen die Autoren das Pattern Immutable Configuration vor. Die umfangreichen Daten werden dazu in einen eigenen Container verpackt und dem Pod zugeordnet, so dass sie zur Laufzeit ausgelesen werden können. Wie der Name sagt, können die Daten zur Laufzeit nicht geändert werden, bzw. jede Änderung würde nach einem Neustart verloren sein.

Mit dem Pattern Configuration Template beschreiben die Autoren schließlich den Fall großer und komplexer Konfigurationsdaten, die mehrfach verwendet werden und sich dabei nur punktuell unterscheiden. Jeweils eine komplette Kopie der Daten wäre Verschwendung. Als Lösung schlagen sie die Verwendung eines externen Template-Frameworks vor – Kubernetes selbst bietet hier keine eigenen Konzepte an.

Fortgeschrittene Patterns

Teil V bildet das Finale. Dort sind verschiedene spezielle Features von Kubernetes gesammelt und erklärt.

In Kapitel 22 steigen die Autoren in die Maschinerie von Kubernetes ein und stellen das Konzept des Controllers vor, die das „Herz von Kubernetes bilden“ (siehe Seite 169). Mit Hilfe eines Controllers können Kubernetes-Ressourcen, wie z.B. Pods beobachtet und verändert werden. Anhand von Beispielen wird erläutert, wie man einen eigenen Controller schreibt und in Kubernetes integriert.

Schon auf Seite 171 wird der Controller klar gegen einen Operator abgegrenzt, dem das nächste Kapitel gewidmet ist. Es wird darin beschrieben, wie man Kubernetes mit einem eigenen Ressourcentyp und dem zugehörigen Operator erweitert. Die Autoren sehen im Operator eine Spezialisierung des Controllers, daher auch die genaue Abgrenzung. Auch hier gibt es wieder ein detailliertes Beispiel.

Das Pattern ElasticScale behandelt den Fall, dass es nicht möglich ist den Ressourcenbedarf eines Service genau zu beschreiben, z.B. weil die Zahl der Requests pro Stunde stark variiert. Als Lösung stellen die Autoren den Horizontal Pod Autoscaler (HPA), den (experimentellen) Vertical Autoscaler (VPA) und den Cluster Autoscaler (CA) von Kubernetes vor. Der CA nimmt dabei eine Sonderrolle ein, da er nicht auf Kubernetes als Plattform beschränkt ist, sondern mit der Cloud-Plattform interagiert, um z.B. einen weiteren Knoten bereitzustellen (siehe Seite 208). Das Kapitel wird durch einen Vergleich und eine Einordnung der drei Varianten abgerundet.

Mit dem Pattern ImageBuilder werfen die Autoren die interessante Frage auf, ob es in Zeiten von Continuous Deployment (CD) nicht besser wäre, die Container-Images und alle dazu benötigten binären Artefakte an Ort und Stelle im Produktivsystem zu bauen. Für Kubernetes ist der Buildvorgang dann ein überwachter Prozess wie jeder andere auch, d.h. es gibt keine besondere Unterstützung durch Kubernetes. Als Lösung stellen die Autoren zwei Frameworks vor, an denen sie selbst beteiligt sind: OpenShiftBuild und KnativeBuild.

Diskussion

Die Autoren Ibryam und Huß stellen in Ihrem Buch Kubernetes Patterns auf originelle Weise eine Auswahl von Features von Kubernetes vor und verbinden diese mit wiederkehrenden Anwendungsfällen beim Betrieb und bei der Entwicklung containerbasierter, verteilter Anwendungen. Darauf wird der Leser schon durch das Vorwort von Brendan Burns, einem Mitbegründer des Kubernetesprojekts, eingestimmt: „Jedes Objekt oder Feature, das zu Kubernetes hinzugefügt wurde, steht für ein grundlegendes Werkzeug, das dazu entworfen und gebaut wurde, eine spezifische Anforderung des Software-Designers zu erfüllen. Dieses Buch erklärt, wie die Konzepte in Kubernetes Probleme in der Praxis lösen… “ (siehe Seite XI)

Und genau das erwartet den Leser. Das Buch vertieft evtl. vorhandenes Vorwissen und schafft durch seine Struktur Ordnung in der verwirrenden Vielzahl von Features von Kubernetes. Die Kapitel sind technisch fundiert geschrieben und selbst nach mehrjähriger Erfahrung mit Kubernetes gibt es noch Neues zu entdecken, z.B. wie genau ein Pod auf einen der vorhandenen Nodes platziert wird. Das Buch hilft bei der Frage: Wozu brauche ich ein Feature und wie kann es sinnvoll in die Entwicklung und Betrieb eines verteilten Systems eingesetzt werden, um die Komplexität und den Umfang des eigenen Codes zu reduzieren? Und bezogen auf die Welt der Clouds, in der alles ein Preisschild hat, hilft es Kosten zu sparen, indem man z.B. den tatsächlichen Ressourcenbedarf herausfindet und beschreibt.

Auf die Tatsache, dass es sich um eine Auswahl von Themen handelt, wird man schon in der Einleitung durch die Autoren selbst hingewiesen: dieses Buch ist keine Einführung in Kubernetes und keine Referenz, steht auf Seite XVII in der Einleitung.

Ganz in diesem Sinne fehlt eine Anleitung zur Installation von Kubernetes, es gibt aber Verweise auf andere Bücher, die dies beschreiben. Ebenso fehlen Aspekte wie Ausfallsicherheit der Plattform, Durchführung von Updates von Kubernetes, Erzeugen von Backups und Sicherheit, z.B. durch verschlüsselte Kommunikation oder Anwenden von Rollen und Berechtigungen.

In der Einleitung heißt es auch: „Das Buch ist in einem lockeren Stil geschrieben und ähnelt eher einer Reihe von Essays, die unabhängig voneinander gelesen werden können“ (Seite XVII). Ganz so unabhängig sind die Kapitel dann aber zum Glück nicht, es gibt immer Verweise auf schon behandelte Themen oder später zu besprechende Features, so dass man sich während des Lesens gut orientieren kann, vor allem wenn man das Buch nicht von vorne bis hinten durchliest. Die Aufteilung in ein Pattern pro Kapitel lädt ja dazu ein.

Das aktuell besprochene Thema wird immer wieder genannt. Meiner Meinung nach übertreiben es die Autoren dabei. Der Text könnte an vielen Stellen gestraffter, zusammenhängender und flüssiger sein. Hier ein Beispiel von Seite 15:

Die Grundlage […] liegt darin, die Ressourcen-Anforderungen und Laufzeit-Abhängigkeiten der Anwendungen zu identifizieren und zu deklarieren. Dieses Pattern Predictable Demands dreht sich darum, wie Sie Anwendungs-Anforderungen deklarieren sollten – egal, ob das harte Laufzeit-Anforderungen oder Ressource-Anforderungen sind. Das Deklarieren Ihrer Anforderungen ist für Kubernetes essentiell,..

S. 15

Hmm, ein bisschen viel Anforderung…

Oder auf Seite 71:

Diesem Konzept entsprechen die DaemonSets in Kubernetes. Angesichts der Tatsache, dass es sich bei Kubernetes um eine verteilte Plattform handelt, die über mehrere Knoten verteilt ist und vor allem zum Ziel hat, Anwendungs-Pods zu managen, wird ein DaemonSet durch Pods repräsentiert, die auf den Cluster-Knoten laufen und im Hintergrund Funktionalität für den Rest des Clusters bereitstellen.

S. 71

Den Satz musste ich mehrfach lesen und der Einschub über Kubernetes als Manager für Anwendungs-Pods hilft hier nicht wirklich weiter. Das dies so ist, wurde schon vorher ausführlich festgestellt.

Der Text holt die Leser ab, wo sie gerade stehen und präsentiert jede Menge Buzzwords als Einladung, Microservices sind ein Muss, CleanCode sowieso. Neben den notwendigen Fachbegriffen schimmert an manchen Stellen gar zu viel Englisch durch. Es wird gemanagt (Seite 47, 83), deployt (Seite 15) und geschedult (Seite 17, 56). Es haben sich Liveness- und Readyness-Proben (Seite 34) eingeschlichen und etwas passiert zur Runtime. Das ist nicht schön, hält sich aber für ein Fachbuch noch im Rahmen.

Positiv zu bemerken ist, dass das Buch in einen größeren Rahmen eingebettet ist. Es gibt viele externe Links zur Vertiefung eines Themas. Die Beispiele im Buch und noch viele Extras sind als externe Projekte
verfügbar. Das macht den Text kompakt, er wird nicht durch das seitenweise Abdrucken von Sourcecode aufgebläht. Außerdem werden die Beispiele im Nachhinein noch gepflegt.

Durch die Auswahl an Themen ist dieses Buch vor allem für Leser geeignet, die die Welt der Softwareentwicklung, der Cloud-nativen Anwendungen und Microservices mit dem Kosmos von Kubernetes verbinden wollen.

Im Nachwort taucht er dann doch noch auf: der Verweis auf das Standardwerk Design Patterns von Gamma, Helm, Johnson und Vlissides, als augenzwinkernder Abschluss, wohl wissend, dass nicht die Autoren allein darüber entscheiden, welche Relevanz ihrem Buch am Ende zugemessen werden wird.


Zu den Autoren

Bilgin Ibryam

Bilgin Ibryam ist Softwarespezialist, Produktmanager und Architekt, OpenSource-Aktivist, Autor und Blogger mit langjähriger Erfahrung mit skalierbaren und ausfallsicheren verteilten Systeme unter anderem bei RedHat oder der Apache Software Foundation.

Siehe auch:
https://developers.redhat.com/authors/bilgin-ibryam
http://www.ofbizian.com/p/about.html

Roland Huß

Roland Huß ist leitender Softwareentwickler bei RedHat und arbeitet zur Zeit im Serverless-Team am Knative Projekt und hat daneben schon zu vielen weiteren OpenSource-Projekten beigetragen. Er ist Autor und tritt regelmäßig als Redner auf Konferenzen auf.

Siehe auch:
https://jax.de/speaker/dr-roland-huss/


Folgende Bücher könnten Sie ebenfalls interessieren:

Stefan Schäfer-Wetzler

Stefan Schäfer-Wetzler ist Software-Engineer mit über zwanzig Jahren Berufserfahrung in den Bereichen Entwicklung, Software-Architektur, technische Projektleitung, Beratung und Dev-Ops. Er hat mit C/C++ angefangen, um sich dann auf die Sprache Java und dessen Ökosystem zu fokussieren. In den letzten Jahren hat er sich zunehmend auf Automatisierungsaspekte Cloud-basierter Microservice-Architekturen mittels Docker und Kubernetes spezialisiert.

Schreibe einen Kommentar