Richtig skalieren

Einfach nur mehr reicht nicht: Entwicklung und Betrieb skalierbarer Architektur

Christian Neudert, Dustin Huptas

©Shutterstock/mindscanner

Nachdem im ersten Teil des Artikels anhand von fünf Prinzipien vorgestellt wurde, wie sich strukturelle Engpässe innerhalb einer Architektur umgehen lassen, beleuchtet dieser zweite Teil, welche Prinzipien für den Entwicklungsprozess und den Betrieb einer skalierbaren Architektur hilfreich sein können

Im ersten Beitrag wurden vor allem Ansätze zur horizontalen Skalierung betrachtet. Auf Hardwareebene verspricht das Hinzufügen von Standardkomponenten im Idealfall eine proportional zu den Lastanforderungen passende Kostenentwicklung. Zusätzlich wird eine Skalierung im laufenden Betrieb ermöglicht. In den Prinzipien 1 bis 5 wurden dazu passende gesamtarchitektonische Ansätze vorgestellt, u. a. die Lastverteilung durch Sharding, die Zerlegung des Systems in funktional getrennte Dienste oder die Verwendung eines asynchronen, eventgetriebenen Programmiermodells. Was bedeuten diese Entscheidungen für den Entwicklungsprozess und den Betrieb?

API Confernce 2019
 Maik Schöneich

gRPC – Ein neuer heiliger Gral?

mit Maik Schöneich

Oliver Drotbohm

REST Beyond the Obvious – API Design for Ever Evolving Systems

mit Oliver Drotbohm (Pivotal Software, Inc.)

DDD Summit 2019
Nicole Rauch

Event Storming für Domain-driven Design

mit Nicole Rauch (Softwareentwicklung und Entwicklungscoaching)

Horizontal skalierende Systeme benötigen für jeden Skalierungsschritt infrastrukturelle Ressourcen. Aus Effizienzgründen kommt dabei einer elastischen Ressourcenbereitstellung im Bedarfsfall eine steigende Bedeutung zu, gegenüber einem reinen Vorhalten von Überkapazität. Das Einspielen neuer Softwarereleases kann bei einem System mit hohen Anforderungen an die Skalierbarkeit ebenfalls eine Herausforderung darstellen. Je nach Anwendungsfall und damit verknüpften Verfügbarkeitsanforderungen können Wartungsfenster zu selten oder Schwachlastphasen zu kurz für eine Aktualisierung des gesamten Systems sein. Gleichzeitig ist bei horizontaler Skalierung eine steigende Anzahl an Dienstinstanzen zu aktualisieren. Diese bei gleichbleibender Personaldecke zu pflegen, ist eine weitere typische Herausforderung.

Im Zusammenhang mit einer an die Geschäftserfordernisse angepassten Reaktionsgeschwindigkeit werden Mechanismen notwendig, um das System kosteneffizient zu skalieren, die an die Stelle eines reaktiven Kapazitätsmanagements und dem Vorhalten von hohen Überkapazitäten treten (Abb. 1). Die im Folgenden beschriebenen Prinzipien zeigen Lösungsoptionen für die dargestellten Herausforderungen auf und können unabhängig voneinander oder aufeinander aufbauend genutzt werden.

Abb. 1: Vorhalten von Kapazität erzeugt Kosten ohne Nutzen

Prinzip 6: Wahl einer geeigneten Betriebsplattform

Ein besonderes Augenmerk gilt der Auswahl einer geeigneten Betriebsplattform. Diese sollte den heutigen und zukünftigen Anforderungen genügen und insbesondere bei Aufwand und Kosten möglichst linear skalieren. Die Möglichkeit der elastischen Zuteilung von Compute-, Network- und Storage-Ressourcen ist hierbei von zentraler Bedeutung. Die angestrebte Lösung sollte eine Steuerungsfunktionalität über die reine Compute-Ebene hinaus anbieten, selbst wenn diese nicht sofort in vollem Umfang genutzt wird, da der Zeitraum für Auswahl und Migration einer Betriebsplattform typischerweise den Veränderungshorizont der Geschäftsanforderungen übersteigt.

Ein Großteil der heute verfügbaren Systeme und Lösungen bietet bereits die Möglichkeit einer automatisierbaren Ansteuerung. Dabei reicht die Bandbreite von der Kickstart-Installation bereits verbauter Standby-Hardware über einfache Virtualisierungslösungen, die per Skripte gesteuert werden bis zu Cloud-Computing-Plattformen, die zur Ansteuerung der Ressourcen und Auswertung der Zustände über eine zentrale API-Schnittstelle verfügen.

Nach der mittlerweile als Standard etablierten Virtualisierung der Compute-Ressourcen stellt insbesondere die Automatisierung von Netzwerkkomponenten wie Firewalls und Loadbalancern einen der nächsten Schritte dar, um Bereitstellungszeiten zu verkürzen. Weiterhin werden dadurch die Abstimmungen mit dem Betriebsdienstleiter für Standardänderungen vereinfacht bzw. entfallen ganz. Bei der Auswahl einer geeigneten Plattform sind außerdem die Anforderungen hinsichtlich Datenschutz und -sicherheit zu berücksichtigen. Sie bestimmen maßgeblich, ob entweder eine dediziert betriebene physische Umgebung mit einfacher Virtualisierung (bzw. eine Private Cloud), eine mit anderen Kunden gemeinsam genutzte Public Cloud oder mit der Hybrid Cloud ein Mischmodell gewählt wird.

Alle angeführten Modelle erfordern mindestens eine Auseinandersetzung mit dem Betriebssystem und den darauf betriebenen Softwareablaufumgebungen, sodass dieses Know-how idealerweise intern vorgehalten wird. Einen Schritt weiter geht der Platform-as-a-Service(PaaS-)Ansatz, bei dem der Betriebsdienstleister eine standardisierte Softwareablaufumgebung zur Verfügung stellt. Hierbei kann sich die IT primär auf die eigentliche Softwareentwicklung konzentrieren. Dies setzt allerdings die Vereinbarkeit mit der einhergehenden Homogenisierung der Betriebsplattform voraus und schränkt die Wahlmöglichkeiten auf die am Markt als PaaS-Lösungen angebotenen Softwareablaufumgebungen ein.

Prinzip 7: Festlegen einer passenden Delivery-Strategie

Ein weiterer Baustein, um eine bedarfsgerechte Skalierung umzusetzen, ist die Wahl einer passenden Delivery-Strategie zur Softwareverteilung und -aktualisierung. Diese sollte zum Lastprofil des Systems passen und muss gleichzeitig den Anforderungen nach Veränderung am System gerecht werden. Klassisch wird ein Release für alle Systemkomponenten bereitgestellt, ausführlich getestet und während einer Schwachlastphase oder eines Wartungsfensters installiert. Das ist für Systeme mit konstant hoher Last nur schwer umsetzbar. Zudem sind in vielen Systemen die Änderungsraten der einzelnen Teilsysteme unterschiedlich, werden aber durch die Synchronisierung vom Teilsystem mit dem längsten Zyklus vorgegeben.

In einem solchem System lohnt sich die im ersten Beitrag vorgestellte hierarchische Zerlegung in funktional getrennte Vertikale, da diese unabhängige Releasezyklen erlauben. Das dazu passende Muster nennt sich „Deployment-Pipeline“ und hat mit dem Paradigmenwechsel rund um Continuous Delivery Einzug gehalten (Kasten: „Deployment-Pipeline“). Damit wird auf der einen Seite die Komplexität für Entwicklungsteams gesenkt, da sie nur innerhalb ihrer Vertikalen für die Entwicklung und Lieferung skalierbarer Dienste verantwortlich sind. Andererseits muss auf die Einhaltung stabiler Schnittstellen und die nichtfunktionale Anforderung Lieferfähigkeit bei der Entwicklung neuer Funktionen geachtet werden. Entwicklungsvorgehen wie „Feature Toggles“, abwärtskompatible Schnittstellen oder „Branch by Abstraction“ haben sich in solchen Fällen bewährt.

Deployment-Pipeline
Eine Deployment-Pipeline ist die Umsetzung zentraler Konzepte aus dem „Continuous Delivery“. Dessen Ziel es ist, zu jeder Zeit Software in einem getesteten lieferfähigen Zustand bereitzustellen. Dazu wird die Deployment-Pipeline als zuverlässiger, wiederholbarer Prozess etabliert. Eine Pipeline besteht aus mehreren Stufen. Die erste prüft bei jeder Codeänderung im Versionsverwaltungssystem, ob diese kompiliert werden kann, alle Unit Tests besteht und den festgelegten Softwaremetriken gerecht wird. Aufbauend darauf enthält eine Deployment-Pipeline Stufen, um das paketierte Softwareartefakt automatisiert auf verschiedenen Testsystemen, zum Beispiel für Lasttests, auszurollen und diese Tests auszuführen. Nicht alle diese Stufen sind automatisiert, meist existiert noch ein manueller Testschritt vor der finalen Freigabe. Die sonst im Entwicklungsprozess nachgelagerten Schritte der Integration, des Deployments und des Testens werde in einer Deployment-Pipeline standardisiert und kontinuierlich ausgeführt. Zusätzlich zu den qualitätsgesicherten Softwareartefakten wird auch der Prozess der Bereitstellung kontinuierlich durchgeführt und verbessert – getreu einem Leitsatz des Continuous Delivery „If it hurts, do it more often.“.

Abb. 2: Blue-Green Deployment

Aus betrieblicher Sicht steigt mit jeder unabhängigen Deployment-Pipeline auch die Anzahl der Deployments und somit das Risiko, Instabilität in das System einzubringen. Diesem kann mit angepassten Deployment-Mechanismen, die ein Fallback-Szenario enthalten, begegnet werden.

Eine solche Variante, um gänzlich ohne Wartungsfenster auszukommen, ist das Blue-Green Deployment. Dabei werden neue Dienstinstanzen installiert, aber noch nicht im Load Balancer aktiviert (Abb. 2). Dies geschieht erst nach erfolgreichen Funktionstests und zeitgleich mit Deaktivierung der alten Instanzen. Diese werden noch eine zeitlang vorgehalten, um im Problemfall reaktiviert zu werden. Anschließend können diese gelöscht werden, um Ressourcen für das Deployment eines anderen Diensts freizugeben. Eine weitere Variante ist das so genannte Canary Releasing. Bei diesem wird im Unterschied zum Blue-Green Deployment zunächst ein Teil der Last auf einige aktualisierte Dienstinstanzen umgeleitet. Ist deren Verhalten fachlich korrekt und auch unter realen Lastanforderungen vergleichbar zur alten Version, wird die neue Version auf allen Instanzen installiert.

Die beschriebenen Ansätze erlauben eine risikoarme Softwareaktualisierung für Systeme mit sehr hohen Verfügbarkeitsanforderungen. Der Rollout verschiebt sich außerdem vom nächtlichen Arbeiten während Schwachlastphasen oder geplanten Wartungsfenstern hin zu einer täglichen und automatisierbaren Routineaufgabe.

Prinzip 8: Durchgängige Automatisierung umsetzen

Betriebsteams stehen vor der Herausforderung, neben der steigenden Anzahl an Deployments die Plattform den Lastanforderungen entsprechend zu skalieren und im Bedarfsfall Lasten über ein hybrides Betriebsmodell von der dedizierten Infrastruktur kurzfristig in eine Shared-Infrastruktur verschieben zu können. Den Grundstein für eine Umsetzung mit überschaubarem Aufwand lässt sich mit der Automatisierung von Deployments und Konfiguration der Dienste legen. Ein Ziel von Deployment-Pipelines ist die einheitliche Installation von Softwareartefakten über alle beteiligten Systeme bis in die Produktion. Für eine skalierbare Architektur geht die Automatisierung über das Ausrollen eines Softwareartefakts hinaus. Die Bereitstellung von Ressourcen und die Konfiguration der notwendigen Infrastruktur sind für schnelles horizontales Skalieren notwendig. Dazu müssen neben der Dienstinstanz selbst auch Infrastrukturkomponenten wie Load Balancer einfach, idealerweise über ein API, konfiguriert werden können (siehe Prinzip 6).

Der Schlüssel für diese Geschwindigkeit liegt in der standardisierten Instanziierung der Dienste und der dynamischen Konfiguration zur Laufzeit. Die deklarative Beschreibung eines Sollzustands entwickelt sich immer mehr zum Standard für den Aufbau von Systemen. Ein Konfigurationswerkzeug sorgt dafür, die Infrastrukturkomponenten in diesen Sollzustand zu überführen, beispielsweise installiert es Pakete und modifiziert Dateien. Vorteil dieses Ansatzes sind die idempotente Ausführbarkeit, die Anwendbarkeit auf beliebig viele Instanzen und die einfacher lesbare Dokumentation der Infrastrukturkonfiguration verglichen mit skriptbasierten Installationen.
Die Pflege von Kommunikationsbeziehungen ist eine weitere aufwändige Routineaufgabe. Ein Ansatz, diese zu vereinfachen, ist die Verwendung von dynamischen Service-Discovery-Mechanismen wie in Abbildung 3 skizziert. Dazu melden sich Servicegeber bei einer Service Registry an und ab. Die Servicenehmer wiederum werden zur Laufzeit von der Service Registry über Änderungen informiert und können sich dynamisch umkonfigurieren. Dies erlaubt das vollautomatische Einrichten und Entfernen einzelner Instanzen, ohne alle davon betroffenen Dienste zu bestimmen und zentral umkonfigurieren zu müssen. So wissen die Dienste durch die Registry auch bei einer sich verändernden Umgebung, welche anderen Dienste aktiv sind und in welcher Anzahl.

Abb. 3: Einsatz einer Service Registry

Ein weiterer Vorteil bei der Einführung von automatisierten Betriebsabläufen ist die Möglichkeit, Standardaufgaben vorab zu modellieren und als Softwareartefakte auf die Systeme zu verteilen. So können diese Aufgaben zentralisiert angesteuert und die Ergebnisse ausgewertet werden, ohne dass die Einzelschritte individuell auf jedem System ausgeführt werden müssen. Weiterhin besteht die Möglichkeit der Rechteseparation zwischen Ersteller und Nutzer des Ablaufs. Hierdurch können bestimmte Standardaufgaben auch von weniger spezialisiertem Personal übernommen werden, ohne einen direkten Login auf den Systemen bereitstellen zu müssen.

Prinzip 9: Skalierung proaktiv steuern

Neben der Ressourcenbereitstellung und den betrieblichen Prozessen ist die Planung von IT-Kapazitäten ein weiterer wichtiger Aspekt für die Skalierbarkeit der Plattform. Hierbei ist das Ziel, möglichst genau die weitere Entwicklung zu antizipieren und die passenden IT-Kapazitäten zum Zeitpunkt des Bedarfs bereitzustellen. In diesem Sinne sind neben den technischen Ansätzen auch die prozessualen Rahmenbedingungen zu schaffen. Lang laufende Freigabeprozesse und weit im Voraus bereitgestellte Ressourcen sind kostenintensiv, da die erwarteten Leistungen erst zeitverzögert bereitgestellt werden können. Das Ziel sollte eher ein proaktives Überwachen und Steuern der Skalierung sein.

Bei einer ideal skalierenden Plattform entspricht die Kapazitätsplanung einer linearen Skalierung der IT-Ressourcen zum vorhergesagten Lastaufkommen. Je schneller Kapazitäten bereitgestellt werden können, desto kürzer ist der Vorhersagebereich und desto genauer werden diese Planzahlen. Zu beachten ist dabei, dass rechtzeitig und vor allem an der richtigen Stelle skaliert wird. Denn auch hierfür gilt die „Theory of Constraints“, d. h., nur die Behebung des Engpasses sorgt für einen höheren Durchsatz. Wird fälschlicherweise nach dem Engpass skaliert, werden die eingesetzten Ressourcen verschwendet, da die Latenz weiterhin durch den Engpass vorgegeben ist. Erfolgt die Skalierung vor dem Engpass, kann die höhere Last das Problem noch verschärfen oder sogar zum Ausfall führen. Vermeiden lässt sich dies durch ein möglichst detailliertes Bild aus mehrdimensional verfolgten Skalierungsmetriken. Die Überwachung typischer Metriken wie die Anzahl verarbeiteter Nachrichten, Cache Hit Rate, Durchsatz an Anfragen und Latenz sollte möglichst auf allen beteiligten Ebenen, von der Applikation über die Ablaufumgebung zu den Parametern der VM bis hin zum Netzwerk und dem Rechenzentrum erfolgen. So kann die Entstehung eines Engpasses frühzeitig antizipiert werden.

Sind die Anforderungen an die Elastizität der Plattform besonders hoch, sprich die Lastphasen über einen kurzen Zeitraum sehr unterschiedlich, kann durch Einsatz eines Hybrid-Cloud-Modells oder mit ausreichend vorgehaltenen Ressourcen die Skalierung weiter automatisiert werden. Ein Watchdog steuert dabei die Plattformskalierung, indem die Metriken in Echtzeit analysiert werden. Die Plattform wird so in definierten Grenzen dazu in die Lage versetzt, zu „atmen“, also selbstständig Ressourcen hinzuzufügen oder zu entfernen. Dieser zusätzliche Automatismus entlastet das Betriebsteam von einer weiteren Routinetätigkeit und erlaubt es, mehr Zeit in konzeptionelle Aufgaben zu investieren und den Entwicklungsteams beratend zu Seite zu stehen.

Prinzip 10: Resilienz und Fehlertoleranz

Bisher wurden Prinzipien beschrieben, um den architektonischen Ansatz eines verteilten Systems mit einer großen Anzahl an Automatismen und Standardisierungen handhabbar und kosteneffizient zu gestalten. Auch wenn dadurch die einzelnen Elemente stabiler zusammenspielen, bleibt es ein System mit einer hohen Anzahl an beweglichen Einzelteilen. In einem solchen System lohnt sich der Perspektivwechsel weg von maximaler Stabilität einzelner Instanzen hin zu einem sich schnell erholenden, resilienten System mit geringer Wiederherstellungszeit. Einzelne Instanzen werden nicht mehr aufwändig gepflegt, sondern können im Fehlerfall durch eine neue Instanz ersetzt werden („Pet vs. Cattle“). Verlässt eine Instanz den definierten Korridor und lässt sich nicht automatisch reparieren, wird sie verworfen. Mit den bereits vorgestellten Ansätzen ist die automatisierte Neuinstallation eines Diensts einfacher als eine Reparatur – solange der Dienst zustandslos gestaltet ist.

Verteilte Systeme sind im Vergleich zu Monolithen zudem stärker auf das Netzwerk angewiesen und haben dadurch mit limitierter Bandbreite, Latenz und Netzwerkaussetzern zu rechnen. Zwar sinkt mit der durch eine horizontale Skalierung einhergehenden Redundanz das Risiko, dass ein Ausfall einer Instanz geschäftskritisch ist. Allerdings gilt dies nur, wenn die Kommunikationsbeziehungen fehlertolerant sind und eintretende Fehler nicht durch das gesamte System verbreitet werden. Architekturelle Muster wie die im ersten Beitrag vorgestellte asynchrone Kommunikation über Broker oder die Trennung von Code und Ressourcen helfen, auftretende Probleme zu kapseln. In der Umsetzung muss abgewogen werden, ob diese Muster durch einheitliche Infrastrukturkomponenten oder individuell durch die Entwicklungsteams umgesetzt werden. Beispielsweise existieren Ansätze zum fehlertoleranten Umgang mit Ressourcen bereits als Referenzimplementierungen. Ein solches Muster ist der „Circuit Breaker“, abgeleitet von einer Sicherung im Stromkreis. Dieser prüft auf einer Dienstinstanz den Zustand einer ausgehenden Kommunikationsbeziehung. Erreicht dabei die Mischung aus Zeitüberschreitung, Fehlern und Latenz einen festgelegten Schwellwert, unterbricht der Circuit Breaker die Kommunikation und reagiert mit einem vorgegebenen Notfallverhalten wie der Lieferung von Leerdaten, statischen Daten oder einer Fehlerseite bis die angebundene Ressource wieder erreichbar ist. Dies verhindert die Fortpflanzung des Fehlers und vermeidet langlaufende Anfragen und Wiederholungsversuche. Diesem Mehrwert stehen Performanceeinbußen durch die zusätzliche Logik gegenüber (Abb. 4).

Abb. 4: Schematischer Programmablauf eines Circuit Breakers

Fazit

Um sich am Markt behaupten zu können, müssen Unternehmen Innovationen sehr schnell erproben und auf sich verändernde Kundenwünsche reagieren können. Horizontal skalierende Architekturen haben sich hierfür als flexibel genug erwiesen. Die dabei steigende Anzahl beweglicher Einzelteile erfordert allerdings ein Umdenken bei Installation, Konfiguration und Betrieb des Systems. Es zeigen sich, neben der Wahl der Betriebsplattform und einer Delivery-Strategie, die durchgängige Automatisierung und der aktive Umgang mit Skalierung (auch nach unten) als kritische Faktoren. Eine skalierbare Softwarearchitektur ohne diese passenden Rahmenbedingungen kann teuer oder sogar wirkungslos sein. Um den beschriebenen Veränderungen auch auf organisatorischer Ebene gerecht zu werden, sollte die Etablierung einer gemeinsamen Verantwortung für das System durch enge Zusammenarbeit und Aufweichen der klassischen Expertenrollen gefördert werden.

Aufmacherbild: Signpost Scalability von Shutterstock / Urheberrecht: mindscanner

Geschrieben von
Christian Neudert
Christian Neudert
Christian Neudert ist Consultant bei Cassini Consulting und berät u. a. Blue Chips in den Bereichen Softwarearchitektur und agile Softwareentwicklung.
Dustin Huptas
Dustin Huptas
Dustin Huptas ist Management Consultant bei Cassini Consulting. Er konzeptioniert Systemarchitekturen, hoch skalierende Webplattformen und sichere IT-Infrastrukturen für Klienten mit herausfordernden Aufgabenstellungen.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: