Wir betreiben unsere Anwendung mittlerweile produktiv in einer Container-Plattform. Die ganze Anwendung? Nein nicht ganz. Die Batchverarbeitung ist bislang nicht umgestellt worden und läuft weiterhin auf althergebrachte Art und Weise. Im folgenden Artikel möchte ich euch einen Einblick geben, wie wir uns dem Thema nähern und welche Optionen wir sehen.
Das Beste kommt zum Schluss
Warum haben wir bislang überhaupt die Batchverarbeitung außen vorgelassen? Zum einen lief die Batchverarbeitung schon immer anders als der Rest der Anwendung. Zum anderen ging es darum im Projekt Prioritäten zu setzen und Fortschritte zu erzielen. Einzelne Jobs laufen bei uns mit Spring Batch, einem gängigen Framework im Java-Umfeld. Diese wiederum werden als simpler Java-Prozess von einem externen Scheduler (Automic UC4) gestartet, die Parallelisierung erfolgt über Threads. Der große Rest der Anwendung lief auf einem Weblogic Application Server. Neben der Einführung von Containern sollte hier auch gleich der Application Server abgeschafft werden – daher hatte dies Priorität und die Batchverarbeitung sollte später angegangen werden.
Warum nicht gleich auf Batchverarbeitung verzichten?
Tatsächlich ist es so, dass wir in Summe anstreben, Batchverarbeitung als Lösungsmuster seltener einzusetzen. Klare Strategie ist es, mehr auf (Near) Real Time zu setzen und Änderungen sofort in andere Systeme zu propagieren, beispielsweise durch den Einsatz von Events. Dies funktioniert gut, um z. B. Änderungen an Verträgen sofort an ein Data Warehouse oder eine CRM-Lösung zu melden. So sind die beteiligten Systeme im Sekundenbereich mit aktuellen Daten versorgt. Über klassische, nächtliche ETL-Prozesse wären die Daten immer einen Tag alt.
Allerdings sind viele Prozesse bei Versicherern und auch deren Partnern immer noch stark vom Batch-Gedanken und der Trennung von Tages- und Nachtverarbeitung geprägt. Dazu haben manche Workloads auch keinen weiteren fachlichen Trigger als z. B. den Monatswechsel. Und zu guter Letzt: Wir haben bereits sehr viele Batches im Einsatz, die wir nicht alle loswerden können.
Kubernetes und Batches
Ok, wir brauchen weiterhin Batches. Doch was hindert uns daran, nun einfach auch für die Batchverarbeitung Images auszuliefern und als Container zu starten? Um die Vorteile einer Container-Plattform wie Kubernetes gänzlich auszuschöpfen, müssen die Anwendungen gewisse Anforderungen erfüllen. Die berühmteste Sammlung ist sicher die Beschreibung einer Twelve-Factor App. Ein wesentlicher Punkt gerade auch für die Batchverarbeitung ist, dass die Anwendung damit umgehen können muss. Einzelne Container müssen quasi jederzeit hoch- oder auch wieder heruntergefahren werden können. Das macht Kubernetes, um z. B. dynamisch zu skalieren oder auch um für Betriebssystem-Updates eine Node zu drainen. Batchverarbeitung hat aber meist den Charakter, eher länger zu laufen, oft viele Stunden. Das heißt die Batchverarbeitung muss jederzeit abbrechbar und auch wiederanlauffähig sein. Weiterhin sollten natürlich gerade für Batches die Skalierungsmöglichkeiten der Container-Plattform genutzt werden, schließlich ist die (nächtliche) Batchverarbeitung von Versicherern ein kritischer Pfad.
Viele Möglichkeiten
Der vermeintlich einfachste Weg wäre vermutlich einfach die Job-Container manuell zu starten. Außerhalb von Kubernetes können mehr Zusicherungen gemacht werden, sodass der Container auch über einen längeren Zeitraum verlässlich laufen kann. Aber natürlich gehen so Features und Automatisierungen in Kubernetes verloren. Auch die Skalierung kann dann nur in einer Node erfolgen.
Wir haben mit Jobstream schon eine Lösung, welche darauf setzt einen nativen Kubernetes Job zu starten. Dies wird bislang allerdings nur auf unseren Testumgebungen eingesetzt. Problematisch ist hier, dass die Skalierung weiterhin im Container durch Spring Batch erfolgt. Doch durch den Einsatz einer Container-Plattform möchte man davon profitieren, dass (viele) Standard-Server eingesetzt werden können und horizontal skaliert wird. Einzelne Batches sind allerdings sehr ressourcenintensiv mit sehr hohem Parallelisierungsgrad und Speicherbedarf. Somit bräuchte es wieder größere einzelne Maschinen, da eben nicht im Cluster skaliert wird. Daher ist dieser Ansatz für Testumgebungen passend, aber für den Produktivbetrieb eher ungeeignet.
Aber Kubernetes Jobs sind auch parallelisierbar, sodass im Cluster skaliert werden kann. Dann braucht es aber meist zusätzlich eine Queue, damit die Arbeit zwischen den verschiedenen Container dann sinnvoll verteilt werden kann. Das macht die Lösung natürlich deutlich komplexer und es müssen hier grundsätzliche Concurrency-Probleme in der jeweiligen Technologie für die Queue gelöst werden. Weiterhin müsste entschieden werden, ob jeder Pod nur eine Verarbeitungseinheit ausführt oder so lange läuft, bis keine Verarbeitungseinheit mehr verfügbar ist. Aber Kubernetes könnte auch statisch die Workloads zwischen verschiedenen Pods auf Basis eines Index verteilen. Das alles hat wiederum Auswirkungen darauf, wie viele Pods entstehen können und wie skaliert wird.
Kein „one size fits all“
Was ist jetzt also die richtige Lösung? Wie immer gilt die klassische Berater-Antwort: Es kommt darauf an. Es sind die Anforderungen des jeweiligen Kontextes zu bewerten, um dann eine passende Lösung auszuwählen. Wie habt Ihr das gelöst? Das würde mich interessieren. Kontaktiert mich gern auf LinkedIn.