Technology Diversity
Überall schießen Microservice Frameworks wie Pilze aus dem Boden – sei es für Java, sei es für NodeJS. Alle versprechen die verschiedenen Anforderungen von Microservice Architekturen auf einfache Art und Weise zu unterstützen: unabhängiges Deployment, Service Discovery, Überwachung, Skalierung von Diensten, Selbstheilung und vieles mehr.
Fast immer wird dabei jedoch ein wichtiges Prinzip von Microservices unterschlagen: ihre Technologieunabhängigkeit.
In einem grösseren Umfeld möchte ich nämlich weder Programmiersprachen und Frameworks, noch die Art und Weise wie ich Daten persistiere, vorschreiben. Jedes Team, das an einem bestimmten fachliche Problem arbeitet, weiß genau, welche Programmiersprache und welches Framework für seine spezifische Domäne am besten geeignet ist. Vielleicht ist es R oder Java oder auch Go. Das gleiche gilt für die Persistenz. Handelt es sich um relationale Daten, Graphenstrukturen, Dokumente, Volltextsuchen oder genügt ein Filesystem? Die Persistenz muss zu den Daten und den Zugriffen darauf passen. Ebenso notwendig ist es, den Teams eine Möglichkeit zu bieten, mit neuen Technologien zu experimentieren, ohne grosse Aufwände oder Risiken zu erzeugen.
Die Unterstützung des Prinzips der Technologieunabhängigkeit der Microservice ist also insgesamt sehr wichtig.
Deployment und Konfiguration
Microservices sollen unabhängig voneinander in Produktion gebracht und betrieben werden können. Wenn wir die Technologieunabhängigkeit aber als wichtiges Prinzip ermöglichen wollen, werden wir schnell mit einer großen Vielfalt von Frameworks und Artefakten konfrontiert werden. Binaries, JARs, WARs, JavaScript Files, Node Module oder GEMs wollen als Artefakte verwaltet und in Betrieb gebracht werden.
Aber nicht nur das kann aufwändig sein. In unterschiedlichen Microservice Frameworks werden Services auf verschiedene Art und Weise konfiguriert, skaliert und überwacht. Die einen haben eine JSON Konfiguration, die anderen ein Dashboard, der nächste nutzt XML. Verschiedene Frameworks verstehen unter einem Service und einer App etwas anderes. Werden dann unterschiedliche Persistenzen verwendet, kommt noch die Vielfalt der Datenbanken und ihre spezifischen Konfigurationen hinzu. Wer möchte einen solchen Zoo konfigurieren und betreiben?
Container
Zum Glück haben wir eine Technologie, die uns bezüglich der Artefakte ein generelles, übergreifendes “Format” bietet: Container Images. Da Images generische Artefakte mit einem Prozess und seiner Laufzeitumgebung sind, bringen sie eine wesentliche Vereinfachung mit sich. Wir können damit jeden Service, egal in welcher Programmiersprache, mit welchem Framework auch immer, ohne Unterschied exakt gleich behandeln. (Verwenden wir zudem eine Image Registry, können wir unsere Artefakte auch in gleicher Weise verwalten.)
Wenn aber Container unsere Artefakte und somit unsere Services sind, dann können Dinge wie Service Discovery, Load Balancing, Configuration Management, Self-Healing, Deployment, Scheduling oder Resource Management sinnvoller Weise auch nur auf der Containerebene stattfinden und nicht durch ein Framework im Container.
Container Manager
An dieser Stelle beginnt die Arbeit der Container Manager (wie zum Beispiel Kubernetes). Sie sollen unter anderem:
- Container in einem Cluster koordinieren
- die Hardware abstrahieren
- ein initiales Deployment definierbar machen
- den jeweiligen (Ziel-)Status des Cluster deklarativ beschreibbar machen
- Multi-Container durch eine einzelne Entität darstellen
- Routineaufgaben wie placement, healthchecks, healing, scaling, etc. erledigen
- Crossfunctional- oder Nonfunctional Requirements (XFR/NFR) wie discovery, jobs, log aggregation, metrics collection, etc. erfüllen.
Allgemein werden diese Aufgaben durch jeden Container Manager mehr oder weniger umfangreich erledigt. Wichtig ist aber: Nichts davon sollte in den Containern selbst implementiert werden. Die Teams, die diese Container erstellen, können sich dadurch ebenso ganz auf die Entwicklung ihrer Anwendung als Microservice konzentrieren, wie der Betrieb auf seinen Container Manager. Das vereinfacht auch die Arbeit der jeweiligen Abteilungen, denn die Schnittstelle zwischen ihnen ist eindeutig - es sind die Container.
Natürlich bestehen Microservices üblicherweise aus mehreren Containern und haben unterschiedliche Betriebs-Szenarien. Nicht nur die Geschäftslogik selbst, sondern auch Datenbanken, Messaging o.ä. sind möglich. Ein gutes Container Application Design (siehe single oder multi container patterns) hilft hier, durch eine passende Teilung der Aufgaben zu den entsprechenden Containern zu kommen. Die Container Manager unterstützen dies durch einen umfangreichen Abstraktionslayer oberhalb der Container, eine einheitliche Schnittstelle und eine gute Abdeckung unterschiedlichster Betriebs-Szenarien (stateful apps, daemons, replica sets, finite workloads, etc).
Wenn wir so ein mächtiges Tool haben, sollten wir es dann nicht auch möglichst umfangreich nutzen?
Die Vorteile von Container Managern
Container Manager sind zwar komplex, mehrere zusätzlich Dienste müssen dazu installiert und gewartet werden, die Container Ablaufumgebung erhält eine zusätzliche Schicht der Hardwareabstraktion. Dennoch gewinnen wir folgende Vorteile:
- Alles (ob Anwendung, Datenbank, Messaging oder Cluster Filesystem) kann durch den Container Manager einheitlich verwaltet werden.
- Routineaufgaben und viele XFR/NFR können auf den Container Manager verlagert werden
- Das verwendete Framework im Anwendungs-Container kann einfach sein
- Es kann ein Linux verwendet werden, dass sich auf den Betrieb von Containern konzentriert und dadurch mehr Sicherheit gewährleisten kann
- Der Vendor Lock zu einem Infrastrukturanbieter wird geringer
- Alle Tätigkeiten sind Anwendungsorientiert
- Die Entwickler der Microservice können sich besser auf ihre Geschäftsdomäne konzentrieren
Zusammenfassung
Ist ein Container Manager komplex? Ja vielleicht, denn distributed computing ist komplex. Microservice werden mit einer Vielzahl von Technologien implementiert, laufen verteilt auf unterschiedlichen Knoten, sind ggf. stateful, müssen überwacht und eventuell neu gestartet werden und nutzen eine Vielzahl von weiteren Komponenten.
Ein solches komplexes Gesamtsystem zu orchestrieren, dafür eignen sich Container Manager am allerbesten. Durch ihren allgemeinen Ansatz muss sich niemand im Klein-Klein unterschiedlicher Microservice Frameworks verlieren und notwendige Persistenzen, wie z.B. Datenbanken und verteilte Dateisysteme, gesondert behandeln.
Ein Microservice besteht aus mehreren Containern mit möglichst je nur einem Prozess. Dieser Prozess überwacht sich nicht selbst, trifft keine Entscheidung über Skalierung oder Placement und startet sich auch nicht selbst neu, wenn etwas schief gegangen ist. Der Prozess kann klein, schlank, “einfach” sein und konzentriert sich auf seine Geschäftsdomäne. Der Rest ist Aufgabe des Container Managers. Er ist das perfekte Microservice Framework.